git vuelve al estado de un file antes del conflicto

Tuve el conflicto de fusión mientras tiraba de otra sucursal y usé "Usar el mío" en lugar de "Usar el suyo" por error para un solo file. los otros files en conflicto fueron tomados con cuidado.

Ahora quiero pasar al estado anterior de este file (antes del conflicto) y volver a extraer de la twig para crear el conflicto y seleccionar la opción correcta.

He usado checkout git checkout "commit #" PathToFile que correctamente recupera el file al estado en el que estaba (antes del conflicto), pero ahora cuando lo hago desde otra twig el conflicto no está allí.

Hay una manera de hacer esto para un solo file, sin utilizar los aspectos de Git "máquina del time" del control de versiones. Pero también podría usar la versión completa, ya que funciona para más casos.

Primero, restring que git pull solo significa: "ejecuta git fetch (con algunos arguments), luego si eso tiene éxito, ejecuta git merge (con algunos otros arguments), o tal vez git rebase (con algunos arguments)". Parece que no le has dicho que use git rebase lugar de git merge , así que git rebase que quieres y estás usando el comportamiento de fusión.

Tenga en count que está usando git merge , y para repetir la fusión correctamente ahora, querrá ejecutar git merge , no deje que git pull haga. Entonces, debes saber cómo ejecutar git merge y qué hace. La parte de "necesito saber qué es lo que hace" es verdadera incluso si dejas que git pull ejecute git merge ". Para saber qué es lo que hace Git git merge , también necesitas saber qué hace git fetch . Encuentro que las personas nuevas en Git realmente lo hacen mejor si ejecutan git fetch y git merge (o git rebase ) por separado: en realidad se lleva algo del misterio y la confusión.

Lo que git fetch hace

Tu Git funciona en tu repository, que tiene tus compromisos, recordados (apuntados) por tus twigs. Tu Git también habla con un segundo Git; ese otro Git tiene su propio repository, con sus propios commits y twigs. En varias ocasiones, necesitas hacer que tu Git llame a su Git y llevar a cabo sus commits, específicamente, cualquier commit que tengan, que no haces.

Para ello, ejecute git fetch y asígnele el nombre de un control remoto , probablemente origin . Si solo tienes una remota, la mayoría de la gente lo hace, generalmente se llama origin , y tu Git puede fácilmente descifrar a cuál search, ya que de todos modos solo hay uno. Pero vamos a escribirlo:

 git fetch origin 

Utiliza la URL guardada, un "control remoto" guarda una URL, para llamar al otro Git y get sus commits y branches y traerlos a su repository. También cambia el nombre de todas sus twigs, de modo que su master convierta en su origin/master , su develop convierta en su origin/develop , y así sucesivamente. Una vez que tienes sus commits, se vuelven "tuyos" también (no en términos de "autor" o "committer", pero en eso ahora los tienes , en tu repository), así que ahora puedes trabajar con ellos.

Dibuja un gráfico de confirmaciones

Antes de llegar al paso de fusión, es importante tener alguna forma de visualizar lo que está sucediendo.

Si usas una GUI como gitk o gitg o una de las varias webs de lujo, muchas de ellas dibujarán charts. Incluso en la línea de command, puede ejecutar git log --graph --oneline --decorate --all para get un crudo gráfico de sus commits.

Para StackOverflow, prefiero dibujar los commits de izquierda a derecha, en su mayoría, horizontalmente. Compromisos más nuevos están hacia la derecha. Dado que un nombre de twig como el master apunta a la confirmación de la twig más puntiaguda, puse los nombres de las twigs a la derecha también, con una flecha que apunta a la punta de la twig:

 o <- o <- o <-- master 

Cada compromiso, cada o , señala "hacia atrás" a su compromiso anterior (padre). Estas flechas hacia atrás son principalmente molestas y molestas, así que vamos a dibujar los commits con líneas de connection. Y, dibujemos una estructura de bifurcación, en la que tenga sus propios compromisos, y su git fetch haya generado otras confirmaciones. Ahora tenemos esto:

 o--o--o <-- master \ o--o <-- origin/master 

