¿Cómo le digo a git que siempre seleccione mi versión local para combinaciones conflictivas en un archivo específico?

100

Supongamos que estoy colaborando con alguien a través de un repositorio de git, y hay un archivo en particular en el que nunca quiero aceptar ningún cambio externo.

¿Hay alguna forma de configurar mi repositorio local para no quejarme de una combinación conflictiva cada vez que git pull? Me gustaría seleccionar siempre mi versión local al fusionar este archivo.

saffsd
fuente
1
Acabo de agregar una solución simple a través de .gitattributes y un "controlador de combinación" muy básico
VonC
12
TD; LR:echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@CiroSantilli: funciona como un encanto en Linux. Este controlador es lo suficientemente simple como para integrarse en Git ...
krlmlr
¿Desea enviar sus cambios al archivo? ¿O es, por ejemplo, un archivo de configuración donde el valor predeterminado se almacena en git.
Ian Ringrose
El comentario de @CiroSantilli 新疆 改造 中心 六四 事件 法轮功 es correcto, pero hará que este comportamiento ocurra para cada repositorio en su sistema con la --globaletiqueta. Si solo desea este comportamiento para un solo repositorio, --globalecho 'path/to/file merge=ours' >> .gitattributes && git config merge.ours.driver true
omita

Respuestas:

140

En la instancia específica de un archivo de configuración, estaría de acuerdo con la respuesta de Ron :
una configuración debe ser "privada" para su espacio de trabajo (por lo tanto, "ignorada", como en "declarada en un .gitignorearchivo").
Puede tener una plantilla de archivo de configuración con valores tokenizados y una secuencia de comandos que transforma ese config.templatearchivo en un archivo de configuración privado (e ignorado).


Sin embargo, esa observación específica no responde a lo que es una pregunta más general y más amplia, es decir, su pregunta (!):

¿Cómo le digo a git que siempre seleccione mi versión local para combinaciones conflictivas en un archivo específico? (para cualquier archivo o grupo de archivos)

Este tipo de fusión es una "fusión de copia", en la que siempre copiará la versión "nuestra" o "suya" de un archivo siempre que haya un conflicto.

(como señala Brian Vandenberg en los comentarios , ' ours' y ' theirs' se utilizan aquí para una combinación .
Se invierten para una rebase : consulte " Why is the meaning of “ours” and “theirs” reversed with git-svn", que utiliza una rebase, " git rebase, realizando un seguimiento de 'local' y 'remoto' " )

Para "un archivo" (un archivo en general, sin hablar de un archivo de "configuración", ya que es un mal ejemplo), lo lograría con un script personalizado llamado mediante fusiones.
Git llamará a ese script porque habrá definido un valor de gitattributes , que define un controlador de combinación personalizado .

El "controlador de combinación personalizado" es, en este caso, un script muy simple que básicamente mantendrá sin cambios la versión actual, por lo que le permitirá seleccionar siempre su versión local.

. Es decir, como señaló por Ciro Santilli :

echo 'path/to/file merge=ours' >> .gitattributes
git config --global merge.ours.driver true

Probemos eso en un escenario simple, con un msysgit 1.6.3 en Windows, en una mera sesión de DOS:

cd f:\prog\git\test
mkdir copyMerge\dirWithConflicts
mkdir copyMerge\dirWithCopyMerge
cd copyMerge
git init
Initialized empty Git repository in F:/prog/git/test/copyMerge/.git/

Ahora, creemos dos archivos, que tendrán conflictos, pero que se fusionarán de manera diferente.

echo a > dirWithConflicts\a.txt
echo b > dirWithCopyMerge\b.txt
git add -A
git commit -m "first commit with 2 directories and 2 files"
[master (root-commit) 0adaf8e] first commit with 2 directories and 2 files

Introduciremos un "conflicto" en el contenido de ambos archivos en dos ramas de git diferentes:

git checkout -b myBranch
Switched to a new branch 'myBranch'
echo myLineForA >> dirWithConflicts\a.txt
echo myLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in myBranch"
[myBranch 97eac61] add modification in myBranch

git checkout master
Switched to branch 'master'
git checkout -b hisBranch
Switched to a new branch 'hisBranch'
echo hisLineForA >> dirWithConflicts\a.txt
echo hisLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in hisBranch"
[hisBranch 658c31c] add modification in hisBranch

Ahora, intentemos fusionar "hisBranch" en "myBranch", con:

  • resolución manual para fusiones conflictivas
  • excepto por dirWithCopyMerge\b.txtdonde siempre quiero mantener mi versión de b.txt.

Dado que la fusión se produce en " MyBranch", volveremos a utilizarla y agregaremos las gitattributesdirectivas " " que personalizarán el comportamiento de fusión.

git checkout myBranch
Switched to branch 'myBranch'
echo b.txt merge=keepMine > dirWithCopyMerge\.gitattributes
git config merge.keepMine.name "always keep mine during merge"
git config merge.keepMine.driver "keepMine.sh %O %A %B"
git add -A
git commit -m "prepare myBranch with .gitattributes merge strategy"
[myBranch ec202aa] prepare myBranch with .gitattributes merge strategy

Tenemos un .gitattributesarchivo definido en el dirWithCopyMergedirectorio (definido solo en la rama donde ocurrirá la fusión :) myBranch, y tenemos un .git\configarchivo que ahora contiene un controlador de fusión.

[merge "keepMine"]
        name = always keep mine during merge
        driver = keepMine.sh %O %A %B

Si aún no define keepMine.sh y ejecuta la combinación de todos modos, esto es lo que obtiene.

git merge hisBranch
sh: keepMine.sh: command not found
fatal: Failed to execute internal merge
git st
# On branch myBranch
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   dirWithConflicts/a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

type dirWithConflicts\a.txt
a
<<<<<<< HEAD:dirWithConflicts/a.txt
myLineForA
=======
hisLineForA
>>>>>>> hisBranch:dirWithConflicts/a.txt

Eso está bien:

  • a.txt está listo para fusionarse y tiene un conflicto
  • b.txtaún está intacto, ya que se supone que el controlador de combinación se encarga de ello (debido a la directiva en el .gitattributesarchivo en su directorio).

Defina un keepMine.shen cualquier lugar de su %PATH%(o $PATHpara nuestro amigo de Unix. Yo hago ambas cosas, por supuesto: tengo una sesión de Ubuntu en una sesión de VirtualBox)

Como se ha comentado por lrkwz , y se describe en el " Combinar estrategias sección" de Personalización de Git - Git atributos , puede reemplazar el script de shell con el comando shell true.

git config merge.keepMine.driver true

Pero en el caso general, puede definir un archivo de script:

keepMine.sh

# I want to keep MY version when there is a conflict
# Nothing to do: %A (the second parameter) already contains my version
# Just indicate the merge has been successfully "resolved" with the exit status
exit 0

(que era un conductor de combinación sencilla;) (Incluso más sencillo en ese caso, el uso true)
(Si quería mantener la otra versión, sólo tiene que añadir antes de la exit 0línea:
cp -f $3 $2.
Eso es todo Combinar conductor tendría tiendas de comida para mantener la versión procedente del otro. rama, anulando cualquier cambio local)

Ahora, volvamos a intentar la fusión desde el principio:

git reset --hard
HEAD is now at ec202aa prepare myBranch with .gitattributes merge strategy

git merge hisBranch
Auto-merging dirWithConflicts/a.txt
CONFLICT (content): Merge conflict in dirWithConflicts/a.txt
Auto-merging dirWithCopyMerge/b.txt
Automatic merge failed; fix conflicts and then commit the result.

La fusión falla ... solo para un.txt .
Edite un.txt y deje la línea de 'hisBranch', luego:

git add -A
git commit -m "resolve a.txt by accepting hisBranch version"
[myBranch 77bc81f] resolve a.txt by accepting hisBranch version

Comprobemos que b.txt se ha conservado durante esta fusión

type dirWithCopyMerge\b.txt
b
myLineForB

