tag practices eliminar crear commits commands best git commit revert cherry-pick

git - practices - Eliminar compromiso específico



git tags best practices (9)

Estaba trabajando con un amigo en un proyecto y él editó un montón de archivos que no deberían haberse editado. De alguna manera fusioné su trabajo con el mío, ya sea cuando lo saqué o cuando intenté simplemente seleccionar los archivos específicos que quería. He estado buscando y jugando durante mucho tiempo, tratando de averiguar cómo eliminar las confirmaciones que contienen las ediciones de esos archivos, parece ser un repunte entre revertir y rebase, y no hay ejemplos sencillos, y el Los doctores suponen que sé más que yo.

Así que aquí hay una versión simplificada de la pregunta:

Dado el siguiente escenario, ¿cómo elimino commit 2?

$ mkdir git_revert_test && cd git_revert_test $ git init Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/ $ echo "line 1" > myfile $ git add -A $ git commit -m "commit 1" [master (root-commit) 8230fa3] commit 1 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 myfile $ echo "line 2" >> myfile $ git commit -am "commit 2" [master 342f9bb] commit 2 1 files changed, 1 insertions(+), 0 deletions(-) $ echo "line 3" >> myfile $ git commit -am "commit 3" [master 1bcb872] commit 3 1 files changed, 1 insertions(+), 0 deletions(-)

El resultado esperado es

$ cat myfile line 1 line 3

Aquí hay un ejemplo de cómo he estado tratando de revertir

$ git revert 342f9bb Automatic revert failed. After resolving the conflicts, mark the corrected paths with ''git add <paths>'' or ''git rm <paths>'' and commit the result.


Así que hiciste un trabajo y lo presionaste, llamémosles los compromisos A y B. Tu compañero de trabajo también hizo un poco de trabajo, los compromisos C y D. Combinaste el trabajo de tus compañeros de trabajo con el tuyo (fusionar cometer E), luego continuaste trabajando, comprometiste eso, también (cometa F), y descubrió que su compañero de trabajo cambió algunas cosas que no debería haber hecho.

Así que tu historial de compromiso se ve así:

A -- B -- C -- D -- D'' -- E -- F

Realmente quieres deshacerte de C, D y D ''. Ya que dice que fusionó el trabajo de sus compañeros de trabajo en el suyo, estos compromisos ya están "ahí fuera", por lo que eliminarlos mediante, por ejemplo, git rebase es un no-no. Créeme, lo he intentado.

