2009-03-13 10 views
44

Hice un cambio en un script y lo comprometí. Luego hice algunos otros cambios, y los empujé a un repositorio remoto y tal.Deshacer el cambio en git (no reescribir el historial)

Entonces me di cuenta de que el primer cambio que mencioné fue estúpido, y quiero deshacerlo ... ¿Puedo "dejar de aplicar" esa confirmación, sin copiar/pegar manualmente el diff?

A modo de ejemplo: Tengo dos archivos, a.py y b.py:

Commit 1: 
I delete a function in a.py 

Commit 2: 
I change a few lines in b.py 

Commit 3: 
I change the docstring in a.py 

¿Puedo deshacer esa función de eliminación, y hacer que parezca como "comprometerse 4" (en lugar de eliminar comprometerse 1)

+13

¡Oi, está "realizado", no "realizado"! (No soy estadounidense ...) – dbr

+3

Tu ortografía fue corregida por personas que usan yardas y pies para medir cosas ... ja, ja – FaddishWorm

Respuesta

57

Sí, puede usar git revert para esto. Vea el git manual section on this para más información.

Lo esencial es que se puede decir:

git revert 4f4k2a 

Dónde 4f4k2a es el id de la de comprometerse desea deshacer, y que tratará de deshacer.

+0

Aha, por supuesto, ¡gracias! – dbr

+0

¡Por cierto! Se eliminará ese commit 1 objeto – aatifh

+0

Hm, no parece eliminar la confirmación revert'd .. – dbr

31

Sólo un comentario:

git revert aCommit 

qué revertir la todo commit (como en "todos los archivos parte del commit"):
se calcula un parche inversa, se aplica sobre la cabeza y cometer

Así que dos problemas (el primero es fácil de resolver):

  • que no siempre se cometen, por lo que puede añadir -no-commit opción: "git revert --no-commit aCommit": esto es útil cuando se restituyan más de uno compromete el efecto en su índice en una fila.
  • no se aplica a un archivo específico (¿qué ocurre si a.py era parte de una confirmación con otros 1000 cambios que puede que no desee revertir)?
    Por eso, si se desea extraer archivos específicos como lo fueron en otra confirmación, debería ver git-checkout, específicamente la sintaxis git checkout <commit> <filename> (que no es exactamente lo que necesita en este caso, sin embargo)

Easy Git (Elijah Newren) intentaron traer un "reverso completo" al Git Mailing list; pero sin mucho éxito:

Las personas ocasionalmente quieren "revertir los cambios".

Ahora, esto puede ser:

  • los cambios entre 32 y hace 29 revisiones,
  • que podría ser todos los cambios desde la última confirmación,
  • que podrían ser los cambios desde el 3 comete hace , o
  • podría ser solo una confirmación específica.
  • El usuario puede querer tal subconjunto reversiones a sólo archivos específicos,

(eg revert es documented here, pero no estoy seguro de que es parte de la distribución actual de, por ejemplo, aunque)

pero todo se reduce a "revertir cambios" al final.

eg revert --since HEAD~3 # Undo all changes since HEAD~3 
eg revert --in HEAD~8  # much like git revert HEAD~8, but nocommit by default 
eg revert --since HEAD foo.py # Undo changes to foo.py since last commit 
eg revert foo.py    # Same as above 
eg revert --in trial~7 bar.c baz. # Undo changes made in trial~7 to bar.[ch] 

Son estos tipos de "reversión de datos" realmente tan diferentes que no debería necesitar ser diferentes comandos, o que algunas de estas operaciones no deben ser apoyados por el simple comando de reversión?
Claro, la mayoría de los usuarios la mayoría de las veces probablemente utilizará el formulario "eg revert FILE1 FILE2..." , pero no vi el daño en el soporte de las capacidades adicionales.

Además ... ¿hay algo fundamental que evite que core git adopte tal comportamiento?

Elías

Nota: se compromete de forma predeterminada no tienen sentido para el comando generalizado revert, y "git revert REVISION" sería error a cabo con las instrucciones (que indica al usuario agregar la opción --en).


