Combinando vários repositórios git

207

Digamos que eu tenho uma configuração que se parece com

phd/code/
phd/figures/
phd/thesis/

Por razões históricas, todos eles têm seus próprios repositórios git. Mas eu gostaria de combiná-los em um único para simplificar um pouco as coisas. Por exemplo, agora eu posso fazer dois conjuntos de alterações e ter que fazer algo como

cd phd/code
git commit 
cd ../figures
git commit

Seria (agora) bom apenas executar

cd phd
git commit

Parece haver algumas maneiras de fazer isso usando sub-módulos ou retirando dos meus sub-repositórios, mas isso é um pouco mais complexo do que estou procurando. No mínimo, eu ficaria feliz com

cd phd
git init
git add [[everything that's already in my other repositories]]

mas isso não parece uma linha. Existe algo gitque possa me ajudar?

Will Robertson
fonte
Considere também este grande abordagem: stackoverflow.com/questions/1425892/...
Johan Sjöberg
Considere também: saintgimp.org/2013/01/22/…
ptim 27/02
O script join-git-repos.py faz um bom trabalho se você tiver repositórios separados, cada um com ramificações principais que deseja combinar.
Mark

Respostas:

149

Aqui está uma solução que eu dei aqui :

  1. Primeiro, faça um backup completo do seu diretório phd: não quero ser responsabilizado pela perda de anos de trabalho duro! ;-)

    $ cp -r phd phd-backup
    
  2. Mova o conteúdo de phd/codepara phd/code/codee corrija o histórico para que pareça que ele sempre esteve lá (isso usa o comando filter-branch do git ):

    $ cd phd/code
    $ git filter-branch --index-filter \
        'git ls-files -s | sed "s#\t#&code/#" |
         GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
         git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
    
  3. O mesmo para o conteúdo de phd/figurese phd/thesis(basta substituir codepor figurese thesis).

    Agora sua estrutura de diretórios deve ficar assim:

    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
    
  4. Em seguida, crie um repositório git no diretório raiz, coloque tudo nele e remova os repositórios antigos:

    $ cd phd
    $ git init
    
    $ git pull code
    $ rm -rf code/code
    $ rm -rf code/.git
    
    $ git pull figures --allow-unrelated-histories
    $ rm -rf figures/figures
    $ rm -rf figures/.git
    
    $ git pull thesis --allow-unrelated-histories
    $ rm -rf thesis/thesis
    $ rm -rf thesis/.git
    

    Finalmente, agora você deve ter o que queria:

    phd
      |_.git
      |_code
      |    |_(your code...)
      |_figures
      |    |_(your figures...)
      |_thesis
           |_(your thesis...)
    

Um lado bom desse procedimento é que ele deixará arquivos e diretórios sem versão .

Espero que isto ajude.


Apenas uma palavra de aviso: se seu codediretório já possui um codesubdiretório ou arquivo, as coisas podem dar muito errado (o mesmo para figurese, é thesisclaro). Se for esse o caso, basta renomear esse diretório ou arquivo antes de executar todo este procedimento:

$ cd phd/code
$ git mv code code-repository-migration
$ git commit -m "preparing the code directory for migration"

E quando o procedimento for concluído, adicione esta etapa final:

$ cd phd
$ git mv code/code-repository-migration code/code
$ git commit -m "final step for code directory migration"

Obviamente, se o codesubdiretório ou arquivo não tiver versão, use em mvvez de git mve esqueça os git commits.

MiniQuark
fonte
13
Obrigado por esse trecho - ele fez exatamente o que eu precisava (uma vez que representaram Mac OS X não sed processamento "\ t" (Eu tive que usar ^ V ^ I vez).
Craig Trader
6
Não consegui fazer isso funcionar no começo e, finalmente, encontrei a solução para o problema em outro quadro de mensagens antigo. Na última linha, eu tive que colocar aspas nos nomes dos arquivos assim: mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADe depois funcionou muito bem!
Jorin
3
O comando funky filter-branch é das páginas de manual filter-branch do git. Você deve dizer o seguinte: a) deve ser atribuído corretamente b) não executarei esse comando apenas porque alguém, mesmo com alta reputação, o postou no StackOverflow. Sabendo que é das páginas de manual, eu irei.
tymtam
5
CUIDADO! O MacOS X não usa a extensão GNU do sed, portanto, não conhece a sequência \ t. O resultado é uma história confusa! Minha solução foi colar o código em um arquivo de script e escrever um caractere <TAB> real nele. No Terminal, uma guia pode ser inserida pressionando ctrl + ve, em seguida, escrevendo um <TAB>. Não tentei a solução de Craig
Gil Vegliach
5
ATENÇÃO (2)! Observe também que se alguns arquivos ou diretórios contiverem hífens ('-'), o comando sed falhará. Nesse caso, você pode substituí-lo por algo como 's ~ \ t ~ & code / ~'. Aqui, aplicando a mesma lógica, cuidado com '~' nos nomes
Gil Vegliach
75