Ahora, veo dos salidas:

  • Si aún no ha enviado E y F a su compañero de trabajo ni a ninguna otra persona (por lo general, su servidor de "origen"), aún puede eliminarlos del historial por el momento. Este es tu trabajo que quieres guardar. Esto se puede hacer con una

    git reset D''

    (Reemplace D ''con el hash de confirmación real que puede obtener de un git log

    En este punto, las confirmaciones E y F han desaparecido y los cambios son cambios no confirmados en su área de trabajo local nuevamente. En este punto, los movería a una rama o los convertiría en un parche y los guardaría para más adelante. Ahora, revertir el trabajo de su compañero de trabajo, ya sea automáticamente con un git revert o manualmente. Cuando hayas hecho eso, vuelve a reproducir tu trabajo sobre eso. Es posible que tenga conflictos de combinación, pero al menos estarán en el código que escribió, en lugar del código de su compañero de trabajo.

  • Si ya presionó el trabajo que hizo después de que su compañero de trabajo se compromete, aún puede intentar obtener un "parche inverso" ya sea manualmente o usando git revert , pero como su trabajo está "en el camino", por así decirlo, Probablemente obtendrás más conflictos de fusión y más confusos. Parece que eso es lo que terminaste en ...


El algoritmo que usa Git para calcular las diferencias a revertir requiere que

  1. las líneas que están siendo revertidas no son modificadas por ninguna confirmación posterior.
  2. Que no haya ningún otro compromiso "adyacente" más adelante en la historia.

La definición de "adyacente" se basa en el número predeterminado de líneas de un diff de contexto, que es 3. Entonces, si ''myfile'' se construyó así:

$ cat >myfile <<EOF line 1 junk junk junk junk line 2 junk junk junk junk line 3 EOF $ git add myfile $ git commit -m "initial check-in" 1 files changed, 11 insertions(+), 0 deletions(-) create mode 100644 myfile $ perl -p -i -e ''s/line 2/this is the second line/;'' myfile $ git commit -am "changed line 2 to second line" [master d6cbb19] changed line 2 1 files changed, 1 insertions(+), 1 deletions(-) $ perl -p -i -e ''s/line 3/this is the third line/;'' myfile $ git commit -am "changed line 3 to third line" [master dd054fe] changed line 3 1 files changed, 1 insertions(+), 1 deletions(-) $ git revert d6cbb19 Finished one revert. [master 2db5c47] Revert "changed line 2" 1 files changed, 1 insertions(+), 1 deletions(-)

Entonces todo funciona como se espera.

La segunda respuesta fue muy interesante. Hay una función que aún no se ha lanzado oficialmente (aunque está disponible en Git v1.7.2-rc2) llamada Revert Strategy. Puedes invocar git así:

git revert --estrategy resolver <commit>

y debería hacer un mejor trabajo para averiguar a qué se refería. No sé cuál es la lista de estrategias disponibles, ni tampoco conozco la definición de ninguna estrategia.


Hay cuatro formas de hacerlo:

  • Manera limpia, revirtiendo pero manteniendo en registro la reversión:

    git revert --strategy resolve <commit>

  • De manera áspera, elimina por completo solo el último commit:

    git reset --soft "HEAD^"

Nota: Evite git reset --hard ya que también descartará todos los cambios en los archivos desde la última confirmación. Si --soft no funciona, intente --mixed o --keep .

  • Rebase (muestre el registro de las últimas 5 confirmaciones y elimine las líneas que no desea, o reordene, o elimine las confirmaciones múltiples en una, o haga lo que quiera, esta es una herramienta muy versátil):

    git rebase -i HEAD~5

Y si se comete un error:

git rebase --abort

  • Rebase rápido: elimina solo una confirmación específica usando su id:

    git rebase --onto commit-id^ commit-id

  • Alternativas: también puedes probar:

    git cherry-pick commit-id

  • Otra alternativa más:

    git revert --no-commit

  • Como último recurso, si necesita una libertad total de edición de historial (por ejemplo, porque git no le permite editar lo que desea), puede usar esta aplicación de código abierto muy rápida : reposurgeon .

Nota: por supuesto, todos estos cambios se realizan a nivel local, debe git push después para aplicar los cambios al control remoto. Y en caso de que su repo no quiera eliminar la confirmación ("no se permite el avance rápido", lo que sucede cuando desea eliminar una confirmación que ya haya presionado), puede usar git push -f para forzar la inserción de los cambios.

Nota 2: si está trabajando en una rama y necesita forzar el empuje, debe evitar absolutamente git push --force porque esto puede sobrescribir otras ramas (si ha realizado cambios en ellas, incluso si su pago actual está en otra rama). Prefiere especificar siempre la rama remota al forzar push : git push --force origin your_branch .


Muy facil manera

git rebase -i HEAD ~ x

(x = no. de cometer)

Al ejecutar el archivo de bloc de notas, se abrirá ''put'' junto a su confirmación

y ya está listo ... solo sincronice el panel de control de git y los cambios se enviarán al control remoto. Si la confirmación que dejó caer ya estaba en el control remoto, tendrá que forzar la actualización. Dado que --force se considera harmful , use git push --force-with-lease .


Por favor, siga las instrucciones a continuación, ya que estamos usando - forzar que necesita tener derechos de administrador sobre el repositorio de git para hacer esto.

Paso 1: busque la confirmación antes de la confirmación que desea eliminar git log

Paso 2: Checkout que confirme git checkout <commit hash>

Paso 3: Haz una nueva sucursal usando tu actual checkout commit git checkout -b <new branch>

Paso 4: Ahora debe agregar el compromiso después del compromiso eliminado git cherry-pick <commit hash>

Paso 5: Ahora repita el paso 4 para todas las demás confirmaciones que desee mantener.

Paso 6: Una vez que todas las confirmaciones se hayan agregado a su nueva sucursal y se hayan comprometido. Verifique que todo esté en el estado correcto y que funcione según lo previsto. Comprueba todo lo que se ha cometido: git status

Paso 7: Cambia a tu git checkout <broken branch>

Paso 8: Ahora realice un git reset --hard <commit hash> en la rama rota de la confirmación anterior a la que desea eliminar git reset --hard <commit hash>

Paso 9: Combina tu rama fija en esta rama git merge <branch name>

Paso 10: Empuje los cambios combinados de nuevo a origen. ADVERTENCIA: Esto sobrescribirá el repositorio remoto! git push --force origin <branch name>

Puede realizar el proceso sin crear una nueva rama reemplazando los pasos 2 y 3 con los pasos 8 y luego no realice los pasos 7 y 9.


Por lo tanto, parece que la confirmación incorrecta se incorporó en una confirmación de fusión en algún momento. ¿Ya se ha retirado tu confirmación de fusión? Si es así, entonces querrás usar git revert ; Tendrás que apretar los dientes y superar los conflictos. Si no, entonces es posible que pueda volver a generar o revertir, pero puede hacerlo antes de que comience la fusión, luego rehacer la fusión.

No hay mucha ayuda que podamos darle para el primer caso, de verdad. Después de intentar revertir y encontrar que el automático falló, debe examinar los conflictos y corregirlos adecuadamente. Este es exactamente el mismo proceso que para corregir conflictos de combinación; puede usar el git status para ver dónde están los conflictos, editar los archivos no combinados, encontrar los tíos en conflicto, averiguar cómo resolverlos, agregar los archivos en conflicto y finalmente confirmar. Si usa git commit por sí mismo (sin -m <message> ), el mensaje que aparece en su editor debe ser el mensaje de plantilla creado por git revert ; puede agregar una nota sobre cómo solucionó los conflictos, luego guardar y salir para confirmar.

Para el segundo caso, al solucionar el problema antes de la fusión, hay dos subcasos, dependiendo de si ha realizado más trabajo desde la fusión. Si no lo ha hecho, simplemente puede git reset --hard HEAD^ para eliminar la fusión, hacer la reversión y luego rehacer la fusión. Pero supongo que sí. Entonces, terminarás haciendo algo como esto:

  • cree una rama temporal justo antes de la fusión y échele un vistazo
  • haga la reversión (o use git rebase -i <something before the bad commit> <temporary branch> para eliminar la confirmación incorrecta)
  • rehacer la fusión
  • rehaga el trabajo posterior en: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • quitar la rama temporal

Por otras respuestas aquí, estaba un poco confundido con el uso de git rebase -i para eliminar un compromiso, así que espero que esté bien anotar mi caso de prueba aquí (muy similar al OP).

Aquí hay un script de bash que puede pegar para crear un repositorio de prueba en la carpeta /tmp :

set -x rm -rf /tmp/myrepo* cd /tmp mkdir myrepo_git cd myrepo_git git init git config user.name me git config user.email [email protected] mkdir folder echo aaaa >> folder/file.txt git add folder/file.txt git commit -m "1st git commit" echo bbbb >> folder/file.txt git add folder/file.txt git commit -m "2nd git commit" echo cccc >> folder/file.txt git add folder/file.txt git commit -m "3rd git commit" echo dddd >> folder/file.txt git add folder/file.txt git commit -m "4th git commit" echo eeee >> folder/file.txt git add folder/file.txt git commit -m "5th git commit"

En este punto, tenemos un file.txt con estos contenidos:

aaaa bbbb cccc dddd eeee

En este punto, HEAD está en el quinto compromiso, HEAD ~ 1 sería el cuarto, y HEAD ~ 4 sería el primer compromiso (por lo tanto, HEAD ~ 5 no existiría). Digamos que queremos eliminar la tercera confirmación: podemos emitir este comando en el directorio myrepo_git :

git rebase -i HEAD~4

( Tenga en cuenta que git rebase -i HEAD~5 da como resultado "fatal: se necesita una sola revisión; HEAD en sentido ascendente no válido de 5" ) . Se abrirá un editor de texto (vea la captura de pantalla en la respuesta de @Dennis ) con este contenido:

pick 5978582 2nd git commit pick 448c212 3rd git commit pick b50213c 4th git commit pick a9c8fa1 5th git commit # Rebase b916e7f..a9c8fa1 onto b916e7f # ...

Así que recibimos todas las confirmaciones desde (pero sin incluir ) nuestra HEAD ~ 4 solicitada. Elimine la línea pick 448c212 3rd git commit y guarde el archivo; obtendrás esta respuesta de git rebase :

error: could not apply b50213c... 4th git commit When you have resolved this problem run "git rebase --continue". If you would prefer to skip this patch, instead run "git rebase --skip". To check out the original branch and stop rebasing run "git rebase --abort". Could not apply b50213c... 4th git commit

En este punto, abra myrepo_git / folder/file.txt en un editor de texto; Verás que ha sido modificado:

aaaa bbbb <<<<<<< HEAD ======= cccc dddd >>>>>>> b50213c... 4th git commit

Básicamente, git ve que cuando HEAD llegó a la segunda confirmación, había contenido de aaaa + bbbb ; y luego tiene un parche de cccc + dddd que no sabe cómo agregar al contenido existente.

Así que aquí git no puede decidir por usted; es usted quien debe tomar una decisión: al eliminar la tercera confirmación, puede mantener los cambios introducidos por ella (aquí, la línea cccc ) o no. Si no lo hace, simplemente elimine las líneas adicionales, incluido cccc , en la folder/file.txt con un editor de texto, para que se vea así:

aaaa bbbb dddd

... y luego guardar folder/file.txt . Ahora puedes emitir los siguientes comandos en el directorio myrepo_git :

$ nano folder/file.txt # text editor - edit, save $ git rebase --continue folder/file.txt: needs merge You must edit all merge conflicts and then mark them as resolved using git add

Ah, para marcar que hemos resuelto el conflicto, debemos git add la folder/file.txt , antes de hacer git rebase --continue :

$ git add folder/file.txt $ git rebase --continue

Aquí se abre nuevamente un editor de texto, que muestra la línea 4th git commit - aquí tenemos la oportunidad de cambiar el mensaje de confirmación (que en este caso podría cambiarse significativamente a 4th (and removed 3rd) commit o similar). Digamos que no desea hacerlo, así que simplemente salga del editor de texto sin guardar; Una vez que hagas eso, obtendrás:

$ git rebase --continue [detached HEAD b8275fc] 4th git commit 1 file changed, 1 insertion(+) Successfully rebased and updated refs/heads/master.

En este punto, ahora tiene un historial como este (que también podría inspeccionar con, por ejemplo, gitk . u otras herramientas) del contenido de la folder/file.txt (con, al parecer, las marcas de tiempo sin cambios de las confirmaciones originales):

1st git commit | +aaaa ---------------------------------------------- 2nd git commit | aaaa | +bbbb ---------------------------------------------- 4th git commit | aaaa | bbbb | +dddd ---------------------------------------------- 5th git commit | aaaa | bbbb | dddd | +eeee

Y si previamente, decidimos mantener la línea cccc (el contenido del tercer compromiso de git que eliminamos), tendríamos:

1st git commit | +aaaa ---------------------------------------------- 2nd git commit | aaaa | +bbbb ---------------------------------------------- 4th git commit | aaaa | bbbb | +cccc | +dddd ---------------------------------------------- 5th git commit | aaaa | bbbb | cccc | dddd | +eeee

Bueno, este fue el tipo de lectura que esperaba encontrar, para comenzar a analizar cómo funciona git rebase en términos de eliminar confirmaciones / revisiones; así que espero que pueda ayudar a otros también ...


Puede eliminar los compromisos no deseados con git rebase . Digamos que incluyó algunas confirmaciones de la rama temática de un compañero de trabajo en su rama temática, pero luego decida que no desea esas confirmaciones.

git checkout -b tmp-branch my-topic-branch # Use a temporary branch to be safe. git rebase -i master # Interactively rebase against master branch.

En este punto, su editor de texto abrirá la vista de rebase interactiva. Por ejemplo

  1. Elimina las confirmaciones que no quieras eliminando sus líneas.
  2. Guardar y Salir

Si la reorganización no tuvo éxito, elimine la rama temporal y pruebe otra estrategia. De lo contrario continúe con las siguientes instrucciones.

git checkout my-topic-branch git reset --hard tmp-branch # Overwrite your topic branch with the temp branch. git branch -d tmp-branch # Delete the temporary branch.

Si está empujando su rama de tema a un control remoto, es posible que deba forzar el impulso ya que el historial de confirmación ha cambiado. Si otros están trabajando en la misma rama, dales un aviso.


Tu elección es entre

  1. manteniendo el error e introduciendo una solución y
  2. Eliminando el error y cambiando el historial.

Debería elegir (1) si alguien más ha recogido el cambio erróneo y (2) si el error se limita a una rama privada sin empujar.

Git revert es una herramienta automatizada para hacer (1), crea un nuevo commit deshaciendo algunos commit anteriores. Verá el error y la eliminación en el historial del proyecto, pero las personas que salen de su repositorio no tendrán problemas cuando se actualicen. No funciona de forma automática en su ejemplo, por lo que necesita editar ''myfile'' (para eliminar la línea 2), git add myfile y git commit para resolver el conflicto. Luego terminará con cuatro confirmaciones en su historial, con la confirmación de revocación de confirmación 4.

Si a nadie le importa que su historial cambie, puede reescribirlo y eliminar el commit 2 (opción 2). La forma más fácil de hacer esto es usar git rebase -i 8230fa3 . Esto lo colocará en un editor y puede elegir no incluir la confirmación errónea eliminando la confirmación (y manteniendo "pick" al lado de los otros mensajes de confirmación. Lea las consecuencias de hacerlo .