Come importare un repository Git esistente in un altro?


477

Ho un repository Git in una cartella chiamata XXX e ho un repository Git chiamato YYY .

Voglio importare il repository XXX nel repository YYY come una sottodirectory denominata ZZZ e aggiungere la cronologia delle modifiche di XXX a YYY .

Struttura delle cartelle prima di:

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

Struttura delle cartelle dopo:

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

Questo può essere fatto o devo ricorrere all'uso dei sottomoduli?


2
Su Github è ora possibile farlo dall'interfaccia web quando si crea un nuovo repository
bgcode

Risposte:


430

Probabilmente il modo più semplice sarebbe quello di trascinare la roba XXX in un ramo in AAA e poi fonderla nel master:

In AA :

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

In realtà ho appena provato questo con un paio di miei repository e funziona. A differenza della risposta di Jörg non ti consentirà di continuare a utilizzare l'altro repository, ma non credo che tu l'abbia specificato comunque.

Nota: poiché questo è stato originariamente scritto nel 2009, git ha aggiunto l'unione sottostruttura menzionata nella risposta di seguito. Probabilmente userei quel metodo oggi, anche se ovviamente questo metodo funziona ancora.


1
Grazie. Ho usato una versione leggermente modificata della tua tecnica: ho creato un ramo di "stadiazione" su XXX dove ho creato la cartella ZZZ e ho spostato la "roba" in essa. Quindi ho unito XXX in AAA.
Vijay Patel,

1
Questo ha funzionato alla grande per me. Le uniche modifiche che ho apportato sono state: 1) "git branch -d ZZZ" prima del push perché non volevo che questo ramo temporaneo fosse sospeso. 2) "git push" mi stava dando l'errore: "Nessun riferimento in comune e nessuno specificato; non fare nulla. Forse dovresti specificare un ramo come" master "." (L'origine a cui stavo spingendo era un repository vuoto e vuoto.) "Git push --all" ha funzionato come un campione.
CrazyPyro,

1
Volevo finire con solo la cartella ZZZ più la cronologia nel repository YYY: volevo eliminare il repository XXX originale e il ramo ZZZ nel repository YYY. Ho scoperto che l'eliminazione del ramo ZZZ come suggerito da @CrazyPyro ha rimosso la cronologia - per mantenerlo ho unito il ramo ZZZ al master prima di eliminarlo.
Oli Studholme,

4
@SebastianBlask Ho appena fatto un casino con questo con due dei miei repository, e mi sono reso conto che c'è un passo mancante che nessuno sembra aver mai notato, nonostante abbia ottenuto voti su questo per anni. :-) Ho menzionato la fusione in master, ma in realtà non lo ha mostrato. Modificandolo ora ...
ebneter,

2
potresti aggiungere qualcosa del genere quando sposti i file nella tua sottocartella: git mv $(ls|grep -v <your foldername>) <your foldername>/ questo copierà tutti i file e le cartelle nella tua nuova cartella
serup

367

Se si desidera conservare l'esatta cronologia di commit del secondo repository e quindi conservare anche la possibilità di unire facilmente le modifiche a monte in futuro, ecco il metodo desiderato. Il risultato è che la cronologia non modificata del sottotree viene importata nel repository più un commit di unione per spostare il repository unito nella sottodirectory.

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

Puoi tenere traccia delle modifiche a monte in questo modo:

git pull -s subtree XXX_remote master

Git scopre da solo dove sono le radici prima di fare l'unione, quindi non è necessario specificare il prefisso nelle successive fusioni.

Il rovescio della medaglia è che nella cronologia unita i file non sono prefissati (non in una sottodirectory). Di conseguenza, git log ZZZ/averranno visualizzate tutte le modifiche (se presenti) tranne quelle nella cronologia unita. Tu puoi fare:

git log --follow -- a

ma questo non mostrerà le modifiche se non nella storia unita.

In altre parole, se non si modificano ZZZi file nel repository XXX, è necessario specificare--follow un percorso non prefissato. Se le modificate in entrambi i repository, allora avete 2 comandi, nessuno dei quali mostra tutte le modifiche.

Versioni Git precedenti alla 2.9 : Non è necessario passare l' --allow-unrelated-historiesopzione agit merge .

Il metodo nell'altra risposta che usa read-treee salta ilmerge -s ours passaggio non è effettivamente diverso dalla copia dei file con cp e il commit del risultato.

La fonte originale proveniva dall'articolo della guida "Sottotree Merge" di github . E un altro link utile .


9
questo non sembra aver conservato la cronologia ... se faccio uno git logdei file che ho inserito vedo solo il commit della fusione singola e niente della sua vita precedente nell'altro repository? Git 1.8.0
Anentropic

8
aha! se uso il vecchio percorso del file importato, ovvero ometto il subdir in cui è stato importato, allora git log mi fornirà la cronologia del commit, ad esempio git log -- myfileinvece digit log -- rack/myfile
Anentropic

2
@FrancescoFrassinelli, non è desiderabile? Portare la storia in è una caratteristica di questo metodo.
patrickvacek,

4
@FrancescoFrassinelli, se non vuoi la storia, perché non fare una copia normale? Sto cercando di capire cosa ti spingerebbe a questo metodo se non fosse per la storia - questa è l'unica ragione per cui ho usato questo metodo!
patrickvacek,

7
Da Git 2.9, è necessaria l'opzione --allow-unrelated-historiesquando si esegue l'unione.
stuXnet,

113

git-subtreeè uno script progettato esattamente per questo caso d'uso di fusione di più repository in uno conservando la cronologia (e / o la divisione della cronologia dei sottotitoli, sebbene ciò sembri irrilevante per questa domanda). È distribuito come parte dell'albero git dalla versione 1.7.11 .

Per unire un repository <repo>alla revisione <rev>come sottodirectory <prefix>, utilizzare git subtree addcome segue:

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

git-subtree implementa la strategia di unione dei sottotree in un modo più user friendly.

Per il tuo caso, all'interno del repository YYY, eseguiresti:

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

Il rovescio della medaglia è che nella cronologia unita i file non sono prefissati (non in una sottodirectory). Di conseguenza, git log ZZZ/averranno visualizzate tutte le modifiche (se presenti) tranne quelle nella cronologia unita. Tu puoi fare:

git log --follow -- a

ma questo non mostrerà le modifiche se non nella storia unita.

In altre parole, se non si modificano ZZZi file nel repository XXX, è necessario specificare--follow un percorso non prefissato. Se le modificate in entrambi i repository, allora avete 2 comandi, nessuno dei quali mostra tutte le modifiche.

Maggiori informazioni qui .


4
Se hai una directory da unire invece di un repository nudo o remoto,git subtree add -P name-of-desired-prefix ~/location/of/git/repo-without-.git branch-name
Tatsh

2
Esperienza Noob: git (versione 2.9.0.windows.1) risponde "argomento fatale: ambiguo" HEAD ": revisione sconosciuta o percorso non nell'albero di lavoro" quando l'ho provato in un repository locale, non inizializzato di recente, ma ha funzionato bene dopo ho davvero ottenuto il nuovo repository corso, vale a dire dopo l'aggiunta di un normale file e commettendo il modo regolare.
Stein,

Ha funzionato magnificamente per il mio scenario.
Johnny Utahh,

Oh questo è fantastico.
dwjohnston,

Ho usato il suggerimento di @Tatsh e ha funzionato per me
Carmine Tambascia il

49

C'è un noto esempio di questo nel repository Git stesso, che è collettivamente noto nella comunità Git come " la fusione più bella di sempre " (dopo la riga dell'oggetto usata da Linus Torvalds nell'e-mail alla mailing list Git che descrive questo merge). In questo caso, la gitkGUI di Git, che ora fa parte di Git, era in realtà un progetto separato. Linus è riuscito a fondere quel repository nel repository Git in un modo simile

  • appare nel repository Git come se fosse sempre stato sviluppato come parte di Git,
  • tutta la storia è mantenuta intatta e
  • può ancora essere sviluppato in modo indipendente nel suo vecchio repository, con modifiche semplicemente modificate git pull.

L'e-mail contiene i passaggi necessari per la riproduzione, ma non è per i deboli di cuore: in primo luogo, Linus ha scritto Git, quindi probabilmente ne sa un po 'più di te o di me, e in secondo luogo, quasi 5 anni fa e Git è notevolmente migliorato da allora, quindi forse ora è molto più semplice.

In particolare, suppongo che oggigiorno si utilizzerebbe un sottomodulo gitk, in quel caso specifico.


3
BTW. la strategia utilizzata per le successive fusioni (se ce ne sono) si chiama unione sottostruttura e esiste uno git-subtreestrumento di terze parti che può aiutarti in questo: github.com/apenwarr/git-subtree
Jakub Narębski

Grazie, me ne sono dimenticato. La subtreestrategia di unione, specialmente in combinazione con lo git-subtreestrumento, è un'alternativa piacevole, forse persino superiore ai sottomoduli.
Jörg W Mittag,

12

Il modo semplice per farlo è usare git format-patch.

Supponiamo di avere 2 repository git pippo e bar .

foo contiene:

  • foo.txt
  • .idiota

la barra contiene:

  • bar.txt
  • .idiota

e vogliamo finire con foo contenente la cronologia della barra e questi file:

  • foo.txt
  • .idiota
  • foobar / bar.txt

Quindi per farlo:

 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 vogliamo riscrivere tutti i commit dei messaggi dalla barra, possiamo fare, ad esempio su Linux:

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

Ciò aggiungerà "[barra]" all'inizio di ciascun messaggio di commit.


Se il repository originale conteneva rami e fusioni, git amprobabilmente fallirà.
Adam Monsen,

1
Minore gotcha: git am rimuove qualsiasi cosa [ ]dal messaggio di commit. Quindi dovresti usare un marker diverso da[bar]
HRJ

Non ha funzionato per me. Got "errore: foobar / mySubDir / test_host1: non esiste nell'indice. La copia della patch non riuscita si trova in: /home/myuser/src/proj/.git/rebase-apply/patch Dopo aver risolto questo problema , esegui "git am --continue". Dopo aver applicato 11 patch (su 60).
oligofren

1
Questo blog ha una risposta simile a una domanda un po 'diversa (spostare solo i file selezionati).
Jesse Glick,

Vedo uno svantaggio, tutti gli commit vengono aggiunti all'HEAD del repository di destinazione.
CSchulz,

8

Questa funzione clonerà il repository remoto nella directory repo locale, dopo aver salvato tutti i commit, git logverranno mostrati i commit originali e i percorsi corretti:

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 usare:

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

Se si apportano alcune modifiche, è anche possibile spostare file / directory del repository unito in percorsi diversi, ad esempio:

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"

Avvisi I
percorsi vengono sostituiti da sed, quindi assicurarsi che si siano spostati nei percorsi corretti dopo l'unione.
Il --allow-unrelated-historiesparametro esiste solo da git> = 2.9.


2
Per la gente di OS X, installare gnu-sedper far git-add-repofunzionare la funzione. Grazie ancora Andrey!
ptaylor,

7

Sulla base di questo articolo , l'utilizzo di sottostruttura è ciò che ha funzionato per me e solo la cronologia applicabile è stata trasferita. Pubblicare qui nel caso in cui qualcuno abbia bisogno dei passaggi (assicurati di sostituire i segnaposto con i valori applicabili):

nel repository di origine dividere la sottocartella in un nuovo ramo

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

nel repository di destinazione unisci nel ramo del risultato diviso

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

verifica le modifiche e impegna

git status
git commit

Non dimenticare di

Pulisci eliminando il subtree-split-resultramo

git branch -D subtree-split-result

Rimuovi il telecomando che hai aggiunto per recuperare i dati dal repository di origine

git remote rm merge-source-repo


3

Aggiunta di un'altra risposta poiché penso che sia un po 'più semplice. Un pull di repo_dest viene eseguito in repo_to_import e quindi un urlo push --set-upstream: viene eseguito il repo_dest master.

Questo metodo ha funzionato per me importando diversi repository più piccoli in uno più grande.

Come importare: repo1_to_import in 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

Rinominare o spostare file e directory nella posizione desiderata nel repository originale prima di eseguire l'importazione. per esempio

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

Il metodo descritto al seguente link ha ispirato questa risposta. Mi è piaciuto perché sembrava più semplice. MA attenzione! Ci sono draghi! https://help.github.com/articles/importing-an-external-git-repository git push --mirror url:repo_dest invia la cronologia dei repository locali e lo stato al remoto (url: repo_dest). MA cancella la vecchia storia e lo stato del telecomando. Il divertimento ne consegue! : -E


1

Volevo importare solo alcuni file dall'altro repository (XXX) nel mio caso. La sottostruttura era troppo complicata per me e le altre soluzioni non funzionavano. Questo è quello che ho fatto:

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

Questo ti dà un elenco separato da spazi di tutti i commit che influenzano i file che volevo importare (ZZZ) in ordine inverso (potresti dover aggiungere --follow per catturare anche i nomi). Sono quindi entrato nel repository di destinazione (YYY), ho aggiunto l'altro repository (XXX) come remoto, l'ho preso da esso e infine:

git cherry-pick $ALL_COMMITS

che aggiunge tutti i commit al tuo ramo, avrai così tutti i file con la loro cronologia e puoi fare quello che vuoi con loro come se fossero sempre stati in questo repository.


1

Vedi l' esempio di base in questo articolo e considera tale mappatura sui repository:

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

Dopo tutte le attività descritte in questo capitolo (dopo l'unione), rimuovere il ramo B-master:

$ git branch -d B-master

Quindi, spingere le modifiche.

Per me funziona.


0

Mi trovavo in una situazione in cui stavo cercando, -s theirsma ovviamente questa strategia non esiste. La mia storia era che avevo biforcuto un progetto su GitHub, e ora per qualche motivo, il mio locale masternon poteva essere unitoupstream/master me anche se non avevo apportato modifiche locali a questo ramo. (Davvero non so cosa sia successo lì - immagino che a monte avessero fatto delle sporche spinte dietro le quinte, forse?)

Quello che ho finito per fare è stato

# 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

Quindi ora my masterè di nuovo sincronizzato upstream/master(e potresti ripetere quanto sopra per qualsiasi altro ramo che desideri sincronizzare in modo simile).


1
A git reset --hard upstream/mastersulla tua masterfiliale locale farebbe il lavoro. In questo modo non perdi il conflitto delle filiali locali - cose come il monte predefinito.
tomekwi,

0

Posso suggerire un'altra soluzione (alternativa a git-submodules ) per il tuo problema: lo strumento gil (git links)

Permette di descrivere e gestire dipendenze di repository git complessi.

Inoltre fornisce una soluzione al problema di dipendenza dei sottomoduli ricorsivi git .

Considerare di avere le seguenti dipendenze del progetto: esempio grafico delle dipendenze del repository git

Quindi è possibile definire il .gitlinksfile con la descrizione della relazione dei repository:

# 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

Ogni riga descrive git link nel seguente formato:

  1. Nome univoco del repository
  2. Percorso relativo del repository (avviato dal percorso del file .gitlinks)
  3. Git repository che verrà usato nel comando git clone Branch repository per il checkout
  4. La riga vuota o iniziata con # non viene analizzata (trattata come commento).

Finalmente devi aggiornare il tuo repository di root:

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

# The same result with a single command
gil update

Di conseguenza, clonerai tutti i progetti richiesti e li collegherai correttamente.

Se si desidera eseguire il commit di tutte le modifiche in alcuni repository con tutte le modifiche nei repository collegati figlio, è possibile farlo con un singolo comando:

gil commit -a -m "Some big update"

I comandi Pull, push funzionano in modo simile:

gil pull
gil push

Lo strumento Gil (git links) supporta i seguenti comandi:

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

Maggiori informazioni sul problema della dipendenza dai sottomoduli ricorsivi git .


0

Vorrei usare i nomi a(al posto di XXXe ZZZ) e b(al posto diYYY ), poiché ciò rende la descrizione un po 'più semplice da leggere.

Diciamo che desidera unire repository ain b(sto supponendo che stanno trovano una accanto all'altra):

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

Per questo è necessario git-filter-repoinstallare ( filter-branchè sconsigliato ).

Un esempio di fusione di 2 grandi repository, inserendone uno in una sottodirectory: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Maggiori informazioni qui .


-1

Non conosco un modo semplice per farlo. Potresti farlo:

  1. Usa git filter-branch per aggiungere una superdirectory ZZZ sul repository XXX
  2. Inserire il nuovo ramo nel repository YYY
  3. Unisci il ramo spinto con il tronco di YYY.

Posso modificare con i dettagli se sembra attraente.


-2

Penso che puoi farlo usando 'git mv' e 'git pull'.

Sono un buon gob noob - quindi stai attento con il tuo repository principale - ma ho appena provato questo in una directory temporanea e sembra funzionare.

Primo: rinomina la struttura di XXX in modo che corrisponda al modo in cui vuoi che sia quando è entro AAA:

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

Ora XXX è così:

XXX
 |- ZZZ
     |- ZZZ

Ora usa 'git pull' per recuperare le modifiche attraverso:

cd ../YYY
git pull ../XXX

Ora YYY si presenta così:

YYY
 |- ZZZ
     |- ZZZ
 |- (other folders that already were in YYY)
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.