Eliminar compromiso específico

Estaba trabajando con un amigo en un proyecto, y él editó un grupo de files que no deberían haber sido editados. De alguna manera, fusioné su trabajo con el mío, ya sea cuando lo saqué o cuando traté de elegir solo los files específicos que quería. He estado buscando y jugando durante mucho time, tratando de descubrir cómo eliminar las confirmaciones que contienen las ediciones de esos files, parece ser un lanzamiento entre revertir y rebase, y no hay ejemplos claros, y el docs suponen que sé más que yo.

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

Dada la siguiente situación, ¿cómo elimino el compromiso 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. 

El algorithm que usa Git cuando calcula las diferencias para ser revertido requiere que

  1. las líneas que se revierten no son modificadas por ningún commit posterior, y
  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 pnetworkingeterminado de líneas de un context diff, que es 3. Entonces, si 'mifile' 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 esperaba.

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

git revert –strategy resolve <commit>

y debería hacer un mejor trabajo averiguando lo que querías decir. No sé cuál es la list de estrategias disponibles, ni conozco la definición de ninguna estrategia.

Hay cuatro forms de hacerlo:

  • Manera limpia, revertir pero mantener en el logging la revertir:

     git revert --strategy resolve <commit> 
  • Manera difícil, eliminar por completo solo el último compromiso:

     git reset --soft "HEAD^" 

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

  • Rebase (muestra el logging de los últimos 5 commits y elimina las líneas que no deseas, o reorderas, o aplastas múltiples commits en uno, o haces cualquier otra cosa que desees, esta es una herramienta muy versátil):

     git rebase -i HEAD~5 

Y si se comete un error:

 git rebase --abort 
  • Rebase rápido: elimine 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 total libertad 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 : repository .

Nota: por supuesto, todos estos cambios se realizan localmente, luego debe git push para aplicar los cambios al control remoto. Y en caso de que su repository no quiera eliminar la confirmación ("no se permite el avance rápido", que sucede cuando desea eliminar una confirmación que ya ha enviado), puede usar git push -f para forzar los cambios.

Nota 2: si trabaja en una bifurcación y necesita forzar la inserción, debe evitar absolutamente git push --force porque puede sobrescribir otras bifurcaciones (si ha realizado cambios en ellas, incluso si su pago actual está en otra sucursal). Prefiero siempre especificar la twig remota cuando fuerce push : git push --force origin your_branch .

Tu elección está entre

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

Debe elegir (1) si el cambio erróneo ha sido recogido por cualquier otra persona y (2) si el error está limitado a una twig privada no empujada.

Git revert es una herramienta automatizada para hacer (1), crea un nuevo commit deshaciendo algunos commit previos. Verá el error y la eliminación en el historial del proyecto, pero las personas que extraen de su repository no tendrán problemas cuando actualicen. No está funcionando de manera automatizada en su ejemplo, por lo que debe editar 'myfile' (para eliminar la línea 2), hacer git add myfile y git commit para enfrentar el conflicto. Luego terminarás con cuatro commits en tu historial, con commit 4 revirtiendo commit 2.

