¿Cómo se elimina una revisión específica en el historial de git?

213

Supongamos que su historial de git se ve así:

1 2 3 4 5

1–5 son revisiones separadas. Debe eliminar 3 mientras mantiene 1, 2, 4 y 5. ¿Cómo se puede hacer esto?

¿Existe un método eficiente cuando hay cientos de revisiones después de la que se eliminará?

1800 INFORMACIÓN
fuente
Esta pregunta está mal definida. No dice explícitamente que el autor quiere 1-2- (3 + 4) -5 o 1-2-4-5
RandyTek
14
Bueno, 8 años después, no puedo decirte exactamente qué problema estaba tratando de resolver. Pero en git siempre hay muchas maneras de hacer algo, y hay muchas respuestas que les han gustado a varias personas, así que supongo que la ambigüedad no está causando demasiadas dificultades a mucha gente
1800 INFORMACIÓN
1
git rebase --onto 2 3 HEADmás o menos significa rebase en 2, con commits entre 3 y HEAD (HEAD es opcional, o 5 en este caso)
neaumusic

Respuestas:

76

Para combinar la revisión 3 y 4 en una sola revisión, puede usar git rebase. Si desea eliminar los cambios en la revisión 3, debe usar el comando de edición en el modo de rebase interactivo. Si desea combinar los cambios en una sola revisión, use squash.

He utilizado con éxito esta técnica de squash, pero nunca antes había necesitado eliminar una revisión. Con suerte, la documentación de git-rebase en "Splitting commits" debería darle una idea suficiente para resolverlo. (O alguien más podría saberlo).

De la documentación de git :

Comience con la confirmación más antigua que desea conservar tal cual:

git rebase -i <after-this-commit>

Se activará un editor con todos los commits en su rama actual (ignorando los commits de fusión), que vienen después del commit dado. Puede reordenar las confirmaciones de esta lista al contenido de su corazón, y puede eliminarlas. La lista se ve más o menos así:

elegir deadbee La línea de este commit
pick fa1afe1 La línea del próximo commit
...

Las descripciones en línea son puramente para su placer; git-rebase no los verá sino a los nombres de confirmación ("deadbee" y "fa1afe1" en este ejemplo), así que no elimine ni edite los nombres.

Al reemplazar el comando "pick" con el comando "edit", puede decirle a git-rebase que se detenga después de aplicar ese commit, para que pueda editar los archivos y / o el mensaje de commit, modificar el commit y continuar el rebase.

Si desea doblar dos o más commits en uno, reemplace el comando "pick" con "squash" para el segundo commit y el siguiente. Si los commits tenían diferentes autores, atribuirá el commit aplastado al autor del primer commit.

garethm
fuente
42
-1 La pregunta está bien definida pero esta respuesta no es tan clara. El autor no dice cuál es la solución exacta.
Aleksandr Levchuk
1
Esta es una pista equivocada. La sección COMPROMISOS DE DIVISIÓN no es la correcta. Desea leer mucho más arriba en el manual; consulte la respuesta de @Rares Vernica.
Aleksandr Levchuk
1
@AleksandrLevchuk La pregunta no está bien definida: no está claro por la forma en que se plantea la pregunta si el conjunto de cambios en 3 debe mantenerse o descartarse. Estaré de acuerdo en que si se descartan los cambios, las otras respuestas ofrecen un enfoque más fácil. Sin embargo, si se van a mantener los cambios, esta sería una operación puramente cosmética. Ambos enfoques reescriben el historial de maneras peligrosas si otros pueden haber basado el trabajo sobre el historial defectuoso; Si ese es el caso, la limpieza cosmética no debe hacerse, y la eliminación del cambio se haría mejor a través de git revert.
Theodore Murdock
124

Según este comentario (y verifiqué que esto es cierto), la respuesta de rado está muy cerca, pero deja a git en un estado de cabeza separada. En cambio, elimine HEADy use esto para eliminar <commit-id>de la rama en la que se encuentra:

git rebase --onto <commit-id>^ <commit-id>
kareem
fuente
1
Si pudieras decirme cómo eliminar también ese commit-id del historial basado solo en commit-id, serías mi héroe.
kayleeFrye_onDeck
¿Puede por favor incluir una explicación de lo que hace el comando mágico en la respuesta? Es decir, ¿qué denota cada parámetro?
alexandroid
Esto es increíble, pero ¿qué pasa si quiero eliminar la primera confirmación en la historia? (por eso vine aquí: P)
Sterling Camden
2
Por alguna razón, no pasa nada cuando ejecuto esto. Sin embargo, cambiar ^a ~1hacerlo funcionar.
Antimonio
Muy tarde, pero voy a agregar que si te encuentras con conflictos de fusión, aborta el rebase y luego vuelve a ejecutarlo --strategy-option theirsal final.
LastStar007
122

Aquí hay una manera de eliminar de forma no interactiva un específico <commit-id>, sabiendo solo <commit-id>lo que desea eliminar:

git rebase --onto <commit-id>^ <commit-id> HEAD
rado
fuente
2
A mí también me funcionó. Por cierto, ¿qué hace el operador ^? ¿Significa la próxima confirmación después de la especificada?
hopia
3
@hopia significa el (primer) padre de la confirmación especificada. Consulte "revisiones git help"
Emil Styrke
12
Vea la recomendación de @ kareem en su respuesta para omitir a HEADfin de evitar una cabeza desprendida.
mklement0
1
Mucho más fácil que tener que calcular cuántas confirmaciones se vuelven a buscar.
Dana Woodman
1
Esto es increíble, pero ¿qué pasa si quiero eliminar la primera confirmación en la historia? (por eso vine aquí: P)
Sterling Camden
76

Como se señaló antes, git-rebase (1) es tu amigo. Asumiendo que los commits están en su masterrama, usted haría:

git rebase --onto master~3 master~2 master

Antes de:

1---2---3---4---5  master

Después:

1---2---4'---5' master

De git-rebase (1):

También se puede eliminar una serie de confirmaciones con rebase. Si tenemos la siguiente situación:

E---F---G---H---I---J  topicA

entonces el comando

git rebase --onto topicA~5 topicA~3 topicA

daría como resultado la eliminación de confirmaciones F y G:

E---H'---I'---J'  topicA

Esto es útil si F y G fueron defectuosos de alguna manera, o no deberían ser parte del tema A. Tenga en cuenta que el argumento para --onto y el parámetro pueden ser cualquier commit-ish válido.

rvernica
fuente
3
no debería ser esto --onto master~3 master~1?
Matthias
3
Si simplemente desea eliminar la última confirmación, es --onto master ~ 1 master
MikeHoss
Me gustaría llevar este sol. pero me sale el MERGE CONFLICTerror Usé el escenario mencionado en stackoverflow.com/questions/2938301/remove-specific-commit y no puedo eliminar la segunda confirmación en ese ejemplo.
maan81
22

Si todo lo que desea hacer es eliminar los cambios realizados en la revisión 3, es posible que desee utilizar git revert.

Git revert simplemente crea una nueva revisión con cambios que deshacen todos los cambios en la revisión que está revocando.

Lo que esto significa es que retiene información sobre la confirmación no deseada y la confirmación que elimina esos cambios.

Esto es probablemente mucho más amigable si es posible que alguien haya retirado de su repositorio mientras tanto, ya que la reversión es básicamente una confirmación estándar.

SpoonMeiser
fuente
44
Desafortunadamente, no es una buena solución para mí porque alguien accidentalmente cometió 100 MB de basura en el repositorio, explotando el tamaño y haciendo que la interfaz web sea lenta.
Stephen Smith
18

Todas las respuestas hasta ahora no abordan la preocupación final:

¿Existe un método eficiente cuando hay cientos de revisiones después de la que se eliminará?

Los pasos siguen, pero como referencia, supongamos el siguiente historial:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : commit solo siguiendo el commit que se eliminará (clean)

R : El compromiso a eliminar

B : commit justo antes del commit a eliminar (base)

Debido a la restricción de "cientos de revisiones", estoy asumiendo las siguientes condiciones previas:

  1. hay un compromiso vergonzoso que desearías nunca haber existido
  2. hay CERO confirmaciones posteriores que realmente dependen de esa confirmación embarazosa (cero conflictos al revertir)
  3. no le importa que aparezca como el 'Comprador' de los cientos de confirmaciones que intervienen ('Autor' se conservará)
  4. nunca has compartido el repositorio
    • o realmente tiene suficiente influencia sobre todas las personas que alguna vez han clonado la historia con ese compromiso para convencerlos de que usen su nueva historia
    • y que no se preocupan acerca de la reescritura de la historia

Este es un conjunto de restricciones bastante restrictivo, pero hay una respuesta interesante que realmente funciona en este caso de esquina.

Aquí están los pasos:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Si realmente no hay conflictos, esto debería proceder sin más interrupciones. Si hay conflictos, puede resolverlos rebase --continueo decidir vivir con la vergüenza y rebase --abort.

Ahora debería estar en mastereso ya no tiene commit R en él. La saverama apunta a donde estabas antes, en caso de que quieras conciliar.

Depende de usted cómo desea organizar la transferencia de todos los demás a su nuevo historial. Usted tendrá que estar familiarizado con stash, reset --hardy cherry-pick. Y puede eliminar las base, remove-mey savelas ramas

jdsumsion
fuente
¿Cómo me gusta este 150000 veces?
Gena Moroz
Me funcionó para eliminar 3 confirmaciones en secuencia de la mitad de la historia de una rama donde alguien cometió un montón de archivos de 150mb.
Marcos
3

También aterricé en una situación similar. Use el rebase interactivo usando el comando a continuación y mientras selecciona, suelte la tercera confirmación.

git rebase -i remote/branch
Sankalp
fuente
2

Así que aquí está el escenario que enfrenté y cómo lo resolví.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

Aquí Restá la confirmación que necesitaba eliminar, y Ies una confirmación única que viene despuésR

Hice un compromiso de reversión y los aplasté juntos

git revert [commit id of R]
git rebase -i HEAD~3

Durante el squash de rebase interactivo, los últimos 2 commits.

Gautama
fuente
0

Las respuestas de rado y kareem no hacen nada por mí (solo aparece el mensaje "La rama actual está actualizada"). Posiblemente esto sucede porque el símbolo '^' no funciona en la consola de Windows. Sin embargo, de acuerdo con este comentario, reemplazar '^' por '~ 1' resuelve el problema.

git rebase --onto <commit-id>^ <commit-id>
fdermishin
fuente