Nuevamente, los commits más nuevos están hacia la derecha, por lo que la punta del master es el último commit en la primera línea, y la punta del origin/master es el último commit en la segunda línea de commits.

Cada confirmación tiene un único "nombre verdadero", que es la gran identificación de hash feo como 17fac9e... o ea6631f... Para poder hablar sobre ellos de manera más conveniente, vamos con nombres o símbolos de una sola letra en su lugar, y networkingibujamos lo anterior como:

 o--*--A <-- master \ o--B <-- origin/master 

Los nodos networkingondos son compromisos "aburridos", que no necesitamos mencionar nuevamente. Commit * es especial porque es donde el master y el origin/master unen, si trabajas al revés desde ambos. (Si trabajas hacia adelante, es donde se separan. Git siempre funciona al revés, por lo que será más fácil si lo haces también). Confirmar A es especial porque es el compromiso de punta de master , y el compromiso B es especial porque es la punta del origin/master .

También recordemos aquí que cada confirmación tiene una instantánea completa de todos sus files (hecha a partir de "el índice", que es la razón por la cual debe git add files al índice antes de comprometerse).

Lo que hace git merge

Cuando estás en tu master y ejecutas git merge origin/master , Git encuentra dos commits: la punta de tu twig actual y la punta de tu nombre. Aquí estos son A y B Entonces, Git encuentra la base de fusión , que es el primer lugar donde estas dos twigs vuelven a estar juntas. Esa es nuestra comisión * , y esta es la razón por la que la marcamos.

La operación de fusión ejecuta luego dos commands de git diff :

 git diff <id-of-*> <id-of-A> 

y:

 git diff <id-of-*> <id-of-B> 

La primera diferencia representa "lo que hemos cambiado", y la segunda diferencia representa "lo que han cambiado". Como con todas las git diff , debes preguntar: "¿cambiado con respecto a qué ?" La respuesta es: con respecto a la fusión base-commit * .

Git luego intenta combinar estos dos sets de cambios. Si tiene éxito por sí solo, supone que lo hizo todo bien, lo que no siempre es cierto, pero es cierto sorprendentemente a menudo: "sorprendentemente" porque Git no tiene idea sobre el código, simplemente usa reglas simples de text. Luego comete el resultado.

Si Git falla, hace que termines la fusión y luego comprometes el resultado.

(La fusión se lleva a cabo usando el índice y tu tree de trabajo, pero podemos ignorarlo aquí. Sin embargo, eso importa mucho cuando Git te hace terminar la fusión).

De cualquier manera, obtienes una nueva confirmación, así que vamos a agregar eso a nuestro gráfico. El nuevo compromiso es un compromiso de fusión , lo que significa que tiene dos padres en lugar de uno solo:

 o--o--A---M <-- master \ / o--B <-- origin/master 

Tenga en count que la antigua base de combinación es aburrida nuevamente, por lo que ya no tiene una estrella ( * ). La nueva fusión compromete M puntos a A y B Esto significa que commit B ahora está en branch master . También está todavía en la twig de seguimiento remoto denominada origin/master .

Dices que has estropeado la fusión M No dice si agregó más commits luego. Si lo hiciste, todo se vuelve un poco más difícil, porque probablemente quieras save esos commits. Si has aplicado estos commits (incluido el propio M ), se vuelve aún más difícil porque ahora probablemente deberías savelos para siempre. Pero por ahora, echemos un vistazo a lo que sucede si ejecutamos git pull nuevamente.

git pull = git fetch && git merge

Todavía tenemos esto cuando comenzamos:

 o--o--A---M <-- master \ / o--B <-- origin/master 

Ahora corremos git fetch . Esto encuentra cualquier cosa nueva en el origin , que probablemente sea nada. Por lo tanto, no trae nada y deja el origin/master apuntando a cometer B

Ahora ejecutamos git merge , diciéndole que combine origin/master , es decir, commit B , con nuestro commit actual , M Encuentra la base de fusión de M y B , que es la primera confirmación que está en ambas twigs.

