Como importar o repositório Git existente para outro?


476

Eu tenho um repositório Git em uma pasta chamada XXX e eu tenho um segundo repositório Git chamado YYY .

Desejo importar o repositório XXX para o repositório YYY como um subdiretório chamado ZZZ e adicionar todo o histórico de alterações do XXX ao YYY .

Estrutura da pasta antes:

├── XXX
│   ├── .git
│   └── (project files)
└── YYY
    ├── .git
    └── (project files)

Estrutura de pastas após:

YYY
├── .git  <-- This now contains the change history from XXX
├──  ZZZ  <-- This was originally XXX
│    └── (project files)
└──  (project files)

Isso pode ser feito ou devo recorrer ao uso de submódulos?


2
No Github é agora possível fazer isso a partir da interface web quando você criar um novo repo
bgcode

Respostas:


430

Provavelmente, a maneira mais simples seria colocar o material XXX em uma ramificação no AAAA e mesclar no master:

Em AAA :

git remote add other /path/to/XXX
git fetch other
git checkout -b ZZZ other/master
mkdir ZZZ
git mv stuff ZZZ/stuff                      # repeat as necessary for each file/dir
git commit -m "Moved stuff to ZZZ"
git checkout master                
git merge ZZZ --allow-unrelated-histories   # should add ZZZ/ to master
git commit
git remote rm other
git branch -d ZZZ                           # to get rid of the extra branch before pushing
git push                                    # if you have a remote, that is

Na verdade, eu apenas tentei isso com alguns dos meus repositórios e funciona. Diferentemente da resposta de Jörg, ele não permitirá que você continue usando o outro repositório, mas acho que você não especificou isso.

Nota: Como isso foi originalmente escrito em 2009, o git adicionou a mesclagem de subárvore mencionada na resposta abaixo. Eu provavelmente usaria esse método hoje, embora, é claro, esse método ainda funcione.


1
Obrigado. Usei uma versão ligeiramente modificada da sua técnica: criei uma ramificação 'staging' no XXX, onde criei a pasta ZZZ e movi as 'coisas' para ela. Então, fundei XXX no AAA.
Vijay Patel

1
Isso funcionou muito bem para mim. As únicas alterações que fiz foram: 1) "git branch -d ZZZ" antes do push, porque eu não queria esse ramo temporário por aí. 2) "git push" estava me dando o erro: "Nenhum refs em comum e nenhum especificado; não fazendo nada. Talvez você deva especificar um ramo como 'master'." (A origem para a qual eu estava empurrando era um repositório vazio). Mas "git push --all" funcionava como um campeão.
22711 CrazyPyro

1
Queria terminar apenas com a pasta ZZZ e o histórico no repositório YYY: queria excluir o repositório XXX original e a ramificação ZZZ no repositório YYY. Eu encontrei a exclusão da ramificação ZZZ como o @CrazyPyro sugeriu que removeu o histórico - para mantê-la, mesclei a ramificação ZZZ no master antes de excluir.
Oli Studholme

4
@SebastianBlask Eu apenas brinquei com isso com dois dos meus repos e percebi que havia uma etapa que ninguém parecia notar, apesar de eu ter votado nisso há anos. :-) Mencionei mesclar no master, mas não o mostrei. Editando agora ...
ebneter 28/02

2
você pode adicionar algo assim, ao mover arquivos para a subpasta: git mv $(ls|grep -v <your foldername>) <your foldername>/ Isso copiará todos os arquivos e pastas para a nova pasta
serup

367

Se você deseja manter o histórico exato de confirmação do segundo repositório e, portanto, também mantém a capacidade de mesclar facilmente as alterações upstream no futuro, eis o método que você deseja. Isso resulta no histórico não modificado da subárvore sendo importada para seu repositório, mais uma confirmação de mesclagem para mover o repositório mesclado para o subdiretório.

git remote add XXX_remote <path-or-url-to-XXX-repo>
git fetch XXX_remote
git merge -s ours --no-commit --allow-unrelated-histories XXX_remote/master
git read-tree --prefix=ZZZ/ -u XXX_remote/master
git commit -m "Imported XXX as a subtree."

Você pode acompanhar as alterações upstream da seguinte maneira:

git pull -s subtree XXX_remote master

