Nel mio caso, avevo un my-pluginrepository e un main-projectrepository e volevo fingere che my-pluginfosse sempre stato sviluppato nella pluginssottodirectory di main-project.
Fondamentalmente, ho riscritto la storia del my-pluginrepository in modo che apparisse tutto lo sviluppo nella plugins/my-pluginsottodirectory. Quindi, ho aggiunto la storia di sviluppo my-pluginalla main-projectstoria e ho unito i due alberi insieme. Dato che non era plugins/my-plugingià presente una directory nel main-projectrepository, 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-pluginrepository, perché riscriveremo la cronologia di questo repository.
Ora, vai alla radice del my-pluginrepository, controlla il tuo ramo principale (probabilmente master) ed esegui il seguente comando. Ovviamente, dovresti sostituire my-plugine pluginsqualunque 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 (...) HEADesegue 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-branchcomando che ha esito negativo, lascerà dietro di sé alcuni file nella .gitdirectory e al successivo tentativo filter-branchsi lamenterà di questo, a meno che non si fornisca l' -fopzione a filter-branch.
Per quanto riguarda il comando effettivo, non ho avuto molta fortuna bashnel fare quello che volevo, quindi invece uso zsh -cper zsheseguire un comando. Innanzitutto ho impostato l' extended_globopzione, che è ciò che abilita la ^(...)sintassi nel mvcomando, così come l' glob_dotsopzione, che mi permette di selezionare dotfile (come .gitignore) con un glob (^(...) ).
Successivamente, utilizzo il mkdir -pcomando per creare sia pluginseplugins/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 .gite la my-plugincartella 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 mvcomando ha restituito un errore sul commit iniziale (poiché non era disponibile nulla da spostare). Pertanto, ho aggiunto un || truemodo che git filter-branchnon si interrompesse.
L' --allopzione dice filter-branchdi riscrivere la cronologia per tutti i rami nel repository e il extra --è necessario per dire gitdi interpretarla come una parte dell'elenco delle opzioni per i rami da riscrivere, invece che come opzione a filter-branchse stessa.
Ora, vai al tuo main-projectrepository e controlla in quale ramo vuoi unirti. Aggiungi la tua copia locale del my-pluginrepository (con la sua cronologia modificata) come telecomando di main-projectcon:
$ 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-historiesopzione non esiste. Se si utilizza una di queste versioni, è sufficiente omettere l'opzione: il messaggio di errore che --allow-unrelated-historiesimpedisce è stato aggiunto anche in 2.9.0.
Non dovresti avere alcun conflitto di unione. Se lo fai, probabilmente significa che il filter-branchcomando non ha funzionato correttamente o che c'era già una plugins/my-plugindirectory 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 logcomando sopra . Si noti che solo il masterramo verrà unito . Ciò significa che se si ha un lavoro importante su altri my-pluginrami che si desidera unire main-projectall'albero, è necessario astenersi dall'eliminare il my-plugintelecomando fino a quando non si sono eseguite queste fusioni. In caso contrario, i commit da quei rami rimarranno comunque nel main-projectrepository, 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-plugintelecomando utilizzando:
$ git remote remove my-plugin
Ora è possibile eliminare in sicurezza la copia del my-pluginrepository di cui è stata modificata la cronologia. Nel mio caso, ho anche aggiunto un avviso di deprecazione al my-pluginrepository reale dopo che l'unione è stata completata e inviata.
Testato su Mac OS X El Capitan con git --version 2.9.0e zsh --version 5.2. Il tuo chilometraggio può variare.
Riferimenti: