Combinazione di più repository git


209

Diciamo che ho una configurazione che assomiglia a qualcosa

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

Per ragioni storiche, tutti questi hanno i propri repository git. Ma mi piacerebbe combinarli in uno solo per semplificare un po 'le cose. Ad esempio, in questo momento potrei apportare due serie di modifiche e dover fare qualcosa di simile

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

Sarebbe (ora) bello solo esibirsi

cd phd
git commit

Sembra che ci siano un paio di modi per farlo usando sottomoduli o estraendo dai miei sotto-repository, ma è un po 'più complesso di quanto sto cercando. Per lo meno, ne sarei felice

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

ma non sembra una battuta. C'è qualcosa gitche può aiutarmi?


Considera anche questo ottimo approccio: stackoverflow.com/questions/1425892/…
Johan Sjöberg


Lo script join-git-repos.py fa un buon lavoro se hai repository separati, ciascuno con rami principali che vuoi combinare.
Contrassegna il

Risposte:


151

Ecco una soluzione che ho dato qui :

  1. Per prima cosa fai un backup completo della tua directory phd: non voglio essere ritenuto responsabile per i tuoi anni di duro lavoro perduti! ;-)

    $ cp -r phd phd-backup
    
  2. Sposta il contenuto di phd/codein phd/code/codee correggi la cronologia in modo che sembri che sia sempre stata lì (questo utilizza il comando filter-branch di 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. Lo stesso per il contenuto di phd/figurese phd/thesis(basta sostituire codecon figurese thesis).

    Ora la struttura della tua directory dovrebbe assomigliare a questa:

    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
    
  4. Quindi crea un repository git nella directory root, inserisci tutto al suo interno e rimuovi i vecchi repository:

    $ 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
    

    Infine, ora dovresti avere quello che volevi:

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

Un aspetto interessante di questa procedura è che lascerà al loro posto file e directory senza versione .

Spero che sia di aiuto.


Solo una parola di avvertimento però: se la tua codedirectory ha già una codesottodirectory o un file, le cose potrebbero andare molto storte (lo stesso per figurese thesisovviamente). In tal caso, rinomina semplicemente la directory o il file prima di eseguire l'intera procedura:

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

E quando la procedura è terminata, aggiungi questo passaggio finale:

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

Ovviamente, se la codesottodirectory o il file non è dotato di versione, basta usare al mvposto di git mve dimenticare git commits.


13
Grazie per questo frammento - ha fatto esattamente ciò di cui avevo bisogno (una volta che ho considerato Mac OS X sed che non stava elaborando "\ t" (ho dovuto usare ^ V ^ I).
Craig Trader

6
All'inizio non sono riuscito a farlo funzionare e alla fine ho trovato la soluzione al problema su un'altra vecchia bacheca. Nell'ultima riga, ho dovuto mettere virgolette intorno ai nomi dei file in questo modo: mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADe poi ha funzionato alla grande!
Jorin

3
Il funky comando filter-branch proviene dalle pagine man di git filter-branch. Dovresti dire che: a) dovrebbe essere attribuito correttamente b) Non eseguirò un comando del genere solo perché qualcuno, anche con un'alta reputazione, lo ha pubblicato su StackOverflow. Sapendo che è dalle pagine man lo farò.
tymtam

5
ATTENTO! MacOS X non usa l'estensione GNU di sed, quindi non conosce la sequenza \ t. Il risultato è una storia incasinata! La mia soluzione era incollare il codice in un file di script e scrivere un vero carattere <TAB> al suo interno. Dal Terminale, è possibile accedere a una scheda premendo ctrl + v e quindi scrivendo un <TAB>. Non ho provato la soluzione di Craig
Gil Vegliach

5
ATTENZIONE (2)! Notare anche che se alcuni file o directory contengono trattini ('-') il comando sed fallirà. In tal caso puoi sostituirlo con qualcosa come "s ~ \ t ~ & code / ~". Qui, applicando la stessa logica,
fai

75

git-stitch-repoelaborerà l'output di git-fast-export --all --date-ordersui repository git forniti dalla riga di comando e creerà uno stream adatto a git-fast-importquesto creerà un nuovo repository contenente tutti i commit in un nuovo albero dei commit che rispetta la cronologia di tutti i repository di origine.