O Git descobre por si próprio onde estão as raízes antes de fazer a mesclagem, para que você não precise especificar o prefixo nas mesclagens subsequentes.

A desvantagem é que, no histórico mesclado, os arquivos não têm prefixo (não estão em um subdiretório). Como resultado git log ZZZ/a, todas as alterações serão exibidas (se houver), exceto aquelas no histórico mesclado. Você pode fazer:

git log --follow -- a

mas isso não mostrará as alterações além do histórico mesclado.

Em outras palavras, se você não alterar ZZZos arquivos no repositório XXX, precisará especificar --followum caminho sem prefixo. Se você os alterar nos dois repositórios, terá 2 comandos, nenhum dos quais mostra todas as alterações.

Versões Git anteriores à 2.9 : você não precisa passar a --allow-unrelated-historiesopção para git merge.

O método na outra resposta que usa read-treee pula a merge -s oursetapa não é diferente de copiar os arquivos com cp e confirmar o resultado.

A fonte original era do artigo de ajuda "Subtree Merge" do github . E outro link útil .


9
isso parece não ter preservado o histórico ... se eu fizer um git logdos arquivos que eu puxei, apenas vejo a mesclagem única ser confirmada e nada da sua vida anterior no outro repositório? Git 1.8.0
Anentropic

8
aha! se eu usar o velho caminho do arquivo importado, ou seja, omitir o subdir ele foi importado para, em seguida, git log vai me dar a história cometer, por exemplo, git log -- myfileem vez degit log -- rack/myfile
Anentropic

2
@FrancescoFrassinelli, isso não é desejável? Trazer a história é um recurso desse método.
Patrickvacek 9/09/09

4
@FrancescoFrassinelli, se você não quer história, por que não fazer uma cópia regular? Estou tentando descobrir o que o levaria a esse método se não fosse o histórico - essa é a única razão pela qual usei esse método!
Patrickvacek 9/09/13

7
Desde o Git 2.9, você precisa da opção --allow-unrelated-historiesao fazer a mesclagem.
StuXnet

112

git-subtreeé um script projetado para exatamente esse caso de uso de mesclar vários repositórios em um, preservando o histórico (e / ou dividindo o histórico de subárvores, embora isso pareça irrelevante para esta pergunta). Ele é distribuído como parte da árvore git desde a liberação 1.7.11 .

Para mesclar um repositório <repo>na revisão <rev>como subdiretório <prefix>, use git subtree addo seguinte:

git subtree add -P <prefix> <repo> <rev>

O git-subtree implementa a estratégia de mesclagem de subárvore de uma maneira mais amigável.

Para o seu caso, dentro do repositório YYY, você executaria:

git subtree add -P ZZZ /path/to/XXX.git master

A desvantagem é que, no histórico mesclado, os arquivos não têm prefixo (não estão em um subdiretório). Como resultado git log ZZZ/a, todas as alterações serão exibidas (se houver), exceto aquelas no histórico mesclado. Você pode fazer:

git log --follow -- a

mas isso não mostrará as alterações além do histórico mesclado.

Em outras palavras, se você não alterar ZZZos arquivos no repositório XXX, precisará especificar --followum caminho sem prefixo. Se você os alterar nos dois repositórios, terá 2 comandos, nenhum dos quais mostra todas as alterações.

Mais sobre isso aqui .


4
Se você tem um diretório para fundir em vez de um repositório nua ou remoto,git subtree add -P name-of-desired-prefix ~/location/of/git/repo-without-.git branch-name
Tatsh

2
Experiência noob: o git (versão 2.9.0.windows.1) responde "fatal: argumento ambíguo 'HEAD': revisão desconhecida ou caminho que não está na árvore de trabalho" quando tentei isso em um repositório local recém-inicializado, local e não nu, Mas funcionou bem depois que eu realmente iniciei o novo repositório, ou seja, depois de adicionar um arquivo simples e confirmar a maneira regular.
Stein

Funcionou lindamente para o meu cenário.
Johnny Utahh

Oh, isso é fantástico.
dwjohnston

Eu costumava @Tatsh sugestão e funcionou para mim
Carmine Tambascia

49

