Pregunta ¿Cómo fusionas dos repositorios Git?


Considere la siguiente situación:

He desarrollado un pequeño proyecto experimental A en su propio repositorio Git. Ahora ha madurado y me gustaría que A forme parte del proyecto B más grande, que tiene su propio gran repositorio. Ahora me gustaría agregar A como un subdirectorio de B.

¿Cómo combino A en B, sin perder el historial de ningún lado?


1220
2017-09-15 08:31


origen


Respuestas:


Una sola rama de otro repositorio puede colocarse fácilmente en un subdirectorio que conserva su historial. Por ejemplo:

git subtree add --prefix=rails git://github.com/rails/rails.git master

Esto aparecerá como una confirmación única donde todos los archivos de la rama maestra de Rails se agregan al directorio "rails". Sin embargo, el título del compromiso contiene una referencia al antiguo árbol de historial:

Agregar 'rails /' desde commit <rev>

Dónde <rev> es un hash de confirmación SHA-1. Aún puedes ver el historial, culpar a algunos cambios.

git log <rev>
git blame <rev> -- README.md

Tenga en cuenta que no puede ver el prefijo del directorio desde aquí, ya que esta es una antigua rama real que quedó intacta. Deberías tratar esto como un commit de movimiento de archivo habitual: necesitarás un salto extra al alcanzarlo.

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

Hay soluciones más complejas como hacerlo manualmente o reescribir el historial como se describe en otras respuestas.

El comando git-subfree es parte de git-contrib oficial, algunos administradores de paquetes lo instalan por defecto (OS X Homebrew). Pero es posible que deba instalarlo usted mismo además de git.


326
2018-02-20 23:44



Si quieres unirte project-a dentro project-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

Tomado de: git fusionar diferentes repositorios?

Este método funcionó bastante bien para mí, es más corto y, en mi opinión, mucho más limpio.

Nota: los --allow-unrelated-histories parámetro solo existe desde git> = 2.9. Ver Git - git merge Documentación / --allow-unrelated-histories 


1279
2018-05-11 09:37



Aquí hay dos soluciones posibles:

Submódulos

Copie el repositorio A en un directorio separado en el proyecto B más grande, o (quizás mejor) clone el repositorio A en el subdirectorio en el proyecto B. Luego use el repositorio A en un directorio separado en el proyecto B. submódulo git para hacer que este repositorio sea submódulo de un repositorio B.

Esta es una buena solución para repositorios débilmente acoplados, donde el desarrollo en el repositorio A continúa, y la mayor parte del desarrollo es el desarrollo independiente por separado en A. Ver también SubmóduloSoporte y GitSubmoduleTutorial páginas en Wiki Git.

Fusión de Subtree

Puede fusionar el repositorio A en un subdirectorio de un proyecto B utilizando el fusión de subárbol estrategia. Esto se describe en Subtree Merging y usted por Markus Prinz.

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

(opción --allow-unrelated-histories es necesario para git> = 2.9.0)

O puedes usar subárbol de git herramienta (repositorio en github) por apenwarr (Avery Pennarun), anunciado, por ejemplo, en su publicación de blog Una nueva alternativa a los submódulos de git: subárbol de git.


Creo que en tu caso (A es ser parte del proyecto B más grande), la solución correcta sería usar fusión de subárbol


581
2017-09-15 08:38



El enfoque de submódulo es bueno si desea mantener el proyecto por separado. Sin embargo, si realmente desea fusionar ambos proyectos en el mismo repositorio, entonces tiene un poco más de trabajo por hacer.

Lo primero sería usar git filter-branch para reescribir los nombres de todo en el segundo repositorio para que esté en el subdirectorio donde le gustaría que terminen. Entonces, en lugar de foo.c, bar.html, tendrías projb/foo.c y projb/bar.html.

Entonces, deberías poder hacer algo como lo siguiente:

git remote add projb [wherever]
git pull projb