33
Uh, è uno strumento di terze parti, non fa parte di git ... :-)
Aristotle Pagaltzis

1
Infatti, ora me lo dici :) Oh beh, suppongo di aver dovuto imparare a installare i pacchetti CPAN un giorno ...
Will Robertson

1
Grazie per aver sottolineato quel comando. L'ho appena usato per aiutare a spostare alcuni repository da SVN a Git.
Firma il

1
ATTENZIONE potrebbe non funzionare se hai branch / unioni! Dalla pagina git-stich-repo : "git-stich-repo funziona perfettamente con i repository che hanno una cronologia lineare (senza fusioni). .. I miglioramenti all'algoritmo di stitching aggiunti nella versione 0.06 dovrebbero renderlo adatto a lavorare con repository che hanno rami e fusioni. "
Bryan P,

6
Questo è uno script esterno, la risposta è troppo breve e non molto utile, questo script ha problemi con i commit di unione, non molte persone gestiscono Perl o CPAN e questo non è ben spiegato nella risposta. Quindi ... -1, scusa.
Haralan Dobrev

20

Forse, semplicemente (in modo simile alla risposta precedente, ma utilizzando comandi più semplici) facendo in ciascuno dei vecchi repository separati un commit che sposta il contenuto in una sottodirectory adeguatamente denominata, ad esempio:

$ 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 quindi unendo i tre repository separati in uno nuovo, facendo smth come:

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

Quindi salverai le tue storie, ma andrai avanti con un singolo repo.


Questo va bene, ma se stai unendo un repository in un altro (cioè phd era un repository già esistente non vuoto), se phd aveva cartelle con nomi uguali alle sottocartelle nella directory del codice, incontrerai problemi come 'git pull .. / phd / code 'estrae tutti i commit con i percorsi originali e solo alla fine applica il commit mv.
tymtam

1
@Tymek: ma funzionerà comunque in quella situazione, senza problemi. La cosa che non sarà carina è che i percorsi nella storia non saranno "corretti" (corrispondono ai nuovi percorsi).
imz - Ivan Zakharyaschev

19

Potresti provare la strategia di unione dei sottoalberi . Ti consentirà di unire il repo B nel repo A. Il vantaggio git-filter-branchè che non richiede di riscrivere la cronologia del repo A (rompendo le somme SHA1).


Il collegamento non funziona e questo non preserverà la storia, vero?
tymtam

3
@Tymek (Spiacenti, alcune parti di kernel.org sono ancora inattive dopo la violazione della sicurezza). Rompe gli SHA1 del repo in arrivo B. Ma A rimane intatto.
Leif Gruenwoldt


1
@LeifGruenwoldt Il primo collegamento ora funziona. E il collegamento mirror è andato, dovresti rimuoverlo suppongo.
Vadim Kotov

9

La soluzione git-filter-branch funziona bene, ma nota che se il tuo repository git proviene da un'importazione SVN potrebbe non riuscire con un messaggio come:

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

In questo caso è necessario escludere la revisione iniziale dal ramo del filtro - cioè cambiare il HEADalla fine in [SHA of 2nd revision]..HEAD- vedere:

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


2
Grazie! Mi sono grattato la testa perché non funzionava! Il repo proveniva effettivamente da SVN.
Arthur Maltson

1
Stesso errore quando lo faccio. Ho le mie speranze. Inoltre, il collegamento è ora interrotto.
Ryan

Potresti spiegare cosa intendi per "cambiare la testina in a ...", il mio repo proviene da un'importazione SVN e sto affrontando esattamente questo problema, apprezzerei molto l'aiuto!

5