Si a nadie le importa que su historial cambie, puede volver a escribirlo y eliminar el compromiso 2 (opción 2). La manera más fácil de hacerlo es usar git rebase -i 8230fa3 . Esto lo llevará a un editor y puede elegir no include la confirmación errónea quitando la confirmación (y manteniendo "selección" al lado de los otros posts de confirmación. Lea sobre las consecuencias de hacer esto .

Puede eliminar commits no deseados con git rebase . Digamos que incluyó algunas confirmaciones de la twig de temas de un compañero de trabajo en su twig de tema, pero luego decide 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 text abrirá la vista de rebase interactiva. Por ejemplo

git-rebase-todo

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

Si la rebase no fue exitosa, elimine la twig 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 twig de tema a un control remoto, es posible que necesite forzar la inserción ya que el historial de confirmación ha cambiado. Si otros están trabajando en la misma twig, denles un aviso.

Muy fácil

git rebase -i HEAD ~ x

(x = no de commits)

al ejecutar el file del bloc de notas abrirá 'colocar' soltar además de su compromiso enter image description here

y eso es todo, listo … solo sincronice el tablero de git y los cambios se enviarán a control remoto. Si la confirmación que soltaste ya estaba en el control remoto, deberás forzar la actualización. Como –force se considera perjudicial , use git push --force-with-lease .

De otras respuestas aquí, estaba un poco confundida con la forma en que git rebase -i podría usarse para eliminar una confirmación, así que espero que esté bien anotar aquí mi caso de testing (muy similar al OP).

Aquí hay un script bash que puede pegar para crear un repository de testing 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 me@myself.com 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 5to compromiso, HEAD ~ 1 sería el 4 ° y HEAD ~ 4 sería el primer commit (por lo que HEAD ~ 5 no existiría). Digamos que queremos eliminar el tercer compromiso: podemos emitir este command en el directory myrepo_git :

 git rebase -i HEAD~4 

( Tenga en count que git rebase -i HEAD~5 resultados con "fatal: se necesita una única revisión; HEAD upstream no válido ~ 5" ) . Se abrirá un editor de text (ver captura de pantalla en la respuesta de @Dennis ) con estos contenidos:

 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 # ... 

De modo que obtenemos todos los commits desde (pero no incluidos ) nuestro HEAD ~ 4 solicitado. Eliminar la línea pick 448c212 3rd git commit y save el file; 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 text; verás que ha sido modificado:

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

Básicamente, git ve que cuando HEAD llegó a 2nd commit, había contenido de aaaa + bbbb ; y luego tiene un parche de cccc + dddd que no sabe cómo anexar al contenido existente.

Entonces aquí, git no puede decidir por usted; es usted el que debe tomar una decisión: al eliminar el 3er compromiso, o bien conserva los cambios introducidos por él (aquí, la línea cccc ), o no lo hace. Si no lo hace, simplemente elimine las líneas adicionales, incluido el cccc , en la folder/file.txt usando un editor de text, para que se vea así:

 aaaa bbbb dddd 

… y luego save la folder/file.txt . Ahora puede emitir los siguientes commands en el directory 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, entonces 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 de nuevo un editor de text, que muestra la línea 4th git commit – aquí tenemos la posibilidad de cambiar el post de confirmación (que en este caso podría cambiarse significativamente a 4th (and removed 3rd) commit o similar). Digamos que no quiere, así que simplemente salga del editor de text sin save; 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 tienes un historial como este (que también puedes inspeccionar con say gitk . u otras herramientas) del contenido de folder/file.txt (con, aparentemente, marcas de time 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 anteriormente, decidimos mantener la línea cccc (el contenido del 3er commit de git que eliminamos), habríamos tenido:

 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 era el tipo de lectura que esperaba haber encontrado, para comenzar a ver cómo funciona la base de git rebase en términos de eliminación de confirmaciones / revisiones; así que espero que pueda ayudar a otros también …

Por lo tanto, parece que el compromiso incorrecto se incorporó en un compromiso de fusión en algún momento. ¿Ya se ha retirado la fusión? Si es así, entonces querrás usar git revert ; tendrás que apretar los dientes y superar los conflictos. Si no, entonces posiblemente podría volver a establecer la base o revertir, pero puede hacerlo antes de la fusión de compromiso, luego vuelva a hacer la fusión.

No hay mucha ayuda que podamos darte para el primer caso, realmente. Después de intentar la reversión, y de encontrar que la falla automática, debe examinar los conflictos y corregirlos de manera adecuada. Este es exactamente el mismo process que la fijación de conflictos de fusión; puede usar el git status para ver dónde están los conflictos, editar los files no fusionados, encontrar los conflictos conflictivos, encontrar la manera de resolverlos, agregar los files en conflicto y, finalmente, confirmarlos. Si usa git commit por sí mismo (no -m <message> ), el post que aparece en su editor debe ser el post de plantilla creado por git revert ; puede agregar una nota sobre cómo solucionó los conflictos, luego save y salir para confirmar.

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

  • crea una twig temporal justo antes de la fusión, y échale un vistazo
  • revertir (o usar git rebase -i <something before the bad commit> <temporary branch> para eliminar la confirmación incorrecta)
  • rehacer la fusión
  • Vuelva a establecer su trabajo posterior en: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • eliminar la twig temporal

Así que hiciste un poco de trabajo y lo presionaste, vamos a llamarlos compromete A y B. Tu compañero de trabajo también hizo algo de trabajo, compromete C y D. Fusionaste el trabajo de tus compañeros de trabajo con el tuyo (fusión compromete E), luego continuó trabajando, se comprometió a eso, también (cometer F), y descubrió que su compañero de trabajo cambió algunas cosas que no debería tener.

Entonces tu historial de commits se ve así:

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

Realmente quieres deshacerte de C, D y D '. Como dices que fusionaste el trabajo de tus compañeros de trabajo con el tuyo, estos se comprometen ya "por ahí", por lo que eliminar las confirmaciones usando, por ejemplo, git rebase es un no-no. Créame, lo he intentado.

Ahora, veo dos forms de salir:

  • si todavía no ha presionado E y F a su compañero de trabajo o a cualquier otra persona (por lo general, su server de "origen"), igual podría eliminar los del historial por el momento. Este es su trabajo que desea save. Esto se puede hacer con un

     git reset D' 

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

    En este punto, los commits E y F se han ido y los cambios no son confirmados en su espacio de trabajo local nuevamente. En este punto, los movería a una twig o los convertiría en un parche y lo saveé para más adelante. Ahora, revierte el trabajo de tu compañero de trabajo, ya sea automáticamente con un git revert o manualmente. Cuando hayas hecho eso, repite tu trabajo además de eso. Puede tener conflictos de fusión, pero al less estarán en el código que escribió, en lugar del código de su compañero de trabajo.

  • Si ya has presionado el trabajo que hiciste después de las confirmaciones de tu compañero de trabajo, aún puedes intentar get un "parche inverso" ya sea manualmente o usando git revert , pero dado que tu trabajo está "en el path", por así decirlo, podrás probablemente obtenga más conflictos de combinación y más confusos. Parece que eso es en lo que terminaste …

Por favor, siga la siguiente sección de acciones, ya que estamos usando –force, necesita tener derechos de administrador sobre el repository de git para hacer esto.

Paso 1: Encuentra la confirmación antes de la confirmación que deseas eliminar git log

Paso 2: Finalizar la git checkout <commit hash> que confirma el git checkout <commit hash>

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

Paso 4: Ahora necesita agregar la confirmación después de la confirmación de eliminación. git cherry-pick <commit hash>

Paso 5: Ahora repite el Paso 4 para todas las otras confirmaciones que quieras 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 funcione según lo previsto. Verifique todo lo que se ha cometido: git status

Paso 7: Cambie a su git checkout <broken branch>

Paso 8: Ahora realiza un git reset --hard <commit hash> en la twig rota a la confirmación antes de la que deseas eliminar git reset --hard <commit hash>

Paso 9: combine su twig fija en esta twig git merge <branch name>

Paso 10: vuelva a insert los cambios fusionados en el origen. ADVERTENCIA: ¡Esto sobrescribirá el repository remoto! git push --force origin <branch name>

Puede hacer el process sin crear una nueva bifurcación reemplazando los pasos 2 y 3 con el paso 8 y luego no realizar los pasos 7 y 9.