Recuerde que un repositorio de Git está estructurado como un gráfico acíclico dirigido: cada confirmación de Git es una instantánea del árbol junto con referencias a sus confirmaciones principales (generalmente una principal, pero la confirmación inicial no tiene padres, mientras que una confirmación de fusión tiene dos o más padres). Por lo tanto, cada confirmación hace referencia recursivamente a un conjunto de confirmaciones de antepasados. Por lo general, es más útil pensar que un commit representa un parche, al tomar la diferencia entre los árboles principales del commit y el árbol del commit. De esta manera, uno piensa en el árbol de un commit como el resultado de aplicar todos los parches de su antepasado. El árbol de una fusión se compromete entre dos ramas, por lo tanto, puede considerarse como el resultado de aplicar todos los parches en la unión de los conjuntos de antepasados de las dos ramas.
Pero no es así como se implementa realmente git merge
, tanto porque sería terriblemente lento como porque requeriría volver a resolver todos los conflictos de fusión que alguna vez ocurrieron en el camino. Entonces, ¿qué está pasando realmente?
Me gusta pensar en esto matemáticamente: dado commits [math] A [/ math] y [math] B [/ math], el commit de fusión [math] A \ vee B [/ math] está representado por [math] [A \ vee B] = [A] + [B] – [C] [/ math], donde [math] C [/ math] es la base de fusión (ancestro común más reciente) de [math] A [/ math] y [matemáticas] B [/ matemáticas]. Tenemos que “restar” [matemática] [C] [/ matemática] porque de lo contrario ese contenido sería doblemente contado por [matemática] [A] [/ matemática] y [matemática] [B] [/ matemática]. Esta operación [matemática] x + y – w [/ matemática] se denomina fusión tripartita . Puede pensar en esto como aplicar el parche [matemático] y – w [/ matemático] a [matemático] x [/ matemático], o como aplicar el parche [matemático] x – w [/ matemático] a [matemático] y [ /mates].
- Cómo inicializar una matriz de cadenas en una clase
- ¿Cómo visualizo el laberinto que estoy creando?
- ¿Cuál es el mejor algoritmo de compresión de imágenes y cuál es el algoritmo de compresión de Facebook?
- Cómo usar un algoritmo rápido para la detección y el seguimiento del objeto anómalo
- ¿Qué algoritmos de programación de procesos usa Android?
De hecho, esta operación no se implementa literalmente con diff y patch, sino con el algoritmo utilizado para construirlos: el algoritmo de subsecuencia común más largo. La diferencia [matemática] x – w [/ matemática] entre dos secuencias [matemática] x, w [/ matemática] de líneas es justo lo que obtenemos cuando los alineamos a lo largo de su subsecuencia común más larga (y tal vez desechemos los largos que son comunes a ambas secuencias). Para construir la combinación tripartita [matemática] x + y – w [/ matemática], alineamos [matemática] x [/ matemática] y [matemática] w [/ matemática] a lo largo de su subsecuencia común más larga, alineamos [matemática] y [/ math] y [math] w [/ math] a lo largo de su subsecuencia común más larga, y luego genera cada línea que
- común a las tres secuencias, o
- presente en [matemáticas] x [/ matemáticas] pero ausente en [matemáticas] y [/ matemáticas] y [matemáticas] w [/ matemáticas], o
- presente en [matemáticas] y [/ matemáticas] pero ausente en [matemáticas] x [/ matemáticas] y [matemáticas] w [/ matemáticas],
mientras eliminamos las líneas que son
- presente en [matemática] y [/ matemática] y [matemática] w [/ matemática] pero ausente en [matemática] x [/ matemática], o
- presente en [matemática] x [/ matemática] y [matemática] w [/ matemática] pero ausente en [matemática] y [/ matemática].
Aquí hay un ejemplo:
x: w: y: ↦ fusionado: leche leche leche leche jugo jugo harina harina harina harina salchicha salchicha huevos huevos huevos huevos mantequilla mantequilla
El orden de las líneas de [math] x [/ math], [math] y [/ math] y [math] w [/ math] solo puede imponer un orden parcial en las líneas de salida de la fusión de tres vías. Si es así, es porque el mismo bloque de [math] w [/ math] fue editado de dos maneras diferentes entre [math] x [/ math] y [math] y [/ math], así que declaramos que ese bloque es un fusionar conflicto y presentarlo al usuario para que lo resuelva manualmente.
Cuando git
muestra un conflicto de fusión, por defecto solo verá los bloques x
e y
del conflicto:
<<<<<<>>>>>> y
Sin embargo, los conflictos de combinación se vuelven mucho más fáciles de resolver cuando puede ver la base de combinación [math] w [/ math]. Recomiendo activar esto en ~/.gitconfig
ejecutando git config –global merge.conflictstyle diff3.
<<<<<<>>>>>> y
Ahora puede ver que esto resuelve:
Tomé dos huevos y tres salchichas para el desayuno.
(Tenga en cuenta que esta operación es simétrica wrt intercambiando [math] w [/ math] con el resultado, por lo que realmente necesita ver [math] w [/ math]).
Hay otros dos casos a considerar: puede haber líneas que son
- presente en [matemáticas] x [/ matemáticas] y [matemáticas] y [/ matemáticas] pero ausente en [matemáticas] w [/ matemáticas], o
- presente en [matemáticas] w [/ matemáticas] pero ausente en [matemáticas] x [/ matemáticas] y [matemáticas] y [/ matemáticas].
Algunos algoritmos de fusión de tres vías siempre etiquetarán líneas tales como conflictos de fusión. Sin embargo, Git generará o eliminará felizmente dichas líneas, respectivamente, siempre que las líneas circundantes no cambien. Este efecto se denomina fusión limpia accidental . En ocasiones es conveniente en la práctica, especialmente cuando el usuario arruinó y fusionó dos versiones diferentes del mismo parche entre sí. Pero creo que, en general, no es una buena idea ocultar tales errores y desear que este comportamiento se pueda desactivar. Intenta evitar aprovecharlo.
Si ha prestado mucha atención, es posible que haya descubierto un pequeño agujero en mi descripción anterior: dado que los commits [math] A [/ math] y [math] B [/ math] pueden contener commits de fusión, su más reciente ¡Un ancestro común podría no ser único! En general, puede haber muchos ancestros comunes más recientes [matemáticas] C_1, \ ldots, C_k [/ matemáticas]. En este caso, git merge
procede de manera recursiva : primero construye la fusión [math] C = C_1 \ vee \ ldots \ vee C_k [/ math], y la usa como base de fusión para la fusión de tres vías [math] [A] + [B] – [C] [/ matemáticas]. Esta es la razón por la cual la estrategia de fusión predeterminada de Git se llama recursive
.