digamos que usted tiene, de 50 comprometidos, 20 archivos se da cuenta de que el viejo se comprometen X introdujo cambios que no debería haber tenido lugar.
Un poco de plomería está en orden.
Lo que se necesita es una manera de mostrar todos los archivos específicos que necesita volver
(como en "para cancelar los cambios realizados en comprometerse X, manteniendo todos los cambios posteriores"),
y después, para cada de ellos:

git-merge-file -p a.py X X^ 

la cuestión aquí es recuperar la función perdida sin eliminar todos los cambios posteriores en a.py es posible que desee mantener.
Esa técnica a veces se llama "fusión negativa".

Desde git merge-file <current-file> <base-file> <other-file>means:
incorpora todos los cambios que conducen desde el <base-file>-<other-file> en <current-file>, puede restaurar la función de borrado diciendo desea incorporar todos los cambios)

    .
  • desde: X (donde se ha eliminado la función)
  • to: X^(el compromiso anterior antes de que X, donde la función todavía estaba allí)

Nota: el '-p' argumento que le permite revisar primero los cambios sin hacer nada en el archivo actual. Cuando esté seguro, elimine esa opción.

Nota: la git merge-file es not that simple: no se puede hacer referencia a versiones anteriores del archivo así como así.
(que tendría una y otra vez el mensaje frustrante: error: Could not stat X)
Tienes que:

git cat-file blob a.py > tmp/ori # current file before any modification 
git cat-file blob HEAD~2:a.py > tmp/X # file with the function deleted 
git cat-file blob HEAD~3:a.py > tmp/F # file with the function which was still there 

git merge-file a.py tmp/X tmp/F # basically a RCS-style merge 
           # note the inversed commit order: X as based, then F 
           # that is why is is a "negative merge" 
diff -u a.py tmp/ori # eyeball the merge result 
git add a.py 
git commit -m "function restored" # and any other changes made from X are preserved! 

Si esto se va a hacer para un gran número de archivos dentro de una confirmación anterior ... algunos secuencias de comandos está en orden;)

+0

No creo que necesites el dash en: 'git checkout ' – Paul

5

de revertir los cambios a un solo archivo dentro de una confirmación, como VonC señaló, me checkout la rama (principal o tronco o lo que sea) y luego checkout la versión del archivo que quería revertir yt reat como un nuevo compromiso:

$ git checkout trunk 
$ git checkout 4f4k2a^ a.py 
$ git add a.py 
$ git diff    #verify I'm only changing what I want; edit as needed 
$ git commit -m 'recover function deleted from a.py in 4f4k2a' 

Probablemente hay un comando de fontanería que hacerlo directamente, pero yo no lo usaría si lo sabía. No es que no confíe en Git, pero no confío en mí mismo. No confiaría en lo que sabía sin mirar lo que había cambiado en ese archivo en ese compromiso y desde entonces. Y una vez que miro, es más fácil crear una nueva confirmación editando el diff. Quizás sea solo un estilo de trabajo personal.

+1

Dos comentarios: 1/si la función ha sido eliminado en la confirmación de 4f4k2a para el a.py, ¿no deberías retirar a.py del compromiso * anterior *? (el uno * antes * 4f4k2a, como en 4f4k2a ^?) 2/¿Eso no borrará completamente la versión actual de a.py? ¿Qué sucede si quiero mantener los cambios posteriores? – VonC

+0

Prefiero hacer una "fusión negativa" con git-merge-file: ver mi respuesta completa – VonC

+0

1) Sí. Gracias por corregir el otro error tipográfico. 2) Sí - Es por eso que tendría que ver la diferencia. De hecho, utilizaría una diffmerge pirateada en el modo de fusión de 3 vías, lo que me muestra una diferencia más útil con la que estoy acostumbrado a trabajar, pero puede usar git diff. El punto es: solo estoy seguro si miré el código. – Paul

1

Eche un vistazo a this git revert question. Parece haber un problema que revierte las confirmaciones más antiguas si no es en una secuencia consecutiva que incluye la confirmación más reciente.

+0

Upvoting porque esto fue útil, pero realmente debería ser un comentario en lugar de una respuesta. – tripleee

Cuestiones relacionadas