Git checkout a commit 2 commits before hash

Tengo que revertir un cambio realizado en un file, en muchas twigs, en muchos repositorys. Sé que puedo usar git checkout hash filename y luego presiono ese cambio.

El problema es que solo sé que tiene dos confirmaciones antes de la confirmación real a la que deseo volver.

¿Cómo puedo preguntar a git cuál es el hash de dos commits antes de que esto tenga y usarlo para revertir correctamente?

Solutions Collecting From Web of "Git checkout a commit 2 commits before hash"

No estoy seguro de si necesita mirar hacia atrás o hacia adelante a partir de un compromiso particular.

La key de todo esto, sin embargo, es reconocer que a Git no le importan demasiado los nombres de las sucursales. Lo que le importa a Git es el gráfico de compromiso . Cada vez que se trata de este tipo de problema, debe dibujar el gráfico .

Aquí hay algunos charts de muestra. Los nodos networkingondos representan compromisos. El * es el compromiso cuya ID tiene en sus manos, y yo di "potencialmente interesante" y confiere nombres de una letra:

 ...--A--o--*--o--B--...--o <-- branchname ^ ^ 2 before 2 after 

Este gráfico es bastante fácil. Commit A tiene dos antes del commit * , por lo que si tiene el hash de * -let es 1234567 puede escribir 1234567~2 . El ~2 significa "regresar dos primeros padres" (ver más abajo). Encontrar el compromiso B es más difícil (ver más abajo).

Pero podríamos tener este lío:

  o--D--o <-- branch1 / / E--o <-- branch2 A--o / / \ \ / / \ ...--B--o--*--o--F-...-o <-- branch3 \ / o--C----o 

Aquí, los commits A y B son dos pasos antes de * , mientras que todos los C , D , E y F son dos pasos posteriores .

Es mucho más fácil, en Git, encontrar compromisos que están "antes", porque las flechas internas de Git apuntan hacia "atrás". Los nombres de las twigs, que están a la derecha y, por lo tanto, apuntan a la última confirmación en la twig: de hecho, cómo funcionan las twigs en Git: el nombre apunta a la confirmación de punta , y la punta confirma los puntos hacia atrás (hacia la izquierda en estos charts ) a commit (s) anterior (s): simplemente haz que Git comience en términos de encontrar commits.

Dado un compromiso, Git sigue la (s) flecha (s) hacia atrás al padre (s) de ese compromiso. Aquí * es un compromiso de fusión, juntando las líneas A--o y B--o .

El order de los dos padres de * no es algo que se muestra en el gráfico en sí, pero digamos que la línea A se encuentra a través del segundo padre. Digamos que * es nuevamente 1234567 . Entonces 1234567~2 es commit B (not A ) porque ~ sigue solo a los primeros padres. Para search A , puede escribir 1234567^2 (que nombra la parte superior o antes * ) y luego agregar ^1 o ~1 para volver a uno más padre a A

En otras palabras, los dos commits ahora son 1234567~2 y 1234567^2~1 . (Hay más forms de escribir esto.) Pero lo más fácil es ejecutar git log --graph --oneline 1234567 y observar el gráfico – Git lo dibujará verticalmente, pero verá dos líneas que salen de * , que lo harán te permite encontrar tanto A como B

Si hay más de dos forms de llegar a más de dos commits que son "dos pasos atrás", lo verá en el gráfico, como lo hacemos con los commits que son "dos pasos hacia adelante". Pero … encontrar los compromisos que son "dos pasos hacia adelante" es notablemente más difícil.

Una vez más, Git comienza desde la última (más a la derecha en nuestros dibujos). La punta se compromete y funciona al revés. Dibujé tres cosas que eventualmente se fusionan en la punta de branch3 , 1 pero tenga en count que commit D que también es dos pasos hacia adelante, solo se puede encontrar buscando hacia atrás desde branch1 .

No hay "búsqueda hacia adelante"

Git no tiene forma de "search hacia adelante" o "avanzar" de una confirmación. Todas las operaciones que avanzan (¡hay una!) Realmente funcionan retrocediendo. Básicamente, comenzamos desde todos los puntos de inicio posibles , es decir, todos los nombres / sugerencias de twigs. 2 Luego tenemos a Git en la list de todo lo que se remonta al compromiso que queremos, imprimiendo las ID hash de esos commits si y solo si son descendientes 3 de la confirmación que estamos viendo.