La última confirmación representa la fusión completa :

git show -v 77bc81f5e
commit 77bc81f5ed585f90fc1ca5e2e1ddef24a6913a1d
Merge: ec202aa 658c31c
git merge hisBranch
Already up-to-date.

(La línea que comienza con Merge prueba eso)


Considere que puede definir, combinar y / o sobrescribir el controlador de combinación, ya que Git lo hará:

  • examinar <dir>/.gitattributes(que está en el mismo directorio que la ruta en cuestión): prevalecerá sobre el otro .gitattributesen directorios
  • Luego examina .gitattributes(que está en el directorio principal), solo establecerá directivas si aún no lo ha hecho
  • Finalmente examina $GIT_DIR/info/attributes. Este archivo se utiliza para anular la configuración del árbol. Sobrescribirá las <dir>/.gitattributesdirectivas.

Por "combinar", me refiero a un controlador de combinación múltiple "agregado".
Nick Green intenta, en los comentarios , combinar los controladores de combinación: consulte " Combinar pom a través del controlador git de python ".
Sin embargo, como mencionó en su otra pregunta , solo funciona en caso de conflictos (modificación concurrente en ambas ramas).

VonC
fuente
1
¡Gracias por la respuesta detallada! Entiendo que no tiene sentido controlar los archivos de configuración de versiones, pero buscaba un ejemplo sencillo y motivador. De hecho, es la cuestión más amplia la que me interesa. Nunca había oído hablar de los controladores de combinación de git antes, así que gracias por iluminarme.
saffsd
6
El cp -f $3 $2probablemente debe ser citado, es decir cp -f "$3" "$2".
Arco
1
@VonC ¡gracias por la respuesta detallada! El problema que tengo es que depende de que la gente configure el controlador en su archivo .git / config. Me gustaría agregar la información del controlador al proyecto en sí, por lo que sería automático y menos trabajo de configuración por hacer. ¿Algún consejo?
Juan Delgado
2
@ulmangt: también puede almacenar ese script en el repositorio de git, ... siempre que encuentre una manera de agregar su directorio principal al PATH(Unix o Windows PATH). Dado que ese script se interpretará a través del shell bash de Unix, o mediante el shell de Windows MsysGit bash MingWin, será portátil.
VonC
5
@VonC Gracias. Un problema más. En determinadas circunstancias (si no ha habido ningún cambio en la rama local en la que se está fusionando), parece que ni siquiera se llama al controlador de combinación, lo que provoca que se modifiquen los archivos locales (que se supone que utilizan el controlador de combinación personalizado para evitar que cambien durante una fusión). ¿Hay alguna forma de obligar a git a usar siempre el controlador de combinación?
ulmangt
1

Como ha comentado @ ciro-santilli, la forma sencilla de hacerlo es utilizarlo .gitattributescon la configuración:

path/to/file merge=ours

y habilite esta estrategia con:

git config --global merge.ours.driver true

(Estoy agregando esto como una respuesta para hacerlo más visible, pero lo convertiré en un Wiki de la comunidad para no tratar de obtener créditos de usuario por encima de mí. ¡Por favor, vote a favor de su comentario debajo de la Q aquí para felicitarlo!)

Greg Dubicki
fuente
(Aparte: si alguien da la respuesta como comentario y no agrega una respuesta, está perfectamente bien escribir una respuesta que no sea de CW y obtener los créditos. Si todavía es un miembro activo, puede hacer ping para agregar una respuesta si desea, pero técnicamente ya tuvieron su oportunidad :-)).
halfer
0

Tenemos varios archivos de configuración que nunca queremos que se sobrescriban. Sin embargo .gitignore y .gitattributes no funcionaron en nuestra situación. Nuestra solución fue almacenar los archivos de configuración en una rama de configuración. Luego, permita que los archivos se cambien durante la fusión de git, pero inmediatamente después de la fusión use la "rama de verificación de git -". para copiar nuestros archivos de configuración de la rama de configuración después de cada combinación. Respuesta detallada de stackoverflow aquí

HamletHub
fuente