los git pull Haré un git fetch seguido por un git merge. No debería haber conflictos, si el repositorio al que está recurriendo aún no tiene un projb/ directorio.

La búsqueda adicional indica que se hizo algo similar para fusionarse gitk dentro git. Junio ​​C Hamano escribe al respecto aquí: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


187
2018-02-01 08:10



git-subtree es lindo, pero probablemente no es el que quieres.

Por ejemplo, si projectA es el directorio creado en B, después git subtree,

git log projectA

liza solo uno commit: la fusión. Los compromisos del proyecto fusionado son para diferentes rutas, por lo que no se muestran.

La respuesta de Greg Hewgill es la más aproximada, aunque en realidad no dice cómo reescribir los caminos.


La solución es sorprendentemente simple.

(1) En A,

PREFIX=projectA #adjust this

git filter-branch --index-filter '
    git ls-files -s |
    sed "s,\t,&'"$PREFIX"'/," |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD

Nota: Esto reescribe el historial, por lo que si tiene la intención de continuar usando este repositorio A, es posible que desee clonar (copiar) primero una copia descartable.

(2) Luego en B, corre

git pull path/to/A

Voila! Usted tiene un projectA directorio en B. si ejecuta git log projectA, verás todas las confirmaciones de A.


En mi caso, quería dos subdirectorios, projectA y projectB. En ese caso, hice el paso (1) a B también.


62
2018-06-11 14:31



Si ambos repositorios tienen el mismo tipo de archivos (como dos repositorios Rails para diferentes proyectos), puede obtener datos del repositorio secundario en su repositorio actual:

git fetch git://repository.url/repo.git master:branch_name

y luego fusionarlo al repositorio actual:

git merge --allow-unrelated-histories branch_name

Si su versión de Git es menor que 2.9, elimine --allow-unrelated-histories.

Después de esto, pueden ocurrir conflictos. Puedes resolverlos, por ejemplo, con git mergetool. kdiff3 se puede usar únicamente con el teclado, por lo que 5 archivos de conflicto tardan unos pocos minutos en leer el código.

Recuerde terminar la fusión:

git commit

39
2018-02-27 03:09



Seguí perdiendo historial al usar merge, así que terminé usando rebase ya que en mi caso los dos repositorios son lo suficientemente diferentes como para no terminar fusionándose en cada commit:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> resolver conflictos, luego continuar, tantas veces como sea necesario ...

git rebase --continue

Hacer esto lleva a que un proyecto tenga todas las confirmaciones de projA seguidas por las confirmaciones de projB


19
2017-08-18 21:06



En mi caso, tuve una my-plugin repositorio y una main-project repositorio, y yo quería fingir que my-plugin siempre se ha desarrollado en el plugins subdirectorio de main-project.

Básicamente, reescribí la historia de 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 en el main-project historia, y fusionó los dos árboles juntos. Como no había plugins/my-plugin directorio ya presente en el main-project repositorio, esta fue una fusión trivial sin conflictos. El repositorio resultante contenía todo el historial 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|my-plugin) 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, crea una copia del my-plugin repositorio, porque vamos a reescribir el historial de este repositorio.

Ahora, navegue a la raíz del my-plugin repositorio, consulte su rama principal (probablemente master), y ejecuta el siguiente comando. Por supuesto, debes sustituir my-plugin y plugins cualesquiera que sean tus nombres reales

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|my-plugin) plugins/my-plugin || true)'" -- --all

Ahora para una explicación. git filter-branch --tree-filter (...) HEAD ejecuta el (...) comando en cada compromiso que se puede alcanzar desde HEAD. Tenga en cuenta que esto opera directamente en los datos almacenados para cada confirmación, por lo que no debe preocuparse por las nociones de "directorio de trabajo", "índice", "estadificación", etc.

Si ejecuta un filter-branch comando que falla, dejará algunos archivos en el .git directorio y la próxima vez que intente filter-branch se quejará de esto, a menos que proporcione el -f opción de filter-branch.

