¿Cómo fusiono un subdirectorio en Git?

84

¿Es posible fusionar solo los cambios para un subdirectorio de una rama de Git local a una rama de Git remota o es "todo o nada"?

Por ejemplo, tengo:

branch-a
 - content-1
 - dir-1
   - content-2

y

branch-b
 - content-1
 - dir-1
   - `content-2

Solo quiero fusionar el contenido de branch-a dir-1 con el contenido de branch-b dir-1.

Brad Gessler
fuente
1
Creo que esto es un duplicado de: stackoverflow.com/questions/449541/…
Karl Voigtland

Respuestas:

81

Solo como una alternativa a la pregunta SO " ¿Cómo fusionar archivos selectivos con git-merge? ", Acabo de encontrar este hilo de GitHub que podría adaptarse mejor para fusionar un subdirectorio completo, basado en git read-tree :

  • Mi repositorio => cookbooks
    Mi directorio de destino del repositorio =>cookbooks/cassandra
  • Repositorio remoto => infochimps
    Fuente de repositorio remoto en la que quiero fusionar cookbooks/cassandra=>infochimps/cookbooks/cassandra

Aquí están los comandos que usé para fusionarlos

  • Agrega el repositorio y recógelo
git remote add -f infochimps git: //github.com/infochimps/cluster_chef.git
  • Realizar la fusión
git merge --allow-unrelated-history -s our --no-commit infochimps / master

(esto realiza una fusión utilizando la estrategia 'nuestro' ( -s ours), que descarta los cambios de la rama de origen. Esto registra el hecho de que infochimps/masterse ha fusionado, sin modificar ningún archivo en la rama de destino)

  • Fusionar solo infochimps/cookbooks/cassandraencassandra
git read-tree --prefix = cassandra / -u infochimps / master: libros de cocina / cassandra

Esto lee el árbol solo para el subdirectorio fuente requerido cookbooks/cassandra, es decir , en la rama ascendente del repositorio fuente .

Tenga en cuenta que el nombre del subdirectorio de destino también debe ser cookbooks/cassandra, o verá:

fatal: Not a valid object name
  • Comprometer el cambio
 git commit -m 'fusionando en infochimps cassandra'

Apéndice

Es extraño, [edítame] , pero es posible que el read-treepaso falle así:

error: Entry 'infochimps/cookbooks/cassandra/README' overlaps with 'cookbooks/cassandra/README'. Cannot bind.

... incluso cuando ambos archivos son idénticos . Esto podría ayudar:

git rm -r cassandra
git read-tree --prefix=cassandra/ -u infochimps/master:cookbooks/cassandra

Pero, por supuesto, verifique manualmente que esto hace lo que quiere.

VonC
fuente
3
@ Martin git-scm.com/docs/git-rev-parse#_specifying_revisions buscan <rev>:<path>, por ejemplo HEAD:README, :README,master:./README
VonC
6
El git read-treepaso me falla:error: Entry 'foo/bar/baz.php' overlaps with 'bar/baz.php'. Cannot bind.
Weston Ruter
1
@VonC pero no, necesito el historial. Esa respuesta no tiene en cuenta el caso en el que hay modificaciones locales en los archivos dentro del árbol. Por lo que necesita fusionarse.
Weston Ruter
Para mí, el overlaps witherror mencionado anteriormente también aparece en archivos idénticos . ¿Qué tan raro es eso?
ulidtko
1
@ChrisHalcrow Para registrar el hecho de que infochimps/masterse ha fusionado, pero sin modificar ningún archivo en la rama de destino. Porque el siguiente paso git read-tree --prefix=cassandrahará la modificación. La confirmación final registrará el contenido de "fusión" real.
VonC
29

Para mi ejemplo, suponga que tiene una 'fuente' de rama y un 'destino' de rama que reflejan versiones ascendentes de sí mismos (o no, si solo son locales) y se extraen del código más reciente. Digamos que quiero el subdirectorio en el repositorio llamado newFeature que solo existe en la rama 'fuente'.

git checkout destination
git checkout source newFeature/
git commit -am "Merged the new feature from source to destination branch."
git pull --rebase
git push

Es significativamente menos complicado que todo lo demás que he visto y esto funcionó perfectamente para mí, que se encuentra aquí .

Tenga en cuenta que esta no es una 'combinación real', por lo que no tendrá la información de confirmación sobre newFeature en la rama de destino, solo las modificaciones a los archivos en ese subdirectorio. Pero como es de suponer que volverá a fusionar toda la rama más tarde, o la descartará, es posible que eso no sea un problema.

Chris Arena
fuente
2
¿Conserva la historia?
Bibrak
2
@Bibrak este enfoque NO conserva la historia. Al menos no con los comandos actuales.
Ravi Gidwani
6

Obtuve esto de un hilo del foro en Eclipse y funcionó como un encanto:

git checkout source-branch
git checkout target-branch <directories-or-files-you-do-**NOT**-want> 
git commit
git checkout target-branch
git merge source-branch
tobiasbayer
fuente
1
Intenté esto y terminé con un directorio de la rama de origen en la rama de destino que no quería. A pesar de tenerlo especificado con el conjunto de directorios que no quería en ese segundo comando.
maratón
2
Esto no se fusiona, reemplaza la carpeta con la de la rama fuente.
Gp2mv3
@ Gp2mv3 Creo que parece sólido. checkouthace que las carpetas sean iguales => la diferencia es solo un- checkoutfolder => mergediferencia Es una estrategia válida.
Caveman
5

Dado el escenario del OP en el que tienen dos ramas, pero quieren fusionar solo el historial de dir-1 de branch-a en branch-b :

# Make sure you are in the branch with the changes you want
git checkout branch-a

# Split the desired folder into its own temporary branch
# This replays all commits, so it could take a while
git subtree split -P dir-1 -b temp-branch

# Enter the branch where you want to merge the desired changes into
git checkout branch-b

# Merge the changes from the temporary branch
git subtree merge -P dir-1 temp-branch

# Handle any conflicts
git mergetool

# Commit
git commit -am "Merged dir-1 changes from branch-a"

# Delete temp-branch
git branch -d temp-branch
xno
fuente
0

Cree un repositorio de Git para contener tanto branch-a como branch-b:

git checkout branch-a
git diff branch-b dir-1 > a.diff
patch -R -p1 < a.diff
Sun Yin
fuente
14
Esta respuesta necesita más información. ¿Qué de esto es código real vs comentarios?
qodeninja
2
El solicitante desea fusionarse. El uso de un parche para transferir los cambios automáticamente aplasta todas las confirmaciones en un parche y el historial se pierde.
Eric
0

Utilice git cherry-pickpara seleccionar las confirmaciones que desee y fusionar solo estas confirmaciones. El truco clave aquí es obtener estas confirmaciones de una manera fácil (para que no tenga que averiguarlas revisando manualmente el registro de Git e ingresándolas a mano). He aquí cómo: use git logpara imprimir la identificación SHA-1 de la confirmación , así:

git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>

'commit-a' es la confirmación inmediatamente antes del punto de inicio de la rama para fusionar, y 'commit-b' es la última confirmación en la rama para fusionar. '--reverse' imprime estas confirmaciones en orden inverso para seleccionarlas más adelante.

Entonces hazlo como:

git cherry-pick $(git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>)

¡Son dos pasos, simple y estable!

Robert
fuente
excelente y única forma de fusionar un solo directorio de otra rama y preservar el historial. pero ... ... si solo desea una confirmación, cree una rama de todas las confirmaciones del primer comando, luego combine esa nueva rama en una combinación de squash ...
tom
1
Esto supone que las confirmaciones encontradas solo afectan al directorio en cuestión. Si una confirmación afecta tanto al directorio que desea fusionar como a los que no, esto hará que se escoja demasiado.
Scott
Two steps, simple and stable!nada simple :)
Rafa
Entonces, ¿de qué manera crees que es simple? @Rafa
Robert
@Robert No quise dar a entender que tu respuesta no es simple; la culpa es toda git, que tiene una interfaz terrible y un modelo mental terrible lleno de misterios y nombres y significados aleatorios.
Rafa