2012-10-23 17 views
160

Ho bisogno di unire due repository Git in un repository nuovo di zecca, terzo. Ho trovato molte descrizioni su come eseguire questa operazione utilizzando un'unione di sottostrati (ad esempio Jakub Narębski's answer su How do you merge two Git repositories?) e seguendo queste istruzioni funziona per lo più, tranne che quando commetto l'unione di sottoalbero tutti i file dei vecchi archivi vengono registrati come nuovi aggiunti File. Riesco a vedere la cronologia dei commit dai vecchi repository quando faccio git log, ma se faccio git log <file> mostra solo un commit per quel file - l'unione di sottoalbero si unisce. A giudicare dai commenti sulla risposta di cui sopra, non sono il solo a vedere questo problema, ma non ho trovato soluzioni pubblicate per questo.Unisci due repository Git senza spezzare la cronologia dei file

C'è un modo per unire i repository e lasciare intatta la cronologia dei singoli file?

+0

Non sto usando Git, ma in Mercurial dovrei prima fare una conversione, se necessario, per correggere i percorsi dei file dei repository da unire, e quindi forzare-tirare un repository nella destinazione per ottenere i changeset, e quindi fai unire i diversi rami. Questo è testato e funziona;) Forse questo aiuta a trovare una soluzione anche per Git ... rispetto all'approccio di sottostruttura credo che il passaggio di conversione sia diverso in cui la cronologia viene riscritta invece di mappare semplicemente un percorso (se capisco correttamente). Ciò garantisce quindi un'unione uniforme senza alcuna gestione speciale dei percorsi dei file. – Lucero

+0

Ho trovato questa domanda anche utile http://stackoverflow.com/questions/1683531/how-to-import-existing-git-repository-into-another – nacross

+0

Ho creato una domanda di follow-up. Potrebbe essere interessante: unire due repository Git e mantenere la cronologia master: http://stackoverflow.com/questions/42161910/merge-two-git-repositories-and-keep-the-master-history –

risposta

205

Si scopre che la risposta è molto più semplice se si sta semplicemente cercando di incollare due repository insieme e far sembrare che fosse sempre così, piuttosto che gestire una dipendenza esterna. Devi semplicemente aggiungere telecomandi ai tuoi vecchi repository, unirli al tuo nuovo master, spostare i file e le cartelle in una sottodirectory, commettere il movimento e ripetere per tutti i repository aggiuntivi. Sottomoduli, fusioni di sottostrutture e abbellimenti fantasiosi hanno lo scopo di risolvere un problema leggermente diverso e non sono adatti a ciò che stavo cercando di fare.

Ecco uno script PowerShell esempio per incollare due repository insieme:

# Assume the current directory is where we want the new repository to be created 
# Create the new repository 
git init 

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit 
dir > deleteme.txt 
git add . 
git commit -m "Initial dummy commit" 

# Add a remote for and fetch the old repo 
git remote add -f old_a <OldA repo URL> 

# Merge the files from old_a/master into new/master 
git merge old_a/master --allow-unrelated-histories 

# Clean up our dummy file because we don't need it any more 
git rm .\deleteme.txt 
git commit -m "Clean up initial file" 

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later 
mkdir old_a 
dir -exclude old_a | %{git mv $_.Name old_a} 

# Commit the move 
git commit -m "Move old_a files into subdir" 

# Do the same thing for old_b 
git remote add -f old_b <OldB repo URL> 
git merge old_b/master --allow-unrelated-histories 
mkdir old_b 
dir –exclude old_a,old_b | %{git mv $_.Name old_b} 
git commit -m "Move old_b files into subdir" 

Ovviamente si potrebbe invece fondersi old_b in old_a (che diventa il nuovo repo combinato) se si preferisce farlo - modificare la script per soddisfare.

Se si desidera portare over in corso caratteristica rami così, utilizzare questo:

# Bring over a feature branch from one of the old repos 
git checkout -b feature-in-progress 
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress 

che è l'unica parte non evidente del processo - che non è una fusione sottostruttura, ma piuttosto un argomento di la normale unione ricorsiva che dice a Git che abbiamo ribattezzato il bersaglio e che aiuta Git a riorganizzare tutto correttamente.

Ho scritto una spiegazione leggermente più dettagliata here.

+10

