Pregunta

Estoy usando git en un nuevo proyecto que tiene dos ramas de desarrollo paralelas, pero actualmente experimentales:

  • master:importación del código base existente más algunas modificaciones de las que generalmente estoy seguro
  • exp1:rama experimental #1
  • exp2:rama experimental #2

exp1 y exp2 representan dos enfoques arquitectónicos muy diferentes.Hasta que avance, no tengo forma de saber cuál (si es que alguno) funcionará.A medida que avanzo en una rama, a veces tengo ediciones que serían útiles en la otra rama y me gustaría fusionar solo esas.

¿Cuál es la mejor manera de fusionar cambios selectivos de una rama de desarrollo a otra dejando atrás todo lo demás?

Enfoques que he considerado:

  1. git merge --no-commit seguido de la eliminación manual de una gran cantidad de ediciones que no quiero que sean comunes entre las ramas.

  2. Copia manual de archivos comunes en un directorio temporal seguido de git checkout para pasar a la otra rama y luego realizar más copias manuales desde el directorio temporal al árbol de trabajo.

  3. Una variación de lo anterior.abandonar el exp sucursales por ahora y use dos repositorios locales adicionales para experimentar.Esto hace que la copia manual de archivos sea mucho más sencilla.

Estos 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 git-merge más selectivo.

¿Fue útil?

Solución

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 el comprometerse en confirmaciones individuales . En términos generales, utiliza git rebase -i para obtener la confirmación original para editar, luego git reset HEAD^ para revertir los cambios selectivamente, luego git commit para confirmar ese bit como una nueva confirmación en el historial.

Hay otro buen método 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 cambia a un archivo individual (busque en esa página " split ").

Después de dividir los cambios, ahora puede elegir los que desee.

Otros consejos

Tuve exactamente el mismo problema mencionado anteriormente. Pero encontré esto más claro al explicar la respuesta.

Resumen:

  • Verifique las rutas desde la rama que desea fusionar,

    $ git checkout source_branch -- <paths>...
    

    Sugerencia: también funciona sin -- como se ve en la publicación vinculada.

  • o para fusionar selectivamente trozos

    $ git checkout -p source_branch -- <paths>...
    

    Alternativamente, use reset y luego agregue con la opción -p,

    $ git reset <paths>...
    $ git add -p <paths>...
    
  • Finalmente confirmar

    $ git commit -m "'Merge' these changes"
    

Para fusionar selectivamente archivos de una rama a otra rama, ejecute

git merge --no-ff --no-commit branchX

dónde branchX es la rama desde la que desea fusionarse con la rama actual.

El --no-commit La opción preparará los archivos que Git ha fusionado sin confirmarlos realmente.Esto le dará la oportunidad de modificar los archivos combinados como desee y luego confirmarlos usted mismo.

Dependiendo de cómo desee fusionar archivos, existen cuatro casos:

1) Quieres una verdadera fusión.

En este caso, acepta los archivos fusionados de la misma manera que Git los fusionó automáticamente y luego los confirma.

2) Hay algunos archivos que no desea fusionar.

Por ejemplo, desea conservar la versión en la rama actual e ignorar la versión en la rama desde 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 la file1 autofusionado por Git.

3) Si desea la versión en BranchX (y no una verdadera fusión).

Correr:

git checkout branchX file1

Esto recuperará la versión de file1 en branchX y sobrescribir 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 modificado file1 directamente, actualícelo a la versión que desee file1 llegar a ser y luego comprometerse.

Si Git no puede fusionar un archivo automáticamente, informará el archivo como "no fusionado" y produzca una copia donde deberá resolver los conflictos manualmente.



Para explicar más con un ejemplo, digamos que desea fusionar branchX en la rama actual:

git merge --no-ff --no-commit branchX

Luego ejecutas el git status comando 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
#

Dónde file1, file2, y file3 son los archivos que git ha fusionado automáticamente con éxito.

Lo que esto significa es que los cambios en el master y branchX porque esos tres archivos se han combinado sin ningún conflicto.

Puede inspeccionar cómo se realizó la fusión ejecutando el git diff --cached;

git diff --cached file1
git diff --cached file2
git diff --cached file3

Si encuentra que alguna combinación no es deseable, puede hacerlo

  1. editar el archivo directamente
  2. ahorrar
  3. git commit

Si no quieres fusionarte file1 y desea conservar la versión en la rama actual

Correr

git checkout HEAD file1

Si no quieres fusionarte file2 y solo quiero la versión en branchX

Correr

git checkout branchX file2

Si quieres file3 para fusionarse automáticamente, no hagas nada.

Git ya lo ha fusionado en este punto.


file4 Lo anterior es una fusión fallida de Git.Esto significa que hay cambios en ambas ramas que ocurren en la misma línea.Aquí es donde deberás 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 que desee. file4 convertirse.