En cuanto al comando real, no tuve mucha suerte de conseguir bash hacer lo que yo quería, así que en su lugar uso zsh -c para hacer zsh ejecuta un comando. Primero configuro el extended_glob opción, que es lo que permite ^(...) sintaxis en el mv comando, así como la glob_dots opción, que me permite seleccionar archivos punto (como .gitignore) con un glob (^(...))

Luego, uso el mkdir -p comando para crear ambos plugins y plugins/my-plugin al mismo tiempo.

Finalmente, uso el zsh función "glob negativo" ^(.git|my-plugin) para unir todos los archivos en el directorio raíz del repositorio a excepción de .git y el recién creado my-plugin carpeta. (Excluyendo .git podría no ser necesario aquí, pero tratar de mover un directorio dentro de sí mismo es un error.)

En mi repositorio, la confirmación inicial no incluía ningún archivo, por lo que 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 así que eso git filter-branch no abortaría

los --all la opción dice filter-branch para reescribir el historial de todas ramas en el repositorio, y el extra -- es necesario contar git interpretarlo como parte de la lista de opciones para que las ramas reescriban, en lugar de como una opción para filter-branch sí mismo.

Ahora, navega hacia tu main-project repositorio y verifique en qué rama desea fusionarse. Agregue su copia local del my-plugin repositorio (con su historial modificado) como control remoto de main-project con:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Ahora tendrá dos árboles no relacionados en su historial de compromisos, que puede visualizar muy bien usando:

$ git log --color --graph --decorate --all

Para fusionarlos, usa:

$ git merge my-plugin/master --allow-unrelated-histories

Tenga en cuenta que en Git pre-2.9.0, el --allow-unrelated-histories la opción no existe. Si está utilizando una de estas versiones, simplemente omita la opción: el mensaje de error que --allow-unrelated-histories se previene además agregado en 2.9.0.

No deberías tener ningún conflicto de fusión. Si lo haces, probablemente signifique que o bien filter-branch comando no funcionó correctamente o ya había un plugins/my-plugin directorio en main-project.

Asegúrese de ingresar un mensaje de compromiso explicativo para cualquier colaborador futuro que se pregunte qué hackeo iba a hacer para crear un repositorio con dos raíces.

Puede visualizar el nuevo gráfico de compromiso, que debe tener dos confirmaciones de raíz, utilizando el gráfico anterior git log mando. Tenga en cuenta que Solo el master rama se fusionará. Esto significa que si tienes un trabajo importante en otro my-plugin ramas que desea fusionar en el main-project árbol, debe abstenerse de eliminar el my-plugin remoto hasta que haya hecho estas fusiones. Si no lo hace, entonces las confirmaciones de esas ramas seguirán en el main-project repositorio, pero algunos serán inalcanzables y susceptibles a la eventual recolección de basura. (Además, deberá hacer referencia a ellos por SHA, ya que al eliminar un control remoto se eliminan sus ramas de seguimiento remoto).

Opcionalmente, después de fusionar todo lo que desea evitar my-plugin, puedes eliminar el my-plugin uso remoto:

$ git remote remove my-plugin

Ahora puede eliminar de forma segura la copia del my-plugin repositorio cuya historia ha cambiado. En mi caso, también agregué un aviso de desaprobación al real my-plugin repositorio después de la fusión se completó y 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


13
2018-03-10 12:51



He estado tratando de hacer lo mismo por días, estoy usando git 2.7.2. Subárbol no conserva el historial.

Puede usar este método si no volverá a utilizar el proyecto anterior.

Yo sugeriría que bifurquen a B primero y trabajen en la rama.

Estos son los pasos sin ramificación:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Si ahora registras cualquiera de los archivos en el subdirector A obtendrás el historial completo

git log --follow A/<file>

Esta fue la publicación que me ayudó a hacer esto:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/


8
2018-05-07 13:38



Sé que es mucho después del hecho, pero no estaba contento con las otras respuestas que encontré aquí, así que escribí esto:

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done

6
2018-01-23 00:08