El command que imprime todos estos ID es git rev-list (que en realidad es solo git log , se le dice que solo imprima los identificadores hash de los commits en lugar de registrar los commits). El indicador que dice "give me --ancestry-path " es --ancestry-path , y debe combinarse con varias piezas más de información. En particular, debemos decirle a Git qué commit es el "interesante", y lo hacemos con el ^ prefijo (no sufijo esta vez):

 git rev-list --ancestry-path ^1234567 --branches 

El ^1234567 le dice a Git qué commit detiene el recorrido del gráfico y limita el set de revisiones impresas a aquellas que son descendientes de 1234567 . The- --branches le dice a Git, como de costumbre, dónde comenzar la búsqueda: desde todos los consejos de las sucursales. (También podemos agregar --tags , o use --all para decir todas las references: todas las twigs, todas las tags, el alijo si hay uno, y cualquier otra cosa que pueda existir. Sin embargo, --branches , o --branches --tags , es probablemente lo que quieres aquí).

Ahora, el problema con git rev-list es que simplemente dertwig todos los ID de revisión. Eso incluye no solo comprometer C , D , E y F , sino también todos los commits "poco interesantes" que vienen después del commit * . Existen numerosas soluciones posibles para esto, pero quizás la más simple es volver a usar git log --graph --oneline : eso, en lugar de imprimir solo el hash (completo), imprimir (abreviado) hashes y confirmar posts, más dibuja el gráfico. Ahora puede mirar el gráfico para encontrar compromisos que son "dos por delante".

(Por supuesto, dado que git log y git rev-list son básicamente el mismo command, también puede ejecutar git rev-list --graph . No hay necesidad de --oneline puesto que el resultado es una ID de confirmación por línea de todos modos. git rev-list es un command de fontanería , destinado a ser utilizado en scripts, y no es muy fácil de usar, mientras que git log está diseñado para interactuar con los usuarios e intenta ser más útil de inmediato.


1 Esta fusión de tres padres es una cosa llamada "fusión de pulpo" y es poco común en la práctica. Más típicamente tendrías dos fusiones, una que lleva a branch2 a branch3 , y a otra (anterior o posterior) que lleva la línea de commits de la branch3 línea sin nombre a branch3 .

2 También es posible que deseemos comenzar con las tags. Por ejemplo, considere este fragment de gráfico:

 ...--o--*--o--o <-- branch \ o--o <-- tag: v0.98alpha 

Aquí, la versión 0.98alpha se consideró "mala", y los dos commits alcanzables desde la label ya no están en ninguna twig. Pero si resulta que uno de esos dos commits tiene algunos datos o códigos preciosos, aún puede encontrarlo, comenzando desde la label y, si es necesario, trabajando hacia atrás hasta la confirmación anterior. La confirmación de dos pasos atrás aquí, marcada con * , también se encuentra en la twig de branch , por lo que la encontrará incluso sin iniciar desde la label v0.98alpha .

3 En estos charts dirigidos que usa Git, un antepasado es cualquier compromiso al que se puede llegar comenzando desde un compromiso y trabajando hacia atrás: el compromiso en sí, cualquiera de sus padres, cualquiera de los padres de sus padres, cualquiera de sus abuelos, etc. . Este es el mismo significado que "ancestros" para ti, excepto que en Git, eres considerado tu propio antepasado.

Debido a que las flechas de Git apuntan hacia atrás, es fácil encontrar antepasados.

El término "descendientes" funciona de la misma manera: usted es su propio descendiente, pero sus hijos, nietos y bisnietos también son sus descendientes, tal como cabría esperar. Git no puede encontrar a estos nietos directamente, pero con un par de confirmaciones, hay una testing muy fácil para "es commit Y un descendiente de commit X ": simplemente invertimos la testing. Para que Y sea ​​un descendiente de X , X debe ser un ancestro de Y. Dado que Git puede responder la pregunta "es ancestro", invertimos la testing y obtenemos nuestra respuesta.

4 Hay algunas diferencias significativas entre git log y git rev-list , por lo que no son exactamente el mismo command. Pero se construyen a partir del mismo código fuente y pueden hacer exactamente lo mismo, dependiendo de las opciones. Simplemente configuran diferentes opciones de forma pnetworkingeterminada: el log imprime cosas legibles por el ser humano, utiliza un buscapersonas, usa salida de color, y más; mientras rev-list imprime cosas legibles por máquina y no usa un buscapersonas. Las banderas necesarias para hacer que uno actúe como el otro no siempre son obvias, tampoco.

Use git log para ver las modificaciones realizadas en el file:

 git log myfile 

Cuando descubrió el hash de confirmación al que desea volver, use

 git checkout thehash myfile