Ese primer compromiso es … bueno, mira el gráfico y sigue M hacia atrás. ¿Es M ? No, porque el origin/master apunta a B , y no se te permite moverte hacia la derecha, solo hacia la izquierda. Tampoco puede ser A , por la misma razón. ¿Qué hay de B ? Eso sin duda es en origin/master , que apunta directamente a eso. Y, aha, eso también está en el master , ¡porque M apunta a B !

Entonces, la base de fusión es B , y si Git realizara una combinación, tendría dos diferencias:

 git diff BM # what we did git diff BB # what they did 

Obviamente, no hay diferencia de B a B , así que Git puede omitir todo esto: combinar "lo que hicimos" (cambiar B para hacer M ) y "lo que hicieron" (nada) nos deja con M Así que git merge no hace nada en absoluto.

¿Qué sucede si extraes el file anterior y luego lo comprometes? Bueno, ahora tienes:

 o--o--A---M--C <-- master \ / o--B <-- origin/master 

donde C tiene la versión del file extraída de A Ahora ejecutamos git merge origin/master . Git encuentra la base de combinación de C y B ¿Cuál es la base de fusión? Por qué, es solo B nuevo. No hay nada que fusionar, y Git no hace nada.

Cómo haces para que Git se re-combine

Para hacer que Git vuelva a hacer la fusión, debes hacer que el compromiso actual sea ​​commit A Para hacer eso, tienes varias opciones:

  • Puede hacer que un nuevo nombre de sucursal apunte a cometer A (Utilice git branch <newname> <id-of-A> y luego git checkout <newname> , o equivalentemente, git checkout -b <newname> <id-of-A> .)
  • Puede usar git reset para descartar confirmaciones M y C , de modo que el master apunte a A (Use git reset --hard , asegurándose de no tener ningún trabajo sin save).
  • Puedes completar el commit por tu count, obteniendo un "HEAD separado", donde HEAD apunta a A

Dibujemos la opción del medio, ya que es probablemente la que desea para este caso particular. No puedo "poner gris" el text, por lo que volveré a dibujar el gráfico más grande y simplemente labelré M y C como abandoned :

 o--o------A <-- master \ \ \ M--C [abandoned] \ / o--B <-- origin/master 

Como commit C ya no tiene un nombre que lo señale, ya no lo verá más. Como C fue el único path que nos permitió encontrar M , tampoco verá M más. Todavía podemos encontrar la confirmación A , por su nombre master , y B , por su nombre origin/master , así que vamos a dibujar esto más simplemente como:

 o--o--A <-- master \ o--B <-- origin/master 

¡Mira eso, hemos vuelto a lo que teníamos antes! Ahora solo podemos ejecutar git merge para volver a hacer la fusión. La base de combinación de A y B es el compromiso justo a la izquierda de A (el que yo había marcado * -puede ser el momento de poner el * nuevo). Git volverá a intentar la fusión y fallará de la misma manera con el mismo conflicto de fusión.

Cuándo hacer algo más complicado

Si presionó commit M y cualquier commit posterior, o de otra manera se los dio a otras personas, hará más trabajo para ellos "retractando" su fusión. En este caso, considere si esto está bien. Si es así, aún puedes hacerlo. De lo contrario, deje M (y cualquier confirmación posterior que también deba conservar), allí.

En este caso, lo que puede hacer es usar el método "HEAD separado" o crear una nueva twig que apunte a A :

 o--o------A <-- HEAD \ \ \ M--C <-- master \ / o--B <-- origin/master 

Ahora puedes git merge origin/master o git merge <id-of-B> para reintentar el commit de fusión, y resolverlo correctamente esta vez. Realiza una nueva confirmación y se agregará a HEAD como si fuera una twig regular (¡en su mayoría es, es solo una sin nombre!):

 o--o------A-----M2 <-- HEAD \ \ / \ M-/-C <-- master \ /_/ o--B <-- origin/master 

(El gráfico se vuelve desorderado y no hay forms excelentes de dibujarlo ahora). Esta nueva combinación M2 tiene el file fusionado correctamente, porque lo hiciste bien esta vez.

Ahora puedes download git checkout master nuevo y simplemente tomar el file correcto de Merge M2 (cuyo ID puedes save, y cortar y pegar, o puedes darle un nombre de twig por un rato, luego eliminar esa twig) .