Finalmente, no olvides git commit.

No me gustan los enfoques anteriores. Usar cherry-pick es excelente para elegir un solo cambio, pero es una molestia si desea incorporar todos los cambios excepto algunos malos. Aquí está mi enfoque.

No hay ningún argumento --interactive que pueda pasar para git merge.

Aquí está la alternativa:

Tiene algunos cambios en la 'función' de la rama y desea llevar algunos, pero no todos, al 'maestro' de una manera no descuidada (es decir, no quiere elegir y comprometer cada uno)

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 envuelva eso en un script de shell, cambie master a $ ay cambie la función a $ 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

Hay otra manera de hacerlo:

git checkout -p

Es una mezcla entre git checkout y git add -p y podría ser exactamente lo que estás 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.

Si bien algunas de estas respuestas son bastante buenas, siento que ninguna respondió a la restricción original de OP:seleccionando archivos particulares de ramas particulares.Esta solución hace eso, pero puede resultar tediosa si hay muchos archivos.

Digamos que tienes el master, exp1, y exp2 sucursales.Desea fusionar un archivo de cada una de las ramas experimentales en el maestro.Yo 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 dentro del archivo para cada uno de los archivos que desee.Nada mas.Nada menos.Es útil tener cambios de archivos radicalmente diferentes entre versiones; en mi caso, cambiar una aplicación de Rails 2 a Rails 3.

EDITAR:esto fusionará archivos, pero realizará una fusión inteligente.No pude descubrir cómo usar este método para obtener información de diferencias en el archivo (tal vez todavía lo haga para diferencias extremas).Pequeñas cosas molestas como los espacios en blanco se vuelven a fusionar a menos que uses el -s recursive -X ignore-all-space opción)

La respuesta de 1800 INFORMATION es completamente correcta. Sin embargo, como git noob, & Quot; use git cherry-pick & Quot; no fue suficiente para que me diera cuenta de esto sin investigar un poco más en Internet, así que pensé en publicar una guía más detallada en caso de que alguien más esté en un barco similar.

Mi caso de uso era querer extraer selectivamente los cambios de la rama github de otra persona a la mía. Si ya tiene una sucursal local con los cambios, solo necesita realizar los pasos 2 y 5-7.

  1. Cree (si no se creó) una sucursal local con los cambios que desea incorporar.

    $ git branch mybranch <base branch>

  2. Cambia a él.

    $ git checkout mybranch

  3. 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>

  4. Despliegue todo desde su rama.

    $ git pull repos-w-changes branch-i-want

  5. Vea los registros de confirmación para ver qué cambios desea:

    $ git log

  6. Vuelva a la rama en la que desea extraer los cambios.

    $ git checkout originalbranch

  7. Cherry elige tus commits, uno por uno, con los hashes.

    $ git cherry-pick -x hash-of-commit

Sugerencia: http://www.sourcemage.org/Git_Guide

Aquí es cómo puede reemplazar el archivo Myclass.java en la rama master con feature1 en la rama <=>. Funcionará incluso si <=> no existe en <=>.

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.

La manera simple, para fusionar archivos específicos de dos ramas, no solo reemplazar archivos específicos con los de otra rama.

Paso uno: Diferencia las ramas

git diff branch_b > my_patch_file.patch

Crea un archivo de parche de la diferencia entre la rama actual y branch_b

Paso dos: aplique el parche en 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 usar * como comodín en el patrón de inclusión.

Las barras no necesitan ser escapadas.

Además, puede usar --exclude en su lugar y aplicarlo a todo excepto a los archivos que coinciden con el patrón, o revertir 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 antepone cada nombre de archivo con a/ o b/ (o más, dependiendo de cómo se generó el archivo de parche) necesita eliminar para que pueda descubrir el archivo real de la ruta al archivo al que debe aplicarse el parche.

Consulte la página de manual de git-apply para obtener 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 realizar su confirmación.

Aquí se explica cómo puede hacer que el historial siga solo un par de archivos de otra rama con un mínimo de alboroto, incluso si hay más & "; simple &"; fusionar habría traído muchos más cambios que no desea.

Primero, darás el paso inusual de declarar de antemano que lo que estás a punto de cometer es una fusión, sin que git haga nada en absoluto a los archivos en tu directorio de trabajo:

git merge --no-ff --no-commit -s ours branchname1

. . . donde " nombre de sucursal " es lo que usted dice que se está fusionando. Si tuviera que comprometerse de inmediato, no haría cambios, pero aún mostraría ascendencia de la otra rama. Puede agregar más ramas / etiquetas / etc. a la línea de comando si es necesario, también. Sin embargo, en este punto, 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 desde más de otra 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 muchas explicaciones que hacer en ese mensaje de confirmación.

