Como você mesclar dois repositórios Git?
-
07-07-2019 - |
Pergunta
Considere o seguinte cenário:
Eu tenho desenvolvido um pequeno projeto experimental Um em seu próprio repositório Git.Ele tem amadurecido, e eu gostaria de Um para ser parte de um grande projeto B, que tem a sua própria grande repositório.Eu agora gostaria de adicionar Uma como um subdiretório do B.
Como faço para mesclar A para B, sem perder a história em qualquer lado?
Solução
Um único ramo de outro repositório pode ser facilmente colocado em um subdiretório mantendo a sua história. Por exemplo:
git subtree add --prefix=rails git://github.com/rails/rails.git master
Esta aparecerá como uma única confirmação onde todos os arquivos de Rails branch master são adicionados no diretório "rails". No entanto, a cometer o título contém uma referência à árvore velha história:
Adicione 'rails /' de cometer
<rev>
Onde <rev>
é um SHA-1 cometer hash. Você ainda pode ver a história, a culpa algumas mudanças.
git log <rev>
git blame <rev> -- README.md
Note que você não pode ver o prefixo de diretório a partir daqui uma vez que este é um ramo de idade real deixada intacta. Você deve tratar isso como um movimento de arquivo de costume cometer: você vai precisar de um salto extra quando alcançá-lo
.# finishes with all files added at once commit
git log rails/README.md
# then continue from original tree
git log <rev> -- README.md
Existem soluções mais complexas como fazer isso manualmente ou reescrever a história como descrito em outras respostas.
O comando git-sub é uma parte do git-contrib oficial, alguns gestores de pacotes instalá-lo por padrão (OS X Homebrew). Mas você pode ter que instalá-lo por si mesmo, além de git.
Outras dicas
Se você quiser project-a
fundem em project-b
:
cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a
Retirado de:? git merge diferentes repositórios
Este método funcionou muito bem para mim, é mais curto e em minha opinião muito mais limpo.
Nota: O parâmetro --allow-unrelated-histories
só existe desde git> = 2,9. Consulte Git - merge git Documentação / --allow- alheios-histórias
Atualizar :. --tags
Adicionado como sugerido por @jstadler a fim de manter as tags
Aqui estão duas soluções possíveis:
submódulos
De qualquer copiar repositório A em um diretório separado no maior projeto de B, ou (talvez melhor) clone repositório A em um subdiretório no projecto B. Em seguida, use git submódulo para tornar este um repositório < strong> submodule de um repositório B.
Esta é uma boa solução para repositórios fracamente acoplados, onde o desenvolvimento em um repositório continua, e a maior parte do desenvolvimento é um desenvolvimento autônomo separado na A. Veja também SubmoduleSupport e GitSubmoduleTutorial páginas Git Wiki.
Subtree fundir
Você pode mesclar repositório A em um subdiretório de um projeto B usando o merge sub estratégia. Isto é descrito em Subtree Mesclando e você por Markus Prinz.
git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master
(é necessária --allow-unrelated-histories
Opção para GIT> = 2.9.0.)
Ou você pode usar git subárvore ferramenta ( repositório no GitHub ) por apenwarr (Avery Pennarun), anunciou, por exemplo, em seu blog Uma nova alternativa para submódulos git:. git subárvore
Eu acho que no seu caso (A é ser parte do maior projeto B) a solução correta seria a utilização de merge sub .
A abordagem submodule é bom se você quiser manter o projeto separadamente. No entanto, se você realmente quer fundir os dois projetos no mesmo repositório, então você tem um pouco mais trabalho a fazer.
A primeira coisa seria usar git filter-branch
para reescrever os nomes de tudo no segundo repositório para estar no subdiretório onde você gostaria que eles acabam. Então, ao invés de foo.c
, bar.html
, você teria projb/foo.c
e projb/bar.html
.
Em seguida, você deve ser capaz de fazer algo como o seguinte:
git remote add projb [wherever]
git pull projb
O git pull
vai fazer um git fetch
seguido por um git merge
. Não deve haver conflitos, se o repositório você está puxando para ainda não tem um diretório projb/
.
Além disso a pesquisa indica que algo semelhante foi feito para gitk
fundem em git
. Junio ??C Hamano escreve sobre ele aqui: http: //www.mail -archive.com/git@vger.kernel.org/msg03395.html
git-subtree
é agradável, mas provavelmente não é o que você quiser.
Por exemplo, se projectA
é o diretório criado no B, após git subtree
,
git log projectA
listas único cometer: a mesclagem. As submissões do projeto mesclada são para diferentes caminhos, para que eles não aparecem.
A resposta de Greg Hewgill mais se aproxima, embora não realmente dizer como reescrever os caminhos.
A solução é surpreendentemente simples.
(1) Em A,
PREFIX=projectA #adjust this
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$PREFIX"'/," |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD
. Nota: Este reescreve a história, por isso, se você pretende continuar usando este repo A, você pode querer clone (cópia) uma cópia descartável nisso antes
(2) Então, em B, run
git pull path/to/A
Voila! Você tem um diretório projectA
em B. Se você executar git log projectA
, você verá todas as submissões de A.
No meu caso, eu queria dois subdiretórios, projectA
e projectB
. Nesse caso, eu fiz o passo (1) para B também.
Se ambos os repositórios têm o mesmo tipo de arquivos (como dois trilhos repositórios para diferentes projetos), você pode buscar dados do repositório secundário para o seu repositório atual:
git fetch git://repository.url/repo.git master:branch_name
e, em seguida, fundi-lo ao repositório atual:
git merge --allow-unrelated-histories branch_name
Se a sua versão Git é menor do que 2,9, remove --allow-unrelated-histories
.
Depois disso, podem ocorrer conflitos. Você pode resolvê-los, por exemplo, com git mergetool
. kdiff3
pode ser usado apenas com o teclado, de modo 5 arquivo de conflito leva ao ler o código de apenas alguns minutos.
Lembre-se de concluir a fusão:
git commit
Eu continuava a perder história quando usando mala, então acabei usando rebase já que em meu caso, os dois repositórios são suficientes diferente para não acabar fundindo em cada commit:
git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB
cd projB
git remote add projA ../projA/
git fetch projA
git rebase projA/master HEAD
conflitos => resolver, em seguida, continuar, tantas vezes quanto necessário ...
git rebase --continue
Fazendo isso leva a um projecto que tem todas as submissões de proja seguido por commits de projB
No meu caso, eu tinha um my-plugin
repositório e um main-project
repositório, e eu queria fingir que my-plugin
sempre tinha sido desenvolvido no plugins
subdiretório de main-project
.
Basicamente, eu reescreveu a história do my-plugin
repositório para que ele apareceu todo o desenvolvimento teve lugar no plugins/my-plugin
subdiretório.Em seguida, acrescentei o desenvolvimento histórico das my-plugin
no main-project
história, e fundiram-se as duas árvores juntas.Como não houve plugins/my-plugin
o diretório já presente no main-project
repositório, este foi um trivial sem conflitos de mesclagem.A resultante repositório contida toda a história de ambos os projetos originais, e tinha duas raízes.
TL;DR
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
A versão longa
Primeiro, crie uma cópia do my-plugin
repositório, porque nós vamos reescrever a história deste repositório.
Agora, navegue até a raiz do my-plugin
repositório, confira o seu ramo principal (provavelmente master
), e execute o seguinte comando.Claro, você deve substituir por my-plugin
e plugins
qualquer que seja a sua nomes reais são.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Agora uma explicação. git filter-branch --tree-filter (...) HEAD
executa o (...)
comando em cada confirmar que é acessível a partir de HEAD
.Note que este atua diretamente sobre os dados armazenados para cada commit, por isso não tem de se preocupar com as noções de "diretório de trabalho", "índice", "teste", e assim por diante.
Se você executar um filter-branch
comando falhar, ele vai deixar para trás alguns arquivos no .git
diretório e da próxima vez que você tentar filter-branch
ele vai reclamar sobre isso, a menos que você fornecer a -f
opção para filter-branch
.
Como para o comando real, eu não tenho muita sorte para obter bash
para fazer o que eu queria, então, em vez disso, eu uso zsh -c
para fazer zsh
executar um comando.Primeiro eu definir o extended_glob
opção, que é o que permite a ^(...)
sintaxe no mv
comando, bem como o glob_dots
opção, o que me permite selecionar arquivos (tais como .gitignore
) com uma bola (^(...)
).
Seguinte, eu uso o mkdir -p
comando para criar plugins
e plugins/my-plugin
ao mesmo tempo.
Finalmente, eu uso o zsh
"negativo glob" recurso ^(.git|plugins)
para corresponder a todos os arquivos no diretório raiz do repositório, exceto para .git
e a recém-criada my-plugin
a pasta.(Excluindo .git
pode não ser necessário aqui, mas tentando mover um diretório em si é um erro.)
No meu repositório, a inicial cometer não incluir todos os arquivos, de modo que o mv
comando retornou um erro na inicial commit (uma vez que nada estava disponível para se mover).Por isso, eu adicionei um || true
de modo que git filter-branch
não iria abortar.
O --all
opção diz filter-branch
para reescrever a história para todos ramos do repositório, e o extra --
é necessário dizer git
a interpretá-lo como uma parte da lista de opção para ramos, reescrever, em vez de como uma opção para filter-branch
em si.
Agora, navegue até o seu main-project
repositório e confira tudo o ramo que você deseja mesclar.Adicionar a sua cópia local do my-plugin
repositório (com sua história modificada) como um controle remoto de main-project
com:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Agora você terá dois relacionados árvores em seu cometer história, que você pode visualizar muito bem a utilizar:
$ git log --color --graph --decorate --all
Para fundi-los, use:
$ git merge my-plugin/master --allow-unrelated-histories
Observe que na pré-2.9.0 Git, o --allow-unrelated-histories
opção não existe.Se você estiver usando uma dessas versões, basta omitir a opção:a mensagem de erro que --allow-unrelated-histories
impede que foi também adicionado em 2.9.0.
Você não deve ter quaisquer conflitos de mesclagem.Se o fizer, isso provavelmente significa que o filter-branch
comando não funcionar corretamente ou já havia uma plugins/my-plugin
diretório no main-project
.
Certifique-se de introduzir uma exposição de mensagem de commit para qualquer futuro contribuintes está imaginando para que hackery estava indo para fazer um repositório com duas raízes.
Você pode visualizar o novo commit gráfico, que deve ter duas raiz compromete-se, usando o acima git log
de comando.Note que só o master
ramo será mesclado.Isso significa que se você tem um trabalho importante em outros my-plugin
ramos que você deseja mesclar com o main-project
árvore, você deve abster-se de eliminar o my-plugin
controle remoto até que você tem feito essas mescla.Se você não o fizer, então a compromete-se a partir desses ramos ainda estará no main-project
repositório, mas alguns serão inacessíveis e suscetíveis a eventual coleta de lixo.(Além disso, você terá que se referir a eles pelo SHA, porque a exclusão de um remoto remove sua remoto-controle de ramos.)
Opcionalmente, depois de ter mesclado de tudo que você quer manter-se de my-plugin
, você pode remover o my-plugin
remoto usando:
$ git remote remove my-plugin
Agora você pode excluir com segurança a cópia do my-plugin
repositório cuja história você mudou.No meu caso, eu também adicionei um aviso de substituição para o real my-plugin
repositório após a fusão foi concluída e empurrou.
Testado no Mac OS X El Capitan com git --version 2.9.0
e zsh --version 5.2
.Sua milhagem pode variar.
Referências:
- https://git-scm.com/docs/git-filter-branch
- https://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-including-hidden-from-one-directory-to-another
- http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
- A purga de arquivo do repositório Git falha, não é possível criar nova cópia de segurança
- o git, filter-branch em todos os ramos
Eu tenho tentado fazer a mesma coisa para os dias, estou usando git 2.7.2. não Subtree não preservar a história.
Você pode usar este método se você não vai usar o velho projeto novamente.
Gostaria de sugerir que você ramo B em primeiro lugar e trabalho no ramo.
Aqui estão os passos sem ramificação:
cd B
# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B
# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B
git add .
git commit -m "Moving content of project B in preparation for merge from A"
# Now merge A into B
git remote add -f A <A repo url>
git merge A/<branch>
mkdir A
# move all the files into subdir A, excluding .git
git mv <files> A
git commit -m "Moved A into subdir"
# Move B's files back to root
git mv B/* ./
rm -rf B
git commit -m "Reset B to original state"
git push
Se você já log qualquer um dos arquivos em subdir A você vai ter a história completa ??p>
git log --follow A/<file>
Este foi o post que me ajudar a fazer isso:
Se você deseja colocar os arquivos de uma filial em repo B em um sub de repo A e também preservar a história, continue lendo. (No exemplo abaixo, eu estou supondo que queremos repo branch master do B fundidos em repo de um branch master.)
Em repo A, primeiro faça o seguinte para fazer repo B disponíveis:
git remote add B ../B # Add repo B as a new remote.
git fetch B
Agora vamos criar um novo ramo da marca (com apenas um commit) em repo A que chamamos new_b_root
. O resultando cometer terá os arquivos que foram cometidos na primeira confirmação de branch master repo do B, mas colocados em uma subpasta chamada path/to/b-files/
.
git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"
Explicação: A opção --orphan
ao comando check-out cheques fora os arquivos de um branch master mas não cria qualquer cometer. Poderíamos ter escolhido qualquer cometer porque seguinte, limpar todos os arquivos de qualquer maneira. Então, sem cometer ainda (-n
), nós cereja-escolher o primeiro comprometer a partir branch master do B. (The cherry-pick preserva o original mensagem de confirmação que um checkout reta parece não fazer.) Então vamos criar a subárvore onde queremos colocar todos os arquivos do repo B. Em seguida, tem que mover todos os arquivos que foram introduzidas no cherry-pick para a sub-árvore. No exemplo acima, há apenas um arquivo README
para mover. Então nós nos comprometemos nossa raiz B-repo cometer, e, ao mesmo tempo, preserve o timestamp do original cometer.
Agora, vamos criar um novo ramo B/master
no topo do new_b_root
recém-criado. Chamamos a nova b
ramo:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
Agora, nos fundimos o nosso ramo b
em A/master
:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
Finalmente, você pode remover o B
remoto e ramos temporários:
git remote remove B
git branch -D new_b_root b
O gráfico final terá uma estrutura como esta:
Eu sei que é muito tempo após o fato, mas eu não estava feliz com as outras respostas que eu encontrei aqui, então eu escrevi o seguinte:
me=$(basename $0)
TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo
echo "building new repo in $TMP"
echo
sleep 1
set -e
cd $TMP
mkdir new-repo
cd new-repo
git init
cd ..
x=0
while [ -n "$1" ]; do
repo="$1"; shift
git clone "$repo"
dirname=$(basename $repo | sed -e 's/\s/-/g')
if [[ $dirname =~ ^git:.*\.git$ ]]; then
dirname=$(echo $dirname | sed s/.git$//)
fi
cd $dirname
git remote rm origin
git filter-branch --tree-filter \
"(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
cd ..
cd new-repo
git pull --no-commit ../$dirname
[ $x -gt 0 ] && git commit -m "merge made by $me"
cd ..
x=$(( x + 1 ))
done
Eu recolhi muita informação aqui no estouro de pilha, etc., e têm conseguem colocar um conjunto de script que resolve o problema para mim.
A ressalva é que ele só leva em conta o ramo 'desenvolver' de cada repositório e funde-o em um diretório separado em um completamente novo repositório.
tags e outros ramos são ignoradas -. Isto pode não ser o que você quer
O script até mesmo alças apresentam ramos e rótulos - renomeando-os no novo projeto para que você saiba de onde vieram
.#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
## and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
## which are to be merged on separate lines.
##
## Author: Robert von Burg
## eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#
# disallow using undefined variables
shopt -s -o nounset
# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'
# Detect proper usage
if [ "$#" -ne "2" ] ; then
echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
exit 1
fi
## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"
# Script functions
function failed() {
echo -e "ERROR: Merging of projects failed:"
echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
echo -e "$1"
exit 1
}
function commit_merge() {
current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
if [[ ! -f ".git/MERGE_HEAD" ]] ; then
echo -e "INFO: No commit required."
echo -e "INFO: No commit required." >>${LOG_FILE} 2>&1
else
echo -e "INFO: Committing ${sub_project}..."
echo -e "INFO: Committing ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
fi
fi
}
# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
exit 1
fi
# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
exit 1
fi
# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo
# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do
if [[ "${url:0:1}" == '#' ]] ; then
continue
fi
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"
echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
echo -e "----------------------------------------------------"
echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1
# Fetch the project
echo -e "INFO: Fetching ${sub_project}..."
echo -e "INFO: Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
git remote add "${sub_project}" "${url}"
if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
failed "Failed to fetch project ${sub_project}"
fi
# add remote branches
echo -e "INFO: Creating local branches for ${sub_project}..."
echo -e "INFO: Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
while read branch ; do
branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)
echo -e "INFO: Creating branch ${branch_name}..."
echo -e "INFO: Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1
# create and checkout new merge branch off of master
if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
# Merge the project
echo -e "INFO: Merging ${sub_project}..."
echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
fi
# And now see if we need to commit (maybe there was a merge)
commit_merge "${sub_project}/${branch_name}"
# relocate projects files into own directory
if [ "$(ls)" == "${sub_project}" ] ; then
echo -e "WARN: Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
echo -e "WARN: Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
else
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
mkdir ${sub_project}
for f in $(ls -a) ; do
if [[ "$f" == "${sub_project}" ]] ||
[[ "$f" == "." ]] ||
[[ "$f" == ".." ]] ; then
continue
fi
git mv -k "$f" "${sub_project}/"
done
# commit the moving
if ! git commit --quiet -m "[Project] Move ${sub_project} files into sub directory" ; then
failed "Failed to commit moving of ${sub_project} files into sub directory"
fi
fi
echo
done < <(git ls-remote --heads ${sub_project})
# checkout master of sub probject
if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
failed "sub_project ${sub_project} is missing master branch!"
fi
# copy remote tags
echo -e "INFO: Copying tags for ${sub_project}..."
echo -e "INFO: Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
while read tag ; do
tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)
# hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
tag_name="${tag_name_unfixed%%^*}"
tag_new_name="${sub_project}/${tag_name}"
echo -e "INFO: Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
echo -e "INFO: Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
echo -e "WARN: Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
echo -e "WARN: Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
fi
done < <(git ls-remote --tags --refs ${sub_project})
# Remove the remote to the old project
echo -e "INFO: Removing remote ${sub_project}..."
echo -e "INFO: Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
git remote rm ${sub_project}
echo
done
# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do
if [[ ${url:0:1} == '#' ]] ; then
continue
fi
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."
echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
failed "Failed to merge branch ${sub_project}/master into master"
fi
# And now see if we need to commit (maybe there was a merge)
commit_merge "${sub_project}/master"
echo
done
# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo
exit 0
Você também pode obtê-lo a partir http://paste.ubuntu.com/11732805
Primeiro, crie um arquivo com o URL para cada repositório, por exemplo .:
git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git
Em seguida, chamar o script dar um nome do projeto e o caminho para o script:
./mergeGitRepositories.sh eitchnet_test eitchnet.lst
O script em si tem um monte de comentários que deve explicar o que faz.
Se você está tentando simplesmente cola dois repositórios juntos, submódulos e funde subárvore são a ferramenta errada para o uso porque não preservar toda a história de arquivo (como as pessoas têm notado em outras respostas). Veja esta resposta aqui para o simples e maneira correta de fazer isso.
Semelhante ao @Smar mas do sistema de arquivos usa caminhos, fixado em primária e secundária:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
Em seguida, você mesclar manualmente.
(adaptado de mensagem por Anar Manafov )
Eu tinha um desafio semelhante, mas no meu caso, que havia desenvolvido uma versão da base de código em repo A, então clonado que em um novo repo, repo B, para a nova versão do produto. Depois de corrigir alguns bugs no repo A, precisávamos FI as alterações no repo B. acabou fazendo o seguinte:
- Adicionando um controle remoto para repo B que apontavam para repo A (git remoto adicionar ...)
- Puxar o ramo atual (que não estávamos usando mestre para correções de bugs) (git pull remoteForRepoA bugFixBranch)
- Empurrando fusões para github
Trabalhou um deleite :)
Quando você deseja mesclar três ou mais projetos em um único comprometer, fazer os passos como descrito em outras respostas (remote add -f
, merge
). Então, (soft) redefinir o índice para a cabeça de idade (onde nenhuma fusão aconteceu). Adicionar todos os arquivos (git add -A
) e cometê-los (mensagem "Mesclar projectos A, B, C, e D em um projeto). Esta é agora a comprometer-id de mestre.
Agora, crie .git/info/grafts
com seguinte conteúdo:
<commit-id of master> <list of commit ids of all parents>
Executar git filter-branch -- head^..head head^2..head head^3..head
. Se você tiver mais de três ramos, basta adicionar o máximo head^n..head
como você tem filiais. Para tags de atualização, --tag-name-filter cat
acréscimo. Nem sempre acrescentar que, porque isso pode causar uma reescrita de alguns commits. Para mais detalhes consulte href="https://www.kernel.org/pub/software/scm/git/docs/git-filter-branch.html" de filter-branch , procurar "enxertos".
Agora, seu último commit tem os pais certos associado.
Para mesclar um A dentro B:
1) No projeto A
git fast-export --all --date-order > /tmp/ProjectAExport
2) No projeto B
git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport
Neste ramo fazer todas as operações que você precisa fazer e cometê-los.
C) Em seguida, de volta para o mestre e uma fusão clássica entre os dois ramos:
git checkout master
git merge projectA
A fusão 2 repos
git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2
delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master
Esta função irá clonar repo remoto para dir repo local, após a fusão todas as submissões serão salvos, git log
será mostrar os commits originais e caminhos adequados:
function git-add-repo
{
repo="$1"
dir="$(echo "$2" | sed 's/\/$//')"
path="$(pwd)"
tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"
git clone "$repo" "$tmp"
cd "$tmp"
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$dir"'/," |
GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"
}
Como usar:
cd current/package
git-add-repo https://github.com/example/example dir/to/save
Se fazer um pouco muda você pode até mesmo mover arquivos / diretórios de repo incorporada em caminhos diferentes, por exemplo:
repo="https://github.com/example/example"
path="$(pwd)"
tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g' | sed 's/\./_/g')"
git clone "$repo" "$tmp"
cd "$tmp"
GIT_ADD_STORED=""
function git-mv-store
{
from="$(echo "$1" | sed 's/\./\\./')"
to="$(echo "$2" | sed 's/\./\\./')"
GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}
# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'
git filter-branch --index-filter '
git ls-files -s |
sed "'"$GIT_ADD_STORED"'" |
GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
GIT_ADD_STORED=""
cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"
Avisos
Caminhos substitui via sed
, por isso certifique-se movia em caminhos adequados após a fusão.
O parâmetro --allow-unrelated-histories
só existe desde git> = 2.9.
Eu fundir projectos ligeiramente manualmente, o que me permite evitar a necessidade de lidar com conflitos de mesclagem.
Primeiro, copie nos arquivos do outro projeto, contudo, você quer que eles.
cp -R myotherproject newdirectory
git add newdirectory
próxima atração na história
git fetch path_or_url_to_other_repo
dizer git para mesclar na história da última coisa buscada
echo 'FETCH_HEAD' > .git/MERGE_HEAD
Agora comprometer, contudo, você normalmente iria cometer
git commit
comando dado é a melhor solução possível, sugiro.
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
Eu queria mover um pequeno projeto para um subdiretório de um maior. Desde o meu pequeno projeto não tinha muitos commits, eu usei git format-patch --output-directory /path/to/patch-dir
. Em seguida, no projeto maior, eu usei git am --directory=dir/in/project /path/to/patch-dir/*
.
Este sente maneira menos assustador e muito mais limpo do que um filter-branch. Concedido, pode não ser aplicável a todos os casos.