Existe uma instância bem conhecida disso no próprio repositório Git, que é coletivamente conhecido na comunidade Git como " a mesclagem mais legal de todos os tempos " (após o assunto Linus Torvalds usado no e-mail da lista de discussão do Git que descreve esse mesclar). Nesse caso, a gitkGUI do Git, que agora faz parte do Git, na verdade costumava ser um projeto separado. Linus conseguiu mesclar esse repositório no repositório Git de uma maneira que

  • ele aparece no repositório Git como se sempre tivesse sido desenvolvido como parte do Git,
  • toda a história é mantida intacta e
  • ainda pode ser desenvolvido de forma independente em seu repositório antigo, com as mudanças sendo simplesmente git pulleditadas.

O e-mail contém as etapas necessárias para a reprodução, mas não é para os fracos de coração: primeiro, Linus escreveu para o Git, então ele provavelmente sabe um pouco mais sobre isso do que você ou eu, e segundo, isso foi há quase 5 anos e o Git melhorou consideravelmente desde então, então talvez agora seja muito mais fácil.

Em particular, acho que hoje em dia se usaria um submódulo gitk, nesse caso específico.


3
Entre. a estratégia utilizada para fusões subsequentes (se houver algum) é chamado de sub merge, e não há terceiros git-subtreeferramenta que pode ajudá-lo com isso: github.com/apenwarr/git-subtree
Jakub Narębski

Obrigado, eu esqueci disso. A subtreeestratégia de mesclagem, especialmente em conjunto com a git-subtreeferramenta, é uma boa alternativa, talvez até superior, aos submódulos.
Jörg W Mittag

12

A maneira mais simples de fazer isso é usar o git format-patch.

Suponha que temos 2 repositórios git foo e bar .

foo contém:

  • foo.txt
  • .git

bar contém:

  • bar.txt
  • .git

e queremos terminar com foo contendo o histórico da barra e esses arquivos:

  • foo.txt
  • .git
  • foobar / bar.txt

