En mi caso, tenía un my-pluginrepositorio y un main-projectrepositorio, y quería fingir que my-pluginsiempre se había desarrollado en el pluginssubdirectorio de main-project.
Básicamente, reescribí la historia del my-pluginrepositorio para que pareciera que todo el desarrollo tuvo lugar en el plugins/my-pluginsubdirectorio. Luego, agregué el historial de desarrollo de my-pluginla main-projecthistoria y fusioné los dos árboles. Como no había ningún plugins/my-plugindirectorio ya presente en el main-projectrepositorio, esta fue una fusión trivial sin conflictos. El repositorio resultante contenía toda la historia de ambos proyectos originales y tenía dos raíces.
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
Versión larga
Primero, cree una copia del my-pluginrepositorio, porque vamos a reescribir la historia de este repositorio.
Ahora, navegue hasta la raíz del my-pluginrepositorio, revise su rama principal (probablemente master) y ejecute el siguiente comando. Por supuesto, debe sustituir my-pluginy pluginscualesquiera que sean sus nombres reales.
$ 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
Ahora para una explicación. git filter-branch --tree-filter (...) HEADejecuta el (...)comando en cada confirmación a la que se puede acceder HEAD. Tenga en cuenta que esto opera directamente en los datos almacenados para cada confirmación, por lo que no tenemos que preocuparnos por las nociones de "directorio de trabajo", "índice", "puesta en escena", etc.
Si ejecuta un filter-branchcomando que falla, dejará algunos archivos en el .gitdirectorio y la próxima vez que lo intente filter-branchse quejará de esto, a menos que proporcione la -fopción filter-branch.
En cuanto al comando real, no tuve mucha suerte bashpara hacer lo que quería, así que en lugar de eso utilizo zsh -cpara zshejecutar un comando. Primero configuro la extended_globopción, que es lo que habilita la ^(...)sintaxis en el mvcomando, así como la glob_dotsopción, que me permite seleccionar archivos de puntos (como .gitignore) con un globo ( ^(...)).
A continuación, uso el mkdir -pcomando para crear ambos pluginsy plugins/my-pluginal mismo tiempo.
Finalmente, utilizo la función zsh"glob negativo" ^(.git|plugins)para hacer coincidir todos los archivos en el directorio raíz del repositorio, excepto para .gitla my-plugincarpeta recién creada . (Es .gitposible que no sea necesario excluir aquí, pero intentar mover un directorio en sí mismo es un error).
En mi repositorio, la confirmación inicial no incluía ningún archivo, por lo que el mvcomando devolvió un error en la confirmación inicial (ya que no había nada disponible para mover). Por lo tanto, agregué un || truepara que git filter-branchno abortara.
La --allopción le dice filter-branchque reescriba el historial de todas las ramas en el repositorio, y el extra --es necesario para gitque lo interprete como parte de la lista de opciones para que las ramas se reescriban, en lugar de ser una opción para filter-branchsí mismo.
Ahora, navegue a su main-projectrepositorio y revise en qué rama desea fusionarse. Agregue su copia local del my-pluginrepositorio (con su historial modificado) como un control remoto main-projectcon:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Ahora tendrá dos árboles no relacionados en su historial de confirmación, que puede visualizar muy bien usando:
$ git log --color --graph --decorate --all
Para fusionarlos, use:
$ git merge my-plugin/master --allow-unrelated-histories
Tenga en cuenta que en Git anterior a 2.9.0, la --allow-unrelated-historiesopción no existe. Si está utilizando una de estas versiones, simplemente omita la opción: el mensaje de error que --allow-unrelated-historiesimpide también se agregó en 2.9.0.
No debe tener ningún conflicto de fusión. Si lo hace, probablemente significa que el filter-branchcomando no funcionó correctamente o que ya había un plugins/my-plugindirectorio main-project.
Asegúrate de ingresar un mensaje de confirmación explicativo para cualquier contribuyente futuro que se pregunte qué hacker está sucediendo para hacer un repositorio con dos raíces.
Puede visualizar el nuevo gráfico de confirmación, que debe tener dos confirmaciones de raíz, utilizando el git logcomando anterior . Tenga en cuenta que solo masterse fusionará la rama . Esto significa que si tiene un trabajo importante en otras my-pluginramas que desea fusionar en el main-projectárbol, debe abstenerse de eliminar el my-plugincontrol remoto hasta que haya realizado estas fusiones. Si no lo hace, entonces los commits de esas ramas aún estarán en el main-projectrepositorio, pero algunos serán inalcanzables y susceptibles a una eventual recolección de basura. (Además, tendrá que referirse a ellos por SHA, porque al eliminar un control remoto se eliminan sus ramas de seguimiento remoto).
Opcionalmente, después de haber fusionado todo lo que desea evitar my-plugin, puede eliminar el my-plugincontrol remoto utilizando:
$ git remote remove my-plugin
Ahora puede eliminar de forma segura la copia del my-pluginrepositorio cuyo historial ha cambiado. En mi caso, también agregué un aviso de desaprobación al my-pluginrepositorio real después de que la fusión se completó y se empujó.
Probado en Mac OS X El Capitan con git --version 2.9.0y zsh --version 5.2. Su experiencia puede ser diferente.
Referencias