A veces es efectivamente imposible (con algunas excepciones de dónde podría tener la suerte de tener datos adicionales) y las soluciones aquí no funcionarán.
Git no conserva el historial de referencia (que incluye ramas). Solo almacena la posición actual de cada rama (la cabeza). Esto significa que puede perder parte del historial de sucursales en git con el tiempo. Cada vez que se bifurca, por ejemplo, se pierde de inmediato qué rama era la original. Todo lo que hace una rama es:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Puede suponer que el primero comprometido es la rama. Este suele ser el caso, pero no siempre es así. No hay nada que le impida comprometerse con cualquiera de las ramas primero después de la operación anterior. Además, no se garantiza que las marcas de tiempo de git sean confiables. No es hasta que te comprometes con ambos que realmente se convierten en ramas estructuralmente.
Mientras que en los diagramas tendemos a numerar los commits conceptualmente, git no tiene un concepto estable real de secuencia cuando el commit se ramifica. En este caso, puede suponer que los números (que indican el orden) están determinados por la marca de tiempo (puede ser divertido ver cómo una interfaz de usuario de git maneja las cosas cuando establece todas las marcas de tiempo en el mismo).
Esto es lo que un humano espera conceptualmente:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Esto es lo que realmente obtienes:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Asumirías que B1 es la rama original, pero podría ser simplemente una rama muerta (alguien realizó el pago -b pero nunca se comprometió). No es hasta que te comprometes con ambos que obtienes una estructura de rama legítima dentro de git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Siempre sabe que C1 vino antes que C2 y C3, pero nunca sabe de manera confiable si C2 vino antes que C3 o C3 vino antes que C2 (porque puede configurar la hora en su estación de trabajo a cualquier cosa, por ejemplo). B1 y B2 también son engañosos ya que no puedes saber qué rama vino primero. Puede hacer una suposición muy buena y generalmente precisa en muchos casos. Es un poco como una pista de carreras. En general, en igualdad de condiciones con los autos, puede suponerse que un auto que viene en una vuelta atrás comenzó una vuelta atrás. También tenemos convenciones que son muy confiables, por ejemplo, master casi siempre representará las ramas más longevas, aunque lamentablemente he visto casos en los que incluso este no es el caso.
El ejemplo dado aquí es un ejemplo de preservación de la historia:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
Real aquí también es engañoso porque nosotros, como humanos, lo leemos de izquierda a derecha, de raíz a hoja (ref). Git no hace eso. Donde hacemos (A-> B) en nuestras cabezas git hace (A <-B o B-> A). Lo lee de ref a root. Las referencias pueden estar en cualquier lugar, pero tienden a ser hojas, al menos para ramas activas. Una referencia apunta a un compromiso y los compromisos solo contienen un me gusta para sus padres, no para sus hijos. Cuando un commit es un commit de fusión, tendrá más de un padre. El primer padre es siempre el commit original en el que se fusionó. Los otros padres siempre son commits que se fusionaron en el commit original.
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
Esta no es una representación muy eficiente, sino una expresión de todos los caminos que git puede tomar de cada referencia (B1 y B2).
El almacenamiento interno de Git se parece más a esto (no es que A como padre aparezca dos veces):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Si vuelcas un commit de git sin procesar, verás cero o más campos principales. Si hay cero, significa que no hay padre y la confirmación es una raíz (en realidad puede tener múltiples raíces). Si hay uno, significa que no hubo fusión y no es una confirmación de raíz. Si hay más de uno, significa que el commit es el resultado de una fusión y todos los padres después del primero son commits de fusión.
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
Cuando ambos golpeen A, su cadena será la misma, antes de eso su cadena será completamente diferente. La primera confirmación que otras dos confirmaciones tienen en común es el antepasado común y de dónde divergieron. Puede haber alguna confusión aquí entre los términos commit, branch y ref. De hecho, puede fusionar un commit. Esto es lo que realmente hace la fusión. Una referencia simplemente apunta a una confirmación y una rama no es más que una referencia en la carpeta .git / refs / heads, la ubicación de la carpeta es lo que determina que una referencia es una rama en lugar de algo más como una etiqueta.
Donde pierde la historia es que la fusión hará una de dos cosas dependiendo de las circunstancias.
Considerar:
/ - B (B1)
- A
\ - C (B2)
En este caso, una combinación en cualquier dirección creará un nuevo commit con el primer padre como el commit señalado por la rama desprotegida actual y el segundo padre como el commit en la punta de la rama que fusionó en su rama actual. Tiene que crear una nueva confirmación ya que ambas ramas tienen cambios desde su antepasado común que deben combinarse.
/ - B - D (B1)
- A /
\ --- C (B2)
En este punto, D (B1) ahora tiene ambos conjuntos de cambios de ambas ramas (en sí y B2). Sin embargo, la segunda rama no tiene los cambios de B1. Si combina los cambios de B1 a B2 para que estén sincronizados, entonces puede esperar algo que se vea así (puede forzar git merge para que lo haga así sin embargo con --no-ff):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Obtendrá eso incluso si B1 tiene confirmaciones adicionales. Mientras no haya cambios en B2 que B1 no tenga, las dos ramas se fusionarán. Hace un avance rápido que es como un rebase (los rebases también comen o linealizan el historial), excepto que, a diferencia de un rebase, ya que solo una rama tiene un conjunto de cambios, no tiene que aplicar un conjunto de cambios de una rama encima de otra.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Si deja de trabajar en B1, las cosas están muy bien para preservar la historia a largo plazo. Solo B1 (que podría ser maestro) avanzará normalmente, por lo que la ubicación de B2 en el historial de B2 representa con éxito el punto en que se fusionó con B1. Esto es lo que git espera que hagas, para bifurcar B de A, luego puedes fusionar A en B tanto como quieras a medida que se acumulan los cambios, sin embargo, al fusionar B de nuevo en A, no se espera que trabajes en B y más . Si continúa trabajando en su sucursal después de avanzar rápidamente fusionándola nuevamente en la sucursal en la que estaba trabajando, borre el historial anterior de B cada vez. Realmente estás creando una nueva bifurcación cada vez después de un avance rápido, compromete a la fuente y luego confirma a la bifurcación.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
1 a 3 y 5 a 8 son ramas estructurales que aparecen si sigue el historial de 4 o 9. No hay forma en git de saber a cuál de estas ramas estructurales sin nombre y sin referencia pertenecen las ramas con nombre y de referencia como Fin de la estructura. Puede suponer de este dibujo que 0 a 4 pertenece a B1 y 4 a 9 pertenece a B2, pero aparte de 4 y 9 no se sabe qué rama pertenece a qué rama, simplemente lo he dibujado de una manera que da ilusión de eso. 0 podría pertenecer a B2 y 5 podrían pertenecer a B1. Hay 16 posibilidades diferentes en este caso de qué rama nombrada podría pertenecer cada una de las ramas estructurales.
Hay una serie de estrategias git que funcionan alrededor de esto. Puede forzar git merge para que nunca avance rápido y siempre cree una rama de fusión. Una forma horrible de preservar el historial de sucursales es con etiquetas y / o ramas (se recomiendan realmente las etiquetas) de acuerdo con alguna convención de su elección. Realmente no recomendaría una confirmación vacía ficticia en la rama en la que se está fusionando. Una convención muy común es no fusionarse en una rama de integración hasta que realmente desee cerrar su rama. Esta es una práctica a la que las personas deberían intentar adherirse, ya que de lo contrario estás trabajando en el punto de tener sucursales. Sin embargo, en el mundo real, el ideal no siempre es práctico, lo que significa que hacer lo correcto no es viable para cada situación. Si lo que tu