Então, para fazer isso:

 1. create a temporary directory eg PATH_YOU_WANT/patch-bar
 2. go in bar directory
 3. git format-patch --root HEAD --no-stat -o PATH_YOU_WANT/patch-bar --src-prefix=a/foobar/ --dst-prefix=b/foobar/
 4. go in foo directory
 5. git am PATH_YOU_WANT/patch-bar/*

E se queremos reescrever todas as confirmações de mensagens da barra, podemos fazer, por exemplo, no Linux:

git filter-branch --msg-filter 'sed "1s/^/\[bar\] /"' COMMIT_SHA1_OF_THE_PARENT_OF_THE_FIRST_BAR_COMMIT..HEAD

Isso adicionará "[bar]" no início de cada mensagem de confirmação.


Se o repositório original contiver ramificações e mesclagens, git amprovavelmente falhará.
Adam Monsen

1
Pega menor: git am retira qualquer coisa [ ]da mensagem de confirmação. Portanto, você deve usar um marcador diferente de[bar]
HRJ 3/13

não funcionou para mim. Erro "obtido: foobar / mySubDir / test_host1: não existe no índice. A cópia do patch que falhou foi encontrada em: /home/myuser/src/proj/.git/rebase-apply/patch Quando você resolveu esse problema , execute "git am --continue". Isso foi após a aplicação de 11 patches (em 60).
oligofren

1
Este blog tem uma resposta semelhante a uma pergunta um pouco diferente (mover apenas os arquivos selecionados).
precisa saber é o seguinte

Eu vejo uma desvantagem, todos os commits são adicionados ao HEAD do repositório de destino.
CSchulz 23/09

8

Essa função clonará o repo remoto no dir repo local, após a mesclagem de todas as confirmações serem salvas, git logserão mostradas as confirmações originais e os 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 fizer algumas alterações, você pode até mover arquivos / diretórios de repositório mesclado para 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, portanto, verifique se ele se moveu nos caminhos adequados após a mesclagem.
O --allow-unrelated-historiesparâmetro existe apenas desde git> = 2.9.


2
Para o pessoal do OS X por aí, instale gnu-sedpara que a git-add-repofunção funcione. Mais uma vez obrigado Andrey!
precisa saber é o seguinte

7

Com base neste artigo , o uso da subárvore é o que funcionou para mim e apenas o histórico aplicável foi transferido. Poste aqui caso alguém precise das etapas (substitua os espaços reservados pelos valores aplicáveis ​​a você):

no repositório de origem, divida a subpasta em uma nova ramificação

git subtree split --prefix=<source-path-to-merge> -b subtree-split-result

no repositório de destino mesclado na ramificação de resultados divididos

git remote add merge-source-repo <path-to-your-source-repository>
git fetch merge-source-repo
git merge -s ours --no-commit merge-source-repo/subtree-split-result
git read-tree --prefix=<destination-path-to-merge-into> -u merge-source-repo/subtree-split-result

verifique suas alterações e confirme

git status
git commit

Não esqueça de

Limpar excluindo a subtree-split-resultramificação

git branch -D subtree-split-result

Remova o controle remoto que você adicionou para buscar os dados do repositório de origem

git remote rm merge-source-repo


3

Adicionando outra resposta, acho que isso é um pouco mais simples. Um pull de repo_dest é feito em repo_to_import e, em seguida, um push --set-upstream URL: repo_dest master.

Esse método funcionou para mim importando vários repositórios menores para um maior.

Como importar: repo1_to_import para repo_dest

# checkout your repo1_to_import if you don't have it already 
git clone url:repo1_to_import repo1_to_import
cd repo1_to_import

# now. pull all of repo_dest
git pull url:repo_dest
ls 
git status # shows Your branch is ahead of 'origin/master' by xx commits.
# now push to repo_dest
git push --set-upstream url:repo_dest master

# repeat for other repositories you want to import

Renomeie ou mova os arquivos e diretórios para a posição desejada no repositório original antes de fazer a importação. por exemplo

cd repo1_to_import
mkdir topDir
git add topDir
git mv this that and the other topDir/
git commit -m"move things into topDir in preparation for exporting into new repo"
# now do the pull and push to import

O método descrito no link a seguir inspirou essa resposta. Eu gostei, pois parecia mais simples. Mas cuidado! Há dragões! https://help.github.com/articles/importing-an-external-git-repository git push --mirror url:repo_dest envia o histórico e o estado do repo local para remoto (url: repo_dest). MAS exclui o histórico antigo e o estado do controle remoto. Segue diversão! : -E


1

Eu queria importar apenas alguns arquivos do outro repositório (XXX) no meu caso. A subárvore era muito complicada para mim e as outras soluções não funcionaram. Isto é o que eu fiz:

ALL_COMMITS=$(git log --reverse --pretty=format:%H -- ZZZ | tr '\n' ' ')

Isso fornece uma lista separada por espaço de todas as confirmações que afetam os arquivos que eu queria importar (ZZZ) na ordem inversa (talvez seja necessário adicionar --follow para capturar renomeados também). Depois fui para o repositório de destino (AAA), adicionei o outro repositório (XXX) como remoto, fiz uma busca a partir dele e, finalmente:

git cherry-pick $ALL_COMMITS

que adiciona todos os commits à sua ramificação, você terá todos os arquivos com o histórico deles e poderá fazer o que quiser com eles, como se eles sempre estivessem neste repositório.


1

Veja Exemplo básico no presente artigo e considerar esse mapeamento em repositórios:

  • A<-> YYY,
  • B <-> XXX

Após toda a atividade descrita neste capítulo (após a mesclagem), remova a ramificação B-master:

$ git branch -d B-master

Em seguida, pressione as alterações.

Funciona para mim.


0

Eu estava em uma situação em que estava procurando, -s theirsmas é claro que essa estratégia não existe. Minha história era que eu havia bifurcado um projeto no GitHub e, agora, por algum motivo, masternão foi possível mesclar meu local, upstream/masterembora eu não tenha feito alterações locais nesse ramo. (Realmente não sei o que aconteceu lá - acho que a montante tinha feito alguns empurrões sujos nos bastidores, talvez?)

O que acabei fazendo foi

# as per https://help.github.com/articles/syncing-a-fork/
git fetch upstream
git checkout master
git merge upstream/master
....
# Lots of conflicts, ended up just abandonging this approach
git reset --hard   # Ditch failed merge
git checkout upstream/master
# Now in detached state
git branch -d master # !
git checkout -b master   # create new master from upstream/master

Então agora o meu masterestá novamente sincronizado upstream/master(e você pode repetir o acima para qualquer outro ramo que também queira sincronizar da mesma forma).


1
A git reset --hard upstream/masterem sua masterfilial local faria o trabalho. Dessa forma, você não perde o conflito de ramificação local - coisas como o upstream padrão.
tomekwi

0

Posso sugerir outra solução (alternativa aos submódulos git ) para o seu problema - ferramenta gil (git links)

Permite descrever e gerenciar dependências complexas de repositórios git.

Também fornece uma solução para o problema de dependência de sub-módulos recursivos do git .

Considere que você possui as seguintes dependências do projeto: exemplo de gráfico de dependência do repositório git

Em seguida, você pode definir o .gitlinksarquivo com a descrição da relação dos repositórios:

# Projects
CppBenchmark CppBenchmark https://github.com/chronoxor/CppBenchmark.git master
CppCommon CppCommon https://github.com/chronoxor/CppCommon.git master
CppLogging CppLogging https://github.com/chronoxor/CppLogging.git master

# Modules
Catch2 modules/Catch2 https://github.com/catchorg/Catch2.git master
cpp-optparse modules/cpp-optparse https://github.com/weisslj/cpp-optparse.git master
fmt modules/fmt https://github.com/fmtlib/fmt.git master
HdrHistogram modules/HdrHistogram https://github.com/HdrHistogram/HdrHistogram_c.git master
zlib modules/zlib https://github.com/madler/zlib.git master

# Scripts
build scripts/build https://github.com/chronoxor/CppBuildScripts.git master
cmake scripts/cmake https://github.com/chronoxor/CppCMakeScripts.git master

Cada linha descreve o link git no seguinte formato:

  1. Nome exclusivo do repositório
  2. Caminho relativo do repositório (iniciado a partir do caminho do arquivo .gitlinks)
  3. Repositório Git que será usado no comando git clone Ramificação do repositório para checkout
  4. Linha vazia ou a linha iniciada com # não é analisada (tratada como comentário).

Finalmente, você deve atualizar seu repositório de amostras raiz:

# Clone and link all git links dependencies from .gitlinks file
gil clone
gil link

# The same result with a single command
gil update

Como resultado, você clonará todos os projetos necessários e os vinculará de maneira adequada.

Se você deseja confirmar todas as alterações em algum repositório com todas as alterações em repositórios vinculados a filhos, é possível fazê-lo com um único comando:

gil commit -a -m "Some big update"

Os comandos pull, push funcionam de maneira semelhante:

gil pull
gil push

A ferramenta Gil (git links) suporta os seguintes comandos:

usage: gil command arguments
Supported commands:
    help - show this help
    context - command will show the current git link context of the current directory
    clone - clone all repositories that are missed in the current context
    link - link all repositories that are missed in the current context
    update - clone and link in a single operation
    pull - pull all repositories in the current directory
    push - push all repositories in the current directory
    commit - commit all repositories in the current directory

Mais sobre o problema de dependência dos sub-módulos recursivos git .


0

Deixe-me usar nomes a(no lugar de XXXe ZZZ) e b(no lugar de YYY), pois isso facilita a leitura da descrição.

Digamos que você deseja mesclar repositório apara b(eu estou supondo que eles estão localizados ao lado do outro):

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Para isso, você precisa git-filter-repoinstalar ( filter-branché desencorajado ).

Um exemplo de mesclagem de 2 repositórios grandes, colocando um deles em um subdiretório: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Mais sobre isso aqui .


-1

Não conheço uma maneira fácil de fazer isso. Você PODE fazer isso:

  1. Use git filter-branch para adicionar um subdiretório ZZZ no repositório XXX
  2. Envie a nova ramificação para o repositório YYY
  3. Mesclar o ramo empurrado com o tronco de YYY.

Posso editar com detalhes, se isso parecer atraente.


-2

Eu acho que você pode fazer isso usando 'git mv' e 'git pull'.

Eu sou um bom git noob - tenha cuidado com seu repositório principal - mas tentei isso em um diretório temporário e ele parece funcionar.

Primeiro - renomeie a estrutura de XXX para corresponder à aparência que deseja quando estiver dentro de AAAA:

cd XXX
mkdir tmp
git mv ZZZ tmp/ZZZ
git mv tmp ZZZ

Agora XXX fica assim:

XXX
 |- ZZZ
     |- ZZZ

Agora use 'git pull' para buscar as alterações:

cd ../YYY
git pull ../XXX

Agora YYY é assim:

YYY
 |- ZZZ
     |- ZZZ
 |- (other folders that already were in YYY)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.