remota - ¿Cómo fusionar o seleccionar selectivamente los cambios de otra rama en Git?
git pull (24)
Estoy usando git en un nuevo proyecto que tiene dos ramas de desarrollo paralelas, pero actualmente experimentales:
-
master
: importación de la base de código existente más algunos mods de los que estoy generalmente seguro -
exp1
: rama experimental # 1 -
exp2
: rama experimental # 2
exp1
y exp2
representan dos enfoques arquitectónicos muy diferentes. Hasta que no avance, no tengo forma de saber cuál funcionará (si es que funciona). A medida que avanzo en una rama, a veces tengo ediciones que serían útiles en la otra rama y me gustaría fusionarlas.
¿Cuál es la mejor manera de combinar cambios selectivos de una rama de desarrollo a otra y dejar atrás todo lo demás?
Enfoques que he considerado:
git merge --no-commit
seguido de una puesta a punto manual de una gran cantidad de ediciones que no quiero hacer en común entre las sucursales.Copia manual de archivos comunes en un directorio temporal seguido de
git checkout
para pasar a la otra rama y luego copia manual del directorio temporal en el árbol de trabajo.Una variación de lo anterior. Abandone las ramas
exp
por ahora y use dos repositorios locales adicionales para la experimentación. Esto hace que la copia manual de archivos sea mucho más sencilla.
Los tres enfoques parecen tediosos y propensos a errores. Espero que haya un mejor enfoque; algo parecido a un parámetro de ruta de filtro que haría que git-merge
más selectivo.
Aquí es cómo puede reemplazar el archivo Myclass.java
en la rama master
con Myclass.java
en la rama Myclass.java
. Funcionará incluso si Myclass.java
no existe en el master
.
git checkout master
git checkout feature1 Myclass.java
Tenga en cuenta que esto sobrescribirá, no fusionará, e ignorará los cambios locales en la rama maestra.
Aquí le explicamos cómo puede hacer que el historial siga solo un par de archivos de otra rama con un mínimo de esfuerzo, incluso si una combinación más "simple" hubiera generado muchos más cambios que no desea.
Primero, tomará el paso inusual de declarar por adelantado que lo que está a punto de confirmar es una combinación, sin que Git haga nada en absoluto a los archivos en su directorio de trabajo:
git merge --no-ff --no-commit -s ours branchname1
. . . donde "nombre de sucursal" es lo que dice que se está fusionando. Si se comprometiera de inmediato, no se realizarían cambios, pero seguiría mostrando la ascendencia de la otra rama. Puede agregar más ramas / etiquetas / etc. a la línea de comandos si es necesario, también. En este punto, sin embargo, no hay cambios para confirmar, así que obtenga los archivos de las otras revisiones, a continuación.
git checkout branchname1 -- file1 file2 etc
Si se estaba fusionando de más de una rama, repita según sea necesario.
git checkout branchname2 -- file3 file4 etc
Ahora los archivos de la otra rama están en el índice, listos para ser confirmados, con historial.
git commit
y tendrás mucho que explicar en ese mensaje de confirmación.
Sin embargo, tenga en cuenta, en caso de que no quede claro, que esto no está bien hecho. No está en el espíritu de lo que es una "rama", y la selección de cerezas es una manera más honesta de hacer lo que estarías haciendo aquí. Si desea hacer otra "combinación" para otros archivos en la misma rama que no trajo la última vez, se detendrá con un mensaje "ya actualizado". Es un síntoma de no ramificar cuando deberíamos, en la rama "desde" debería haber más de una rama diferente.
Cuando solo unos pocos archivos han cambiado entre las confirmaciones actuales de las dos ramas, fusiono manualmente los cambios yendo a través de los diferentes archivos.
git difftoll <branch-1>..<branch-2>
Es extraño que git todavía no tenga una herramienta tan conveniente "fuera de la caja". Lo uso mucho cuando actualizo alguna versión anterior de la versión (que todavía tiene muchos usuarios de software) con solo algunas correcciones de errores de la versión actual. En este caso, a menudo se necesita obtener rápidamente algunas líneas de código del archivo en el tronco, ignorando muchos otros cambios (que no se supone que vayan a la versión anterior) ... Y, por supuesto, una combinación interactiva de tres vías es necesario en este caso, git checkout --patch <branch> <file path>
no es utilizable para este propósito de combinación selectiva.
Puedes hacerlo fácilmente:
Simplemente agregue esta línea a la sección [alias]
en su .gitconfig
global .gitconfig
o local .git/config
:
[alias]
mergetool-file = "!sh -c ''git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;'' -"
Implica que uses Beyond Compare. Simplemente cambie al software de su elección si es necesario. O puede cambiarlo a fusión automática de tres vías si no necesita la fusión selectiva interactiva:
[alias]
mergetool-file = "!sh -c ''git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;'' -"
Entonces use así:
git mergetool-file <source branch> <file path>
Esto le dará la verdadera oportunidad de combinación selectiva en forma de árbol de cualquier archivo en otra rama.
Hay otra manera de ir:
git checkout -p
Es una mezcla entre git checkout
y git add -p
y podría ser exactamente lo que está buscando:
-p, --patch
Interactively select hunks in the difference between the <tree-ish>
(or the index, if unspecified) and the working tree. The chosen
hunks are then applied in reverse to the working tree (and if a
<tree-ish> was specified, the index).
This means that you can use git checkout -p to selectively discard
edits from your current working tree. See the “Interactive Mode”
section of git-add(1) to learn how to operate the --patch mode.
He encontrado jasonrudolph.com/blog/2009/02/25/… para contener la respuesta más simple. Simplemente hacer:
$ #git checkout <branch from which you want files> <file paths>
Ejemplo:
$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore
Vea la publicación para más información.
La forma más sencilla es establecer su repositorio en la rama que desea fusionar y luego ejecutar,
git checkout [branch with file] [path to file you would like to merge]
Si tu corres
git status
Verás el archivo ya montado ...
Entonces corre
git commit -m "Merge changes on ''[branch]'' to [file]"
Sencillo.
La forma sencilla, de combinar archivos específicos de dos ramas, no solo reemplazar archivos específicos con archivos de otra rama.
Paso uno: Difundir las ramas.
git diff branch_b > my_patch_file.patch
Crea un archivo de parche de la diferencia entre la rama actual y la rama_b
Paso dos: aplique el parche en los archivos que coincidan con un patrón
git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch
Notas útiles sobre las opciones.
Puede utilizar *
como comodín en el patrón de inclusión.
Las barras diagonales no necesitan ser escapadas.
Además, podría usar --exclude en su lugar y aplicarlo a todo, excepto a los archivos que coincidan con el patrón, o invertir el parche con -R
La opción -p1 es un remanente del comando de parche * unix y el hecho de que el contenido del archivo de parche preceda a cada nombre de archivo con a/
o b/
(o más dependiendo de cómo se generó el archivo de parche), que debe eliminar para que puede averiguar el archivo real de la ruta al archivo al que se debe aplicar el parche.
Echa un vistazo a la página de manual de git-apply para ver más opciones.
Paso tres: no hay paso tres
Obviamente, querría confirmar sus cambios, pero ¿quién puede decir que no tiene otros ajustes relacionados que desea hacer antes de comprometerse?
La respuesta de 1800 INFORMATION es completamente correcta. Sin embargo, como un git noob, "usar git cherry-pick" no fue suficiente para que resolviera esto sin un poco más de excavación en Internet, así que pensé en publicar una guía más detallada en caso de que alguien más esté en una barco similar.
Mi caso de uso era querer tirar selectivamente los cambios de la rama de github de otra persona a la mía. Si ya tiene una sucursal local con los cambios, solo necesita hacer los pasos 2 y 5-7.
Cree (si no se crea) una rama local con los cambios que desea introducir.
$ git branch mybranch <base branch>
Cambia a eso.
$ git checkout mybranch
Despliegue los cambios que desee de la cuenta de la otra persona. Si aún no lo ha hecho, querrá agregarlos como control remoto.
$ git remote add repos-w-changes <git url>
Tirar todo de su rama.
$ git pull repos-w-changes branch-i-want
Vea los registros de confirmación para ver qué cambios desea:
$ git log
Vuelve a la rama en la que quieres tirar los cambios.
$ git checkout originalbranch
Cereza elige tus compromisos, uno por uno, con los hashes.
$ git cherry-pick -x hash-of-commit
Punta de sombrero: http://www.sourcemage.org/Git_Guide
Me gusta la respuesta ''git-interactive-merge'', arriba, pero hay una más fácil. Deje que git haga esto por usted usando una combinación de rebase de interactivo y en:
A---C1---o---C2---o---o feature
/
----o---o---o---o master
Entonces, el caso es que desea C1 y C2 de la rama ''característica'' (punto de ramificación ''A''), pero nada del resto por ahora.
# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp
Lo que, como se indicó anteriormente, lo envía al editor interactivo donde selecciona las líneas de ''selección'' para C1 y C2 (como se muestra arriba). Guarde y salga, y luego procederá con la rebase y le dará la rama ''temp'' y también HEAD at master + C1 + C2:
A---C1---o---C2---o---o feature
/
----o---o---o---o-master--C1---C2 [HEAD, temp]
Luego, simplemente puede actualizar master a HEAD y eliminar la rama temporal y listo:
# git branch -f master HEAD
# git branch -d temp
No es exactamente lo que buscabas, pero fue útil para mí:
git checkout -p <branch> -- <paths> ...
Es una mezcla de algunas respuestas.
No me gustan los enfoques anteriores. El uso de cherry-pick es ideal para elegir un solo cambio, pero es un dolor si desea incluir todos los cambios excepto algunos malos. Aquí está mi enfoque.
No hay --interactive
argumento --interactive
que puedas pasar a git merge.
Aquí está la alternativa:
Tiene algunos cambios en la ''característica'' de la rama y desea llevar algunos, pero no todos, al ''maestro'' de una manera no descuidada (es decir, no quiere elegir y cometer cada uno de ellos)
git checkout feature
git checkout -b temp
git rebase -i master
# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change
# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
git checkout master
git pull . temp
git branch -d temp
Así que simplemente envuélvalo en un script de shell, cambie el maestro en $ to y cambie la función en $ from y estará listo:
#! /bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp
Para fusionar selectivamente archivos de una rama en otra rama, ejecute
git merge --no-ff --no-commit branchX
donde branchX
es la rama que desea combinar en la rama actual.
La --no-commit
mostrará los archivos que Git ha fusionado sin haberlos --no-commit
. Esto le dará la oportunidad de modificar los archivos combinados como desee y luego cometerlos usted mismo.
Dependiendo de cómo desee fusionar archivos, hay cuatro casos:
1) Quieres una verdadera fusión.
En este caso, acepta los archivos combinados de la forma en que Git los fusionó automáticamente y luego los confirma.
2) Hay algunos archivos que no desea combinar.
Por ejemplo, desea conservar la versión en la rama actual e ignorar la versión en la rama de la que se está fusionando.
Para seleccionar la versión en la rama actual, ejecute:
git checkout HEAD file1
Esto recuperará la versión de file1
en la rama actual y sobrescribirá el archivo1 automatizado por Git.
3) Si quieres la versión en BranchX (y no una verdadera fusión).
Correr:
git checkout branchX file1
Esto recuperará la versión del branchX
file1
en branchX
y sobrescribirá el branchX
file1
fusionado automáticamente por Git.
4) El último caso es si desea seleccionar solo fusiones específicas en file1
.
En este caso, puede editar el file1
modificado1 directamente, actualizarlo a lo que quiera que se convierta en la versión del file1
y luego confirmar.
Si Git no puede fusionar un archivo automáticamente, lo reportará como "no fusionado " y producirá una copia donde deberá resolver los conflictos manualmente.
Para explicar con más branchX
un ejemplo, supongamos que desea fusionar branchX
en la rama actual:
git merge --no-ff --no-commit branchX
A continuación, ejecuta el comando git status
para ver el estado de los archivos modificados.
Por ejemplo:
git status
# On branch master
# Changes to be committed:
#
# modified: file1
# modified: file2
# modified: file3
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: file4
#
Donde file1
, file2
y file3
son los archivos que git se ha fusionado correctamente.
Lo que esto significa es que los cambios en el master
y branchX
para todos esos tres archivos se han combinado sin ningún conflicto.
Puede inspeccionar cómo se realizó la fusión ejecutando git diff --cached
;
git diff --cached file1
git diff --cached file2
git diff --cached file3
Si encuentra alguna fusión indeseable entonces puede
- edita el archivo directamente
- salvar
-
git commit
Si no desea combinar file1
y desea conservar la versión en la rama actual
correr
git checkout HEAD file1
Si no desea combinar file2
y solo quiere la versión en branchX
correr
git checkout branchX file2
Si desea que file3
se fusione automáticamente, no haga nada.
Git ya lo ha fusionado en este punto.
file4
anterior es una fusión fallida por Git. Esto significa que hay cambios en ambas ramas que ocurren en la misma línea. Aquí es donde deberá resolver los conflictos manualmente. Puede descartar la combinación realizada editando el archivo directamente o ejecutando el comando de pago para la versión en la rama en la que desea que se convierta el file4
.
Por último, no te olvides de git commit
.
Puede usar read-tree
de lectura para leer o combinar un árbol remoto dado en el índice actual, por ejemplo:
git remote add foo [email protected]/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder
Para realizar la fusión, utilice -m
lugar.
Ver también: ¿Cómo fusiono un subdirectorio en git?
Sé que esta pregunta es antigua y hay muchas otras respuestas, pero escribí mi propio script llamado ''pmerge'' para fusionar directorios parcialmente. Es un trabajo en progreso y todavía estoy aprendiendo git y bash scripting.
Este comando usa git merge --no-commit
y luego no aplica los cambios que no coinciden con la ruta proporcionada.
Uso: git pmerge branch path
Ejemplo: git merge develop src/
No lo he probado extensivamente. El directorio de trabajo debe estar libre de cambios no confirmados y archivos sin seguimiento.
#!/bin/bash
E_BADARGS=65
if [ $# -ne 2 ]
then
echo "Usage: `basename $0` branch path"
exit $E_BADARGS
fi
git merge $1 --no-commit
IFS=$''/n''
# list of changes due to merge | replace nulls w newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr ''/000'' ''/n'' | sed -e ''s/^[[:graph:]][[:space:]]/{1,/}//'' | uniq); do
[[ $f == $2* ]] && continue
if git reset $f >/dev/null 2>&1; then
# reset failed... file was previously unversioned
echo Deleting $f
rm $f
else
echo Reverting $f
git checkout -- $f >/dev/null 2>&1
fi
done
unset IFS
Sé que llego un poco tarde, pero este es mi flujo de trabajo para fusionar archivos selectivos.
#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes
git merge --no-commit featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit
Si bien algunas de estas respuestas son bastante buenas, siento que ninguna realmente respondió la restricción original de OP: seleccionar archivos particulares de sucursales particulares. Esta solución hace eso, pero puede ser tediosa si hay muchos archivos.
Digamos que tienes las ramas master
, exp1
y exp2
. Desea combinar un archivo de cada una de las ramas experimentales en maestro. Haría algo como esto:
git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b
# save these files as a stash
git stash
# merge stash with master
git merge stash
Esto le dará diferencias en el archivo para cada uno de los archivos que desea. Nada mas. Nada menos. Es útil tener cambios de archivo radicalmente diferentes entre las versiones, en mi caso, cambiar una aplicación de Rails 2 a Rails 3.
EDITAR : esto fusionará archivos, pero hace una combinación inteligente. No pude averiguar cómo usar este método para obtener información de diferencias en el archivo (tal vez todavía lo sea para diferencias extremas. Las pequeñas cosas molestas como el espacio en blanco se vuelven a combinar a menos que use el -s recursive -X ignore-all-space
opción de -s recursive -X ignore-all-space
)
Si no tiene demasiados archivos que hayan cambiado, esto lo dejará sin compromisos adicionales.
1. Duplicar rama temporalmente.
$ git checkout -b temp_branch
2. Restablecer al último compromiso deseado
$ git reset --hard HEAD~n
, donde n
es el número de confirmaciones que necesita para volver
3. Revisa cada archivo de la rama original
$ git checkout origin/original_branch filename.ext
Ahora puede cometer y forzar push (para sobrescribir el control remoto), si es necesario.
Si solo necesita fusionar un directorio en particular y dejar todo lo demás intacto y aún así conservar el historial, puede intentar esto ... cree una nueva target-branch
fuera del master
antes de experimentar.
Los pasos a continuación suponen que tiene dos sucursales target-branch
y source-branch
, y el directorio dir-to-merge
que desea fusionar se encuentra en el source-branch
. También asuma que tiene otros directorios como dir-to-retain
en el destino que no desea cambiar y retener el historial. Además, asume que hay conflictos de combinación en el dir-to-merge
.
git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict.
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.
# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.
Tuve exactamente el mismo problema que mencionaste anteriormente. Pero encontré jasonrudolph.com/blog/2009/02/25/… más claro al explicar la respuesta.
Comando desde el enlace de arriba:
#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
Tuve exactamente el mismo problema que mencionaste anteriormente. Pero encontré jasonrudolph.com/blog/2009/02/25/… más claro al explicar la respuesta.
Resumen:
Verifique la (s) ruta (s) de la rama que desea combinar,
$ git checkout source_branch -- <paths>...
o fusionar selectivamente los tíos
$ git checkout -p source_branch -- <paths>...
Alternativamente, use reiniciar y luego agregue con la opción
-p
,$ git reset <paths>... $ git add -p <paths>...
Finalmente cometer
$ git commit -m "''Merge'' these changes"
Un enfoque simple para la fusión / confirmación selectiva por archivo:
git checkout dstBranch git merge srcBranch // make changes, including resolving conflicts to single files git add singleFile1 singleFile2 git commit -m "message specific to a few files" git reset --hard # blow away uncommitted changes
Utiliza el comando cherry-pick para obtener confirmaciones individuales de una rama.
Si los cambios que desea no están en confirmaciones individuales, utilice el método que se muestra aquí para dividir la confirmación en confirmaciones individuales . En términos generales, usa git rebase -i
para obtener la confirmación original para editar, luego git reset HEAD^
para revertir los cambios de forma selectiva, y luego git commit
para confirmar ese bit como una nueva confirmación en el historial.
Hay otro método agradable aquí en Red Hat Magazine, donde usan git add --patch
o posiblemente git add --interactive
que le permite agregar solo partes de un trozo, si desea dividir diferentes cambios en un archivo individual (búsqueda en esa página para "dividir").
Una vez divididos los cambios, ahora puede elegir los que desee.
Yo haria un
git diff commit1..commit2 filepattern | git-apply --index && git commit
De esta manera puede limitar el rango de confirmaciones para un filepattern desde una rama.
Robado de: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html