En mi caso, tenía un my-plugin
repositorio y un main-project
repositorio, y quería fingir que my-plugin
siempre se había desarrollado en el plugins
subdirectorio de main-project
.
Básicamente, reescribí la historia del my-plugin
repositorio para que pareciera que todo el desarrollo tuvo lugar en el plugins/my-plugin
subdirectorio. Luego, agregué el historial de desarrollo de my-plugin
la main-project
historia y fusioné los dos árboles. Como no había ningún plugins/my-plugin
directorio ya presente en el main-project
repositorio, 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-plugin
repositorio, porque vamos a reescribir la historia de este repositorio.
Ahora, navegue hasta la raíz del my-plugin
repositorio, revise su rama principal (probablemente master
) y ejecute el siguiente comando. Por supuesto, debe sustituir my-plugin
y plugins
cualesquiera 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 (...) HEAD
ejecuta 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-branch
comando que falla, dejará algunos archivos en el .git
directorio y la próxima vez que lo intente filter-branch
se quejará de esto, a menos que proporcione la -f
opción filter-branch
.
En cuanto al comando real, no tuve mucha suerte bash
para hacer lo que quería, así que en lugar de eso utilizo zsh -c
para zsh
ejecutar un comando. Primero configuro la extended_glob
opción, que es lo que habilita la ^(...)
sintaxis en el mv
comando, así como la glob_dots
opción, que me permite seleccionar archivos de puntos (como .gitignore
) con un globo ( ^(...)
).
A continuación, uso el mkdir -p
comando para crear ambos plugins
y plugins/my-plugin
al 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 .git
la my-plugin
carpeta recién creada . (Es .git
posible 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 mv
comando devolvió un error en la confirmación inicial (ya que no había nada disponible para mover). Por lo tanto, agregué un || true
para que git filter-branch
no abortara.
La --all
opción le dice filter-branch
que reescriba el historial de todas las ramas en el repositorio, y el extra --
es necesario para git
que lo interprete como parte de la lista de opciones para que las ramas se reescriban, en lugar de ser una opción para filter-branch
sí mismo.
Ahora, navegue a su main-project
repositorio y revise en qué rama desea fusionarse. Agregue su copia local del my-plugin
repositorio (con su historial modificado) como un control remoto main-project
con:
$ 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-histories
opción no existe. Si está utilizando una de estas versiones, simplemente omita la opción: el mensaje de error que --allow-unrelated-histories
impide 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-branch
comando no funcionó correctamente o que ya había un plugins/my-plugin
directorio 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 log
comando anterior . Tenga en cuenta que solo master
se fusionará la rama . Esto significa que si tiene un trabajo importante en otras my-plugin
ramas que desea fusionar en el main-project
árbol, debe abstenerse de eliminar el my-plugin
control remoto hasta que haya realizado estas fusiones. Si no lo hace, entonces los commits de esas ramas aún estarán en el main-project
repositorio, 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-plugin
control remoto utilizando:
$ git remote remove my-plugin
Ahora puede eliminar de forma segura la copia del my-plugin
repositorio cuyo historial ha cambiado. En mi caso, también agregué un aviso de desaprobación al my-plugin
repositorio 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.0
y zsh --version 5.2
. Su experiencia puede ser diferente.
Referencias