Nel mio caso, avevo un my-plugin
repository e un main-project
repository e volevo fingere che my-plugin
fosse sempre stato sviluppato nella plugins
sottodirectory di main-project
.
Fondamentalmente, ho riscritto la storia del my-plugin
repository in modo che apparisse tutto lo sviluppo nella plugins/my-plugin
sottodirectory. Quindi, ho aggiunto la storia di sviluppo my-plugin
alla main-project
storia e ho unito i due alberi insieme. Dato che non era plugins/my-plugin
già presente una directory nel main-project
repository, questa era una banale fusione senza conflitti. Il repository risultante conteneva tutta la storia di entrambi i progetti originali e aveva due radici.
TL; DR
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
Versione lunga
Innanzitutto, crea una copia del my-plugin
repository, perché riscriveremo la cronologia di questo repository.
Ora, vai alla radice del my-plugin
repository, controlla il tuo ramo principale (probabilmente master
) ed esegui il seguente comando. Ovviamente, dovresti sostituire my-plugin
e plugins
qualunque sia il tuo vero nome.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Ora per una spiegazione. git filter-branch --tree-filter (...) HEAD
esegue il (...)
comando su ogni commit da cui è possibile raggiungere HEAD
. Si noti che questo funziona direttamente sui dati memorizzati per ciascun commit, quindi non dobbiamo preoccuparci delle nozioni di "directory di lavoro", "indice", "stadiazione" e così via.
Se si esegue un filter-branch
comando che ha esito negativo, lascerà dietro di sé alcuni file nella .git
directory e al successivo tentativo filter-branch
si lamenterà di questo, a meno che non si fornisca l' -f
opzione a filter-branch
.
Per quanto riguarda il comando effettivo, non ho avuto molta fortuna bash
nel fare quello che volevo, quindi invece uso zsh -c
per zsh
eseguire un comando. Innanzitutto ho impostato l' extended_glob
opzione, che è ciò che abilita la ^(...)
sintassi nel mv
comando, così come l' glob_dots
opzione, che mi permette di selezionare dotfile (come .gitignore
) con un glob (^(...)
).
Successivamente, utilizzo il mkdir -p
comando per creare sia plugins
eplugins/my-plugin
allo stesso tempo.
Infine, utilizzo la funzione zsh
"negativo globale" ^(.git|plugins)
per abbinare tutti i file nella directory principale del repository ad eccezione di .git
e la my-plugin
cartella appena creata . (esclusa.git
potrebbe non essere necessaria qui, ma provare a spostare una directory in se stessa è un errore.)
Nel mio repository, il commit iniziale non includeva alcun file, quindi il mv
comando ha restituito un errore sul commit iniziale (poiché non era disponibile nulla da spostare). Pertanto, ho aggiunto un || true
modo che git filter-branch
non si interrompesse.
L' --all
opzione dice filter-branch
di riscrivere la cronologia per tutti i rami nel repository e il extra --
è necessario per dire git
di interpretarla come una parte dell'elenco delle opzioni per i rami da riscrivere, invece che come opzione a filter-branch
se stessa.
Ora, vai al tuo main-project
repository e controlla in quale ramo vuoi unirti. Aggiungi la tua copia locale del my-plugin
repository (con la sua cronologia modificata) come telecomando di main-project
con:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Ora avrai due alberi non correlati nella tua cronologia di commit, che puoi visualizzare bene usando:
$ git log --color --graph --decorate --all
Per unirli, utilizzare:
$ git merge my-plugin/master --allow-unrelated-histories
Si noti che in Git precedente alla 2.9.0, l' --allow-unrelated-histories
opzione non esiste. Se si utilizza una di queste versioni, è sufficiente omettere l'opzione: il messaggio di errore che --allow-unrelated-histories
impedisce è stato aggiunto anche in 2.9.0.
Non dovresti avere alcun conflitto di unione. Se lo fai, probabilmente significa che il filter-branch
comando non ha funzionato correttamente o che c'era già una plugins/my-plugin
directory main-project
.
Assicurati di inserire un messaggio esplicativo di commit per tutti i futuri contributori che si chiedono cosa stia succedendo un hacker per creare un repository con due radici.
È possibile visualizzare il nuovo grafico di commit, che dovrebbe avere due commit di root, usando il git log
comando sopra . Si noti che solo il master
ramo verrà unito . Ciò significa che se si ha un lavoro importante su altri my-plugin
rami che si desidera unire main-project
all'albero, è necessario astenersi dall'eliminare il my-plugin
telecomando fino a quando non si sono eseguite queste fusioni. In caso contrario, i commit da quei rami rimarranno comunque nel main-project
repository, ma alcuni saranno irraggiungibili e suscettibili all'eventuale raccolta dei rifiuti. (Inoltre, dovrai fare riferimento a loro da SHA, poiché l'eliminazione di un telecomando rimuove i suoi rami di tracciamento remoto.)
Facoltativamente, dopo aver unito tutto ciò che si desidera mantenere my-plugin
, è possibile rimuovere il my-plugin
telecomando utilizzando:
$ git remote remove my-plugin
Ora è possibile eliminare in sicurezza la copia del my-plugin
repository di cui è stata modificata la cronologia. Nel mio caso, ho anche aggiunto un avviso di deprecazione al my-plugin
repository reale dopo che l'unione è stata completata e inviata.
Testato su Mac OS X El Capitan con git --version 2.9.0
e zsh --version 5.2
. Il tuo chilometraggio può variare.
Riferimenti: