¿Cuáles son las razones y los casos que causan conflictos de fusión de git?

  1. Qué son

    • las condiciones necesarias y suficientes, y / o

    • todos los casos o algunos casos comunes

    que puede causar un conflicto de git merge informe de combinación de git merge ?

    ¿Cómo se git merge determinar si una línea o algunas líneas contienen conflictos de fusión?

  2. Por ejemplo, a veces veo casos como el siguiente, donde la Part 1 o la Part 2 están vacías en

     <<<<<<< HEAD (Part 1) ======= (Part 2) >>>>>>> some-other-branch 

    Parece poco probable que tenga un conflicto de fusión para mí. Entonces, ¿cuáles son algunas de las posibles razones por las que casos como ese tienen conflictos de fusión?

  3. Comparando los conflictos de fusión reportados por git merge y las diferencias reportadas por git diff , ¿es correcto que

    • las diferencias informadas por git diff pueden no estar necesariamente en los lugares de los conflictos de fusión informados por git merge , y

    • los conflictos de fusión reportados por git merge podrían no estar necesariamente en los lugares de las diferencias reportadas por git diff ?

Gracias.

Hay múltiples partes para una respuesta completa y correcta. Primero, tenemos que llegar a una fusión tripartita normal en primer lugar (que en Git requiere usar -s recursive o -s resolve , y si se usa -s recursive , encontrar una única base de fusión y otras dos confirmaciones). Sin embargo, puede omitir hasta la tercera sección.

Los elementos necesarios para una fusión tripartita normal

Para hacer una fusión , necesitas:

  • una base de combinación commit B , 1
  • un compromiso actual L (lado izquierdo, también conocido como HEAD ) que tiene B como antepasado,
  • otro cometer R que también tiene a B como antepasado.

Dado que "es ancestro" permite la igualdad de nodos en el gráfico de confirmación (técnicamente es una comparación de pnetworkingecesor o igual,), es posible tener B = L y / o B = R. Si este es el caso, sin embargo, nunca se requiere fusión, y si se fuerza una combinación (usando git merge --no-ff – esto implica B = L y L ≺ R ; los dos casos no forzados son "avance rápido" "no-en-realidad-una-fusión y un error de" nada para fusionar ") no habrá conflictos de fusión. Así que también podemos suponer que la base de fusión precede a ambos lados de la combinación.


1 Usando --allow-unrelated-histories , puede hacer que Git sustituya en el tree vacío una confirmación básica real, si L y R no tienen los nodos ancestrales comunes más bajos. Esto, sin embargo, hace que todos los files identificados sean conflictos de adición / adición.


Dada una combinación, los elementos necesarios para un conflicto

A continuación, para get un conflicto en alguna ruta de file, necesita lo que yo llamo un conflicto de "alto nivel" o un conflicto de "bajo nivel" (o ambos). Para que esto ocurra, Git debe identificar (es decir, unir) los files en B , L y R. Debido a la capacidad de agregar nuevos files, cambiar el nombre de los files y eliminarlos, no es necesario que el file tenga el mismo nombre en las tres confirmaciones. En particular:

  • Si la ruta P existe en los tres de B , L y R , los tres files con la ruta P se identifican juntos. (Es decir, hay una ruta P B , como path/to/foo.txt , que tiene una path/to/foo.txt P L correspondiente path/to/foo.txt y una path/to/foo.txt P R path/to/foo.txt . Obviamente este file es "el mismo file "a lo largo de la fusión, por lo que Git identifica las tres routes como un solo file").
  • O bien, puede haber hasta tres routes diferentes, P B path/to/basename , P L path2/to2/left y P R path3/to3/right . Uno o más de estos pueden ni siquiera existir. Esto da como resultado: agregar / agregar conflicto, si ∄ P B y P L = P R ; un conflicto de renombrar / eliminar, si P B es igual a una de las routes izquierda o derecha, pero la otra no existe; o un cambio de nombre / cambio de nombre, si P B ≠ P L ≠ P R.

Si aún no hay un conflicto de alto nivel (agregar / agregar, renombrar / renombrar, o renombrar / eliminar), aún puede haber un cambio de nombre / modificar o renombrar / eliminar conflicto, siempre y cuando los ID de hash de los blobs (contenido del file ) nombrados por los dos o tres nombres de ruta no coinciden.

Todos estos conflictos de "alto nivel" provocan un conflicto de fusión, pero no dan lugar a marcadores de conflicto por sí solos. Para get eso, ahora debemos fusionar el file base con los dos files secundarios (lo que significa que los tres files deben existir, o para el caso agregar / agregar, tomamos un file vacío trivial como la versión base).

Esta combinación puede tener o no sus propios conflictos. Si la fusión agrega conflictos de bajo nivel, obtendremos marcadores de conflicto. Si no hay un conflicto de alto nivel (la ruta es la misma en las tres confirmaciones) pero se requiere una combinación completa (los valores hash son todos diferentes), podemos get un conflicto de bajo nivel con los marcadores de conflicto.

Qué requiere Git para un conflicto dentro de un file

Para get un conflicto de combinación dentro de un file en el tree de trabajo, Git debe ver la misma línea modificada por las versiones del lado izquierdo y derecho, pero cambiada de diferentes maneras . (Recuerde, los tres hashes deben diferir, por lo que Git está combinando un set de cambios de, en efecto, diff P B P L con los de diff P B P R )

Los casos más obvios y less confusos

El caso más obvio se produce para un lado (vamos a elegir el lado izquierdo primero) para decir:

  unchanged context -changed line +replacement 1 more unchanged context 

y el otro para decir:

  unchanged context -changed line +replacement 2 more unchanged context 

Aquí se eliminan una o más líneas y, en su lugar, se insertan una o más líneas, pero las líneas insertadas no coinciden. En este caso, el estilo de conflicto de merge presenta esto como:

 unchanged context <<<<<<< left-label replacement 1 ======= replacement 2 >>>>>>> right-label more unchanged context 

El estilo de context diff3 presenta esto en su lugar como:

 unchanged context <<<<<<< left-label replacement 1 

| merged common ancestors changed line ======= replacement 2 >>>>>>> right-label more unchanged context

Si simplemente agregamos text, pero agregamos text diferente , en la misma línea, también obtenemos un conflicto de fusión. Nuevamente, el text agregado de cada lado aparece en los marcadores de conflicto. (Si agregamos el mismo text a ambos lados, ya sea como text nuevo o como text de reemploop para una línea modificada, Git toma una copy de esta adición y no hay conflicto).

Los casos algo confusos

Si una, pero no ambas, las líneas de reemploop están vacías, es decir, si se lee el lado izquierdo o derecho de la diferencia:

  unchanged context -changed line more unchanged context 

luego falta una, pero no ambas líneas de reemploop en el diff3 marcado merge o diff3 . (Si ambas diferencias simplemente eliminan las líneas originales, no hay conflicto: Git toma una eliminación).

De manera similar, si un lado agrega una línea arriba o debajo de una línea que el otro lado elimina, usted obtiene un conflicto. Esta vez, el conflicto muestra que el lado que retenía y luego agregaba tiene todas las líneas y el otro lado no tiene líneas. Por ejemplo:

  some merge conflict. Line that will conflict. +add line below it Rest of the 

vs:

  some merge conflict. -Line that will conflict. Rest of the 

(Y lo mismo ocurre si agrega la línea de arriba en lugar de debajo).

Aquí es donde el estilo de conflicto diff3 es muy útil. Aquí está el file completo fusionado para uno de estos casos:

 We need a base file in which to make some merge conflict. <<<<<<< HEAD 

| merged common ancestors Line that will conflict. ======= Change the line that will conflict. >>>>>>> b2 Rest of the base file for the merge conflict example.

Tenga en count que ahora es obvio, o al less, less misterioso , que había una línea que decía Line that will conflict. en la versión base, que eliminé por completo de la versión HEAD lado izquierdo y la remplacé con una línea diferente en la versión del lado derecho b2 .