questa soluzione che usa 'git mv' non funziona così bene. quando in seguito usi un 'git log' su uno dei file spostati, ottieni solo il commit dalla mossa. tutta la storia precedente è andata perduta. questo perché 'git mv' è veramente' git rm; git add', ma [in un unico passaggio] (http://stackoverflow.com/a/1094392/959352). – mholm815

+11

È uguale a qualsiasi altra operazione di spostamento/rinomina in Git: dalla riga di comando puoi ottenere tutta la cronologia facendo 'git log --follow', o tutti gli strumenti della GUI lo fanno automaticamente. Con una sottostruttura si fondono ** non è possibile ** ottenere la cronologia per i singoli file, per quanto ne so, quindi questo metodo è migliore. –

+2

@EricLee Quando il repository old_b viene unito, ottengo molti conflitti di unione. È previsto? Ottengo CONFLICT (rinomina/cancella) – Jon

8

prega di dare un'occhiata a utilizzare

git rebase --root --preserve-merges --onto 

per collegare due storie nella fase iniziale della loro vita.

Se si dispone di percorsi che si sovrappongono, fissarli con

git filter-branch --index-filter 

quando si utilizza di registro, assicurarsi di "trovare copie di più" con

git log -CC 

in questo modo troverete i movimenti di file nel percorso.

105

Ecco un modo che non riscrive alcuna cronologia, quindi tutti gli ID di commit rimarranno validi. Il risultato finale è che i file del secondo repository finiranno in una sottodirectory.

  1. aggiungere il secondo repo come un telecomando:

    cd firstgitrepo/ 
    git remote add secondrepo [email protected]:andsoon 
    
  2. Assicurarsi di aver scaricato tutti i commit del secondrepo:

    git fetch secondrepo 
    
  3. Creare un ramo locale dal seconda succursale:

    git branch branchfromsecondrepo secondrepo/master 
    
  4. Spostare tutti i suoi file in una sottodirectory:

    git checkout branchfromsecondrepo 
    mkdir subdir/ 
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/ 
    git commit -m "Moved files to subdir/" 
    
  5. unire il secondo ramo in ramo master del primo pronti contro termine:

    git checkout master 
    git merge --allow-unrelated-histories branchfromsecondrepo 
    

Il repository avrà più di una radice commettere, ma questo non dovrebbe rappresentare un problema.

+1

Il passaggio 2 non funziona per me: fatale: non è un nome oggetto valido: 'secondrepo/master'. – Keith

+0

@Keith: assicurati di aver aggiunto il secondo repository come un remoto chiamato "secondrepo", e che quel repository ha un ramo chiamato "master" (puoi vedere i rami su un repository remoto con il comando 'git remote show secondrepo') – Flimm

+0

Ho dovuto eseguire un recupero per portarlo giù. Tra 1 e 2 ho fatto git fetch secondrepo – monkjack

5

ho girato la solution da @Flimm questo in un git alias come questo (aggiunto alla mia ~/.gitconfig):

[alias] 
mergeRepo = "!mergeRepo() { \ 
    [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \ 
    git remote add newRepo $1; \ 
    git fetch newRepo; \ 
    git branch \"$2\" newRepo/master; \ 
    git checkout \"$2\"; \ 
    mkdir -vp \"${GIT_PREFIX}$3\"; \ 
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \ 
    git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \ 
    git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \ 
    git branch -D \"$2\"; git remote remove newRepo; \ 
}; \ 
mergeRepo" 
+6

Solo curioso: lo fai davvero spesso per aver bisogno di un alias? –

+1

No, ma non ricordo mai come farlo, quindi un alias è solo un modo per me di ricordarlo. –

+0

Sì .. ma prova a cambiare computer e dimentica di spostare i tuoi alias;) – quetzalcoatl

2

Questa funzione clonare repo remoto in locale dir repo:

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" 
} 

Come utilizzare:

cd current/package 
git-add-repo https://github.com/example/example dir/to/save 

Utile!

+0

Sto usando zsh piuttosto che bash e v2.13.0 di git. Non importa quello che ho provato, non sono stato in grado di far funzionare 'git filter-branch --index-filter'. In genere viene visualizzato un messaggio di errore indicante che il nuovo file di indice non esiste. Suona qualche campana? –

+0

@PatrickBeard Non so zsh, puoi creare il file separato 'git-add-repo.sh' con la funzione sopra, alla fine del file metti questa riga' git-add-repo "$ @" '. Dopodiché puoi usarlo da zsh come 'cd current/git/package' e' bash path/to/git-add-repo.sh https://github.com/example/example dir/to/save' –

+0

The problema è stato discusso qui: https://stackoverflow.com/questions/7798142/error-combining-git-repositories-into-subdirs 'mv" $ GIT_INDEX_FILE.new "" $ GIT_INDEX_FILE "' non riesce a volte, quindi è necessario aggiungere un 'se test'. –

Problemi correlati