La soluzione @MiniQuark mi ha aiutato molto, ma sfortunatamente non tiene conto dei tag che si trovano nei repository dei sorgenti (almeno nel mio caso). Di seguito è riportato il mio miglioramento alla risposta @MiniQuark.

  1. Prima crea la directory che conterrà il repository composto e i repository uniti, crea la directory per ciascuno di essi unito.

    $ mkdir nuovo_phd
    $ mkdir nuovo_phd / codice
    $ mkdir nuovo_phd / figure
    $ mkdir nuovo_phd / tesi

  2. Esegui un pull di ogni repository e recupera tutti i tag. (Presentazione delle istruzioni solo per la codesottodirectory)

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

  3. (Questo è un miglioramento rispetto al punto 2 nella risposta MiniQuark) Sposta il contenuto di new_phd/codea new_phd/code/codee aggiungi il code_prefisso prima di ogni tag

    $ 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 '--tag-name-filter' sed" s -. * - codice _ & - "'HEAD

  4. Dopo averlo fatto, ci saranno il doppio dei tag rispetto a prima di eseguire il branch-filter. I vecchi tag rimangono nel repository e code_vengono aggiunti nuovi tag con prefisso.

    $ git tag
    mytag1
    code_mytag1

    Rimuovi manualmente i vecchi tag:

    $ ls .git / refs / tags / * | grep -v "/ code_" | xargs rm

    Ripetere il punto 2,3,4 per altre sottodirectory

  5. Ora abbiamo la struttura delle directory come in @MiniQuark anwser punto 3.

  6. Fai come al punto 4 di MiniQuark anwser, ma dopo aver fatto un pull e prima di rimuovere .gitdir, recupera i tag:

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

    Continua..

Questa è solo un'altra soluzione. Spero che aiuti qualcuno, mi ha aiutato :)




3

In realtà, git-stitch-repo ora supporta rami e tag, inclusi i tag annotati (ho scoperto che c'era un bug che ho segnalato ed è stato risolto). Quello che ho trovato utile è con i tag. Poiché i tag sono allegati ai commit e alcune delle soluzioni (come l'approccio di Eric Lee) non riescono a gestire i tag. Provi a creare un ramo da un tag importato e questo annullerà qualsiasi unione / spostamento di git e ti rimanderà indietro come se il repository consolidato fosse quasi identico al repository da cui proveniva il tag. Inoltre, ci sono problemi se utilizzi lo stesso tag su più repository che hai "unito / consolidato". Ad esempio, se hai un annuncio B del repository, entrambi con tag rel_1.0. Unisci il repo A e il repo B nel repo AB. Poiché i tag rel_1.0 sono su due commit diversi (uno per A e uno per B), quale tag sarà visibile in AB? O il tag dal repository importato A o dal repository importato B, ma non entrambi.

git-stitch-repo aiuta ad affrontare questo problema creando tag rel_1.0-A e rel_1.0-B. Potresti non essere in grado di eseguire il checkout del tag rel_1.0 e aspettarti entrambi, ma almeno puoi vederli entrambi e, in teoria, puoi unirli in un ramo locale comune quindi creare un tag rel_1.0 su quel ramo unito (supponendo che tu unire e non modificare il codice sorgente). È meglio lavorare con i rami, poiché puoi unire rami simili da ogni repository in rami locali. (dev-a e dev-b possono essere fusi in un ramo dev locale che può quindi essere inviato all'origine).


2

La sequenza che hai suggerito

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

funzionerà, ma perderai la cronologia dei commit.


Perdere la cronologia non è poi così male, ma dal momento che il repository è per il mio lavoro (cioè, è privato) ci sono un sacco di cose lì che non voglio che sia aggiornato o che non sia ancora aggiornato.
Will Robertson

1

Per unire un secondProject all'interno di un mainProject:

A) Nel secondProject

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

B) Nel progetto principale:

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

In questo ramo fai tutte le trasformazioni pesanti che devi fare e impegnale.

C) Poi torna al master e una classica fusione tra i due rami:

git checkout master
git merge secondProject

Ciò unirebbe tutti i file e le cartelle alla radice di entrambi i progetti git in un unico progetto. Dubito che qualcuno vorrebbe che ciò accadesse.
Clintm

0

Metterò anche qui la mia soluzione. È fondamentalmente un wrapper di script bash abbastanza semplice git filter-branch. Come altre soluzioni, migra solo i rami principali e non migra i tag. Ma la cronologia completa dei commit master viene migrata ed è uno script bash breve, quindi dovrebbe essere relativamente facile per gli utenti rivedere o modificare.

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


0

Questo script bash risolve il problema dei caratteri di tabulazione sed (ad esempio su MacOS) e il problema dei file mancanti.

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

Questa è una combinazione di post di miniquark , marius-butuc e ryan . Salute a loro!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.