Tenga en cuenta, sin embargo, en caso de que no esté claro, que esto es algo desordenado que hacer. No está en el espíritu de lo que es una & Quot; branch & Quot; es para, y cherry-pick es una forma más honesta de hacer lo que estarías haciendo aquí. Si quisieras hacer otra & Quot; fusionar & Quot; para otros archivos en la misma rama que no trajo la última vez, lo detendrá con un " ya actualizado " mensaje. Es un síntoma de no ramificarse cuando deberíamos haberlo hecho, en & Quot; from & Quot; rama debe ser más de una rama diferente.

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

Encontré esta publicación 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 fácil es establecer su repositorio en la rama con la que desea fusionar y luego ejecutar,

git checkout [branch with file] [path to file you would like to merge]

Si corres

git status

verá el archivo ya preparado ...

Luego ejecuta

git commit -m "Merge changes on '[branch]' to [file]"

Simple.

Es extraño que git todavía no tenga una herramienta tan conveniente & "; fuera de la caja &"; Lo uso mucho cuando actualizo alguna rama de la versión anterior (que todavía tiene muchos usuarios de software) mediante solo algunas correcciones de errores de la rama de la versión actual. En este caso, a menudo es necesario obtener rápidamente solo algunas líneas de código del archivo en el tronco, ignorando muchos otros cambios (que no se supone que entren en la versión anterior) ... Y por supuesto, en este caso se necesita la combinación interactiva de tres vías , git checkout --patch <branch> <file path> no es utilizable para este propósito de combinación selectiva.

Puede hacerlo fácilmente:

Simplemente agregue esta línea a la sección [alias] en su archivo 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 usas 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;' -"

Luego use así:

git mergetool-file <source branch> <file path>

Esto le dará la verdadera oportunidad de combinación de forma de árbol selectiva de cualquier archivo en otra rama.

No es exactamente lo que estabas buscando, pero me fue útil:

git checkout -p <branch> -- <paths> ...

Es una mezcla de algunas respuestas.

Tuve exactamente el mismo problema mencionado anteriormente. Pero encontré este blog de git es 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...>

Haría un

  

git diff commit1..commit2 filepattern | git-apply --index & amp; & amp; git commit

De esta manera puede limitar el rango de confirmaciones para un patrón de archivos desde una rama.

Robado de: http: //www.gelato.unsw .edu.au / archives / git / 0701 / 37964.html

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 rama 'A'), pero ninguno de los demás por ahora.

# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp

Que, como arriba, te lleva al editor interactivo donde seleccionas las líneas 'pick' para C1 y C2 (como arriba). Guarde y salga, y luego procederá con el rebase y le dará 'temp' de rama y también HEAD en master + C1 + C2:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o-master--C1---C2 [HEAD, temp]

Luego puede actualizar master a HEAD y eliminar la rama temporal y listo:

# git branch -f master HEAD
# git branch -d temp

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 los scripts de git y bash.

Este comando utiliza git merge --no-commit y luego anula la aplicación de los cambios que no coinciden con la ruta proporcionada.

Uso: git pmerge branch path
Ejemplo: git merge develop src/

No lo he probado extensamente.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

¿Qué pasa con git reset --soft branch? Me sorprende que nadie lo haya mencionado todavía.

Para mí, es la forma más fácil de seleccionar selectivamente los cambios de otra rama, ya que, este comando pone en mi árbol de trabajo, todos los cambios de diferencias y puedo elegir o revertir fácilmente cuál necesito. De esta manera, tengo control total sobre los archivos comprometidos.

Puede usar read-tree para leer o combinar un árbol remoto dado en el índice actual, por ejemplo:

git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

Para realizar la fusión, use -m en su lugar.

Consulte también: ¿Cómo fusiono un subdirectorio en git?

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

Si no tiene demasiados archivos que han cambiado, esto lo dejará sin confirmaciones adicionales.

1. Duplicar sucursal temporalmente
$ git checkout -b temp_branch

2. Restablecer a la última confirmación deseada
$ git reset --hard HEAD~n, donde n es la cantidad de confirmaciones que necesita para volver

3. Extraiga cada archivo de la rama original
$ git checkout origin/original_branch filename.ext

Ahora puede confirmar y forzar la inserción (para sobrescribir el control remoto), si es necesario.

Cuando solo unos pocos archivos han cambiado entre las confirmaciones actuales de las dos ramas, combino manualmente los cambios al revisar los diferentes archivos.

git difftoll <branch-1>..<branch-2>

Si sólo necesita fusionar un directorio en particular y dejar todo lo demás intacto y aún así preservar el historial, podría intentar esto...crear un nuevo target-branch fuera del master antes de experimentar.

Los pasos siguientes suponen que tienes dos ramas. target-branch y source-branch, y el directorio dir-to-merge que quieres fusionar está en el source-branch.También suponga que tiene otros directorios como dir-to-retain en el objetivo que no desea cambiar y conservar el historial.Además, se supone que hay conflictos de fusió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.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top