git-stitch-repoprocessará a saída dos git-fast-export --all --date-orderrepositórios git fornecidos na linha de comando e criará um fluxo adequado para git-fast-importisso, criará um novo repositório contendo todos os commits em uma nova árvore de commit que respeite o histórico de todos os repositórios de origem.

Aristóteles Pagaltzis
fonte
33
Uh, é uma ferramenta de terceiros, que não faz parte do git…:
Aristóteles Pagaltzis 10/11/08
1
Na verdade, agora você me diz :) Oh bem, acho que eu tive que aprender a instalar CPAN empacota um dia ...
Will Robertson
1
Obrigado por apontar esse comando. Acabei de usá-lo para ajudar a mover algumas repos do SVN para o Git.
Signin 20/08/10
1
AVISO pode não funcionar se você tiver ramificações / mesclagens! Na página git-stich-repo : "o git-stich-repo funciona perfeitamente com repositórios que têm um histórico linear (sem mesclagens) ... As melhorias no algoritmo de costura adicionado na versão 0.06 devem ser adequadas para trabalhar com repositórios com ramifica e funde. "
Bryan P
6
Este é um script externo, a resposta é muito curta e não é realmente útil, este script tem problemas com confirmações de mesclagem, poucas pessoas lidariam com Perl ou CPAN e isso não é bem explicado na resposta. Então ... -1, desculpe.
Haralan Dobrev
20

Talvez, simplesmente (da mesma forma que a resposta anterior, mas usando comandos mais simples), faça de cada um dos repositórios antigos separados um commit que move o conteúdo para um subdiretador chamado adequadamente, por exemplo:

$ cd phd/code
$ mkdir code
# This won't work literally, because * would also match the new code/ subdir, but you understand what I mean:
$ git mv * code/
$ git commit -m "preparing the code directory for migration"

e depois mesclar os três repositórios separados em um novo, fazendo o seguinte:

$ cd ../..
$ mkdir phd.all
$ cd phd.all
$ git init
$ git pull ../phd/code
...

Então você salvará suas histórias, mas continuará com um único repositório.

imz - Ivan Zakharyaschev
fonte
Tudo bem, mas se você estiver mesclando um repositório em outro (por exemplo, o phd não era um repositório existente já vazio), se o phd tiver pastas com nomes iguais às subpastas no diretório de código, você encontrará problemas como 'git pull .. / phd / code 'puxa todos os commit com os caminhos originais e somente no final aplica o commit mv.
tymtam
1
@ Tymek: mas isso ainda funcionará nessa situação, sem problemas. O que não será legal é que os caminhos na história não estarão "corretos" (correspondem aos novos caminhos).
IMZ - Ivan Zakharyaschev
19

Você pode tentar a estratégia de mesclagem de subárvore . Isso permitirá que você mescle o repositório B no repositório A. A vantagem git-filter-branché que não exige que você reescreva seu histórico do repositório A (quebrando as somas SHA1).

Leif Gruenwoldt
fonte
O link não funciona e isso não preservaria a história, não é?
tymtam
3
@Tymek (desculpe, partes do kernel.org ainda estão inativas após a violação de segurança). Ele quebra os SHA1 do repo B. recebido, mas A permanece intacto.
Leif Gruenwoldt 01/12/11
2
Aqui está um espelho desse documento, por enquanto, ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/howto/…
Leif Gruenwoldt
1
@LeifGruenwoldt O primeiro link está funcionando agora. E o link do espelho acabou, você deve removê-lo, suponho.
Vadim Kotov
9

A solução git-filter-branch funciona bem, mas observe que se o seu repositório git vier de uma importação SVN, poderá falhar com uma mensagem como:

Rewrite 422a38a0e9d2c61098b98e6c56213ac83b7bacc2 (1/42)mv: cannot stat `/home/.../wikis/nodows/.git-rewrite/t/../index.new': No such file or directory

Nesse caso, você precisa excluir a revisão inicial da ramificação do filtro - ou seja, alterar HEADno final para [SHA of 2nd revision]..HEAD- consulte:

http://www.git.code-experiments.com/blog/2010/03/merging-git-repositories.html

Gareth
fonte
2
Obrigado! Estive coçando a cabeça por que isso não estava funcionando! O repo realmente veio do SVN.
Arthur Maltson 4/13/13
1
Mesmo erro quando faço isso. Tenho minhas esperanças. Além disso, o link agora está quebrado.
22714 Ryan
Você poderia elaborar o que quis dizer com "mudando a cabeça no para ...", meu repositório vem de uma importação SVN e estou enfrentando exatamente esse problema, gostaria muito de ajudar!
5

A solução @MiniQuark me ajudou muito, mas infelizmente não leva em consideração as tags que estão nos repositórios de origem (pelo menos no meu caso). Abaixo está a minha melhoria na resposta @MiniQuark.

  1. Primeiro crie um diretório que conterá repositórios compostos e repositórios mesclados, crie um diretório para cada um deles.

    $ mkdir new_phd
    $ mkdir new_phd / code
    $ mkdir new_phd / figures
    $ mkdir new_phd / tese

  2. Faça um pull de cada repositório e busque todas as tags. (Apresentando instruções apenas para o codesubdiretório)

    $ cd new_phd / code
    $ git init
    $ git pull ../../original_phd/code master
    $ git fetch ../../original_phd/code refs / tags / *: refs / tags / *

  3. (Isso é aprimoramento no ponto 2 da resposta do MiniQuark) Mova o conteúdo de new_phd/codepara new_phd/code/codee adicione o code_prefixo antes de cada tag

    $ git filter-branch --index-filter 'arquivos git ls -s | sed "s- \ t \" * - & code / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.novo git update-index --index-info && mv $ GIT_INDEX_FILE.novo $ GIT_INDEX_FILE.novo $ GIT_INDEX_FILE '- tag-nome-filtro' sed" s -. * - código _ & - "'CABEÇA

  4. Depois de fazer isso, haverá duas vezes mais tags do que antes de fazer o branch-filter. Tags antigas permanecem no repositório e novas tags com code_prefixo são adicionadas.

    tag $ git
    mytag1
    code_mytag1

    Remova as tags antigas manualmente:

    $ ls .git / refs / tags / * | grep -v "/ código_" | xargs rm

    Repita o ponto 2,3,4 para outros subdiretórios

  5. Agora, temos a estrutura de diretórios como no ponto 3 do @MiniQuark anwser.

  6. Faça como no ponto 4 do analisador MiniQuark, mas depois de fazer um pull e antes de remover o .gitdir, busque as tags:

    $ git fetch catalog refs / tags / *: refs / tags / *

    Continuar..

Esta é apenas outra solução. Espero que ajude alguém, me ajudou :)

MichK
fonte
5

git-stitch-repo da resposta de Aristóteles Pagaltzis ' funciona apenas para repositórios com história simples e linear.

A resposta do MiniQuark funciona para todos os repositórios, mas não trata de tags e ramificações.

Criei um programa que funciona da mesma maneira que o MiniQuark descreve, mas ele usa uma confirmação de mesclagem (com N pais) e também recria todas as tags e ramificações para apontar para essas confirmações de mesclagem.

Veja o repositório git-merge-repos para exemplos de como usá-lo.

robinst
fonte
3

Eu criei uma ferramenta que faz essa tarefa. O método usado é semelhante (faça internamente algumas coisas como --filter-branch), mas é mais amigável. É GPL 2.0

http://github.com/geppo12/GitCombineRepo

Giuseppe Monteleone
fonte
3

Na verdade, o git-stitch-repo agora suporta ramificações e tags, incluindo tags anotadas (descobri que havia um bug que relatei e ele foi corrigido). O que eu achei útil é com tags. Como as tags são anexadas às confirmações, e algumas das soluções (como a abordagem de Eric Lee) não conseguem lidar com as tags. Você tenta criar uma ramificação de uma marca importada e ela desfaz qualquer fusão / movimentação do git e o envia de volta como se o repositório consolidado fosse quase idêntico ao repositório de onde a marca veio. Além disso, existem problemas se você usar a mesma tag em vários repositórios que você 'mesclou / consolidou'. Por exemplo, se você tiver um anúncio A do repositório B, ambos com a tag rel_1.0. Você mescla o repositório A e o repositório B no repositório AB. Como as tags rel_1.0 têm duas confirmações diferentes (uma para A e outra para B), qual tag será visível na AB? A tag do repositório importado A ou do repositório importado B, mas não os dois.

O git-stitch-repo ajuda a resolver esse problema criando tags rel_1.0-A e rel_1.0-B. Você pode não conseguir fazer check-out da tag rel_1.0 e esperar as duas, mas pelo menos pode vê-las e, teoricamente, pode mesclá-las em uma ramificação local comum e criar uma tag rel_1.0 nessa ramificação mesclada (supondo que você mesclar e não alterar o código-fonte). É melhor trabalhar com ramificações, pois você pode mesclar ramificações de cada repositório em ramificações locais. (o dev-a e o dev-b podem ser mesclados em uma ramificação local do dev que pode ser empurrada para a origem).

user3622356
fonte
2

A sequência que você sugeriu

git init
git add *
git commit -a -m "import everything"

funcionará, mas você perderá seu histórico de consolidação.

Patrick_O
fonte
Perder a história não é tão ruim, mas como o repositório é para o meu próprio trabalho (ou seja, é privado), há muitas coisas lá que eu não quero versionar ou que ainda não foram versionadas.
Will Robertson
1

Para mesclar um secondProject em um mainProject:

A) No segundo projeto

git fast-export --all --date-order > /tmp/secondProjectExport

B) No projeto principal:

git checkout -b secondProject
git fast-import --force < /tmp/secondProjectExport

Nesse ramo, faça todas as transformações pesadas que você precisa fazer e as comprometa.

C) Depois, de volta ao mestre e uma fusão clássica entre os dois ramos:

git checkout master
git merge secondProject
user123568943685
fonte
Isso mesclaria todos os arquivos e pastas na raiz dos dois projetos git em um projeto. Duvido que alguém queira que isso aconteça.
Clintm
0

Vou jogar minha solução aqui também. É basicamente um wrapper de script bash bastante simples git filter-branch. Como outras soluções, ele migra apenas ramificações principais e não migra tags. Mas o histórico completo de commit mestre é migrado e é um script bash curto, portanto deve ser relativamente fácil para os usuários revisarem ou ajustarem.

https://github.com/Oakleon/git-join-repos

chrishiestand
fonte
0

Esse script bash soluciona o problema de caractere da guia sed (no MacOS, por exemplo) e o problema de arquivos ausentes.

export SUBREPO="subrepo"; # <= your subrepository name here
export TABULATOR=`printf '\t'`;
FILTER='git ls-files -s | sed "s#${TABULATOR}#&${SUBREPO}/#" |
  GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
  git update-index --index-info &&
  if [ -f "$GIT_INDEX_FILE.new" ]; then mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE; else echo "git filter skipped missing file: $GIT_INXEX_FILE.new"; fi'

git filter-branch --index-filter "$FILTER" HEAD

Esta é uma combinação dos posts de miniquark , marius-butuc e ryan . Um brinde a eles!

bue
fonte