¿Cómo puedo revertir las líneas comprometidas para convertirme en cambios no instalados?

Mucho más comúnmente de lo que me gustaría admitir, comprometeré algunas líneas / lockings que no tenía la intención de hacerlo. Y quiero deshacer eso.

Pero entendamos qué significa deshacer los cambios que no pretendía comprometer. No significa eliminar los cambios. Literalmente, deshacer los cambios de línea comprometidos significa que hay una confirmación que los elimina, pero los cambios se conservan como "sin instancia".

Por favor, no recomiendes resetear la cabeza porque eso NO cumple la tarea específica de deshacer "algunas líneas / trozos" (pero no otras). Entiendo que lo que espero hacer aquí es un poco más complejo que eso, ya que implica un compromiso que revierte un set específico de cambios (pero no todos los cambios en ese compromiso), y luego rehace esos cambios, pero no como escenario.

Dudo que sea la única persona que alguna vez haya querido hacer esto.

¿Hay alguna manera bastante fácil de hacerlo en git? ¿Qué tal en SourceTree?

Estoy bastante seguro de que estás buscando el -p de git checkout -p que te permite

Seleccione de forma interactiva trozos en la diferencia entre <tree-ish> (o el índice, si no se especifica) y el tree de trabajo

TL; DR

Probablemente debería reiniciar, probablemente con --soft , luego use git checkout -p , luego git commit . Eso generalmente será más simple y fácil. Pero no tienes que hacerlo de esta manera.

Una larga explicación (con algunas imágenes gráficas) sigue.

Por qué todo el mundo te dice que git reset

Una vez que haya realizado una confirmación con algunos cambios orderados , se almacenan permanentemente de esa manera en esa confirmación. Retrocedamos un poco, porque Git no almacena files de la forma en que mucha gente piensa que lo hace.

Por ejemplo, considere este pequeño repository con solo tres commits. Sus identificaciones reales son algunas cadenas de hash incomprensibles; por simplicidad, los he labeldo de A a C lugar:

 A <-B <-C <-- master 

El nombre master contiene la identificación hash de commit C El Commit C contiene el ID de hash de su commit padre B , y el commit B contiene el hash ID de su padre commit A Como A es la primera confirmación (una confirmación raíz ), no tiene padre, y Git dejará de recorrer el historial después de mostrar la confirmación A

También decimos que el master apunta a C , C apunta a B , y así sucesivamente.

Lo que hay en cada uno de estos tres commits es una serie de instantáneas de tu fuente.

Hacer un nuevo commit simplemente significa hacer un commit D que apunta hacia C , y luego cambiar el nombre master para que apunte a D :

 A <-B <-C <-D <-- master 

Este nuevo compromiso es permanente e inmutable.

Ahora, cada confirmación es una instantánea completa e independiente de toda la fuente. Commit D tiene todos los files exactamente en la forma (en etapas) que tenía cuando ejecutó git commit . Commit C también tiene todos los files exactamente en la forma (en etapas) que tuvo cuando usted, o quien sea, ejecutó el git commit que creó C Para ver D como cambios (de C ), Git debe extraer todo C , luego extraer todo D y luego comparar los dos para ver qué es diferente.

Lo mismo vale para lo que hay en su área de preparación y tree de trabajo en este momento, excepto por el hecho de que estos son maleables (no permanentes). Es decir, el área índice / estadificación contiene una confirmación propuesta , y el tree de trabajo contiene los files en los que realmente puede trabajar (de ahí el nombre "tree de trabajo").

Cuando Git te dice que has organizado cambios, lo que Git está haciendo es comparar lo que hay en tu índice / área de transición con lo que está en la confirmación actual, como por ejemplo, comprometer D Lo que sea diferente, Git se presenta como un "cambio por etapas".

Cuando Git te dice que tienes cambios sin monitorizar, lo que hace Git es comparar lo que hay en tu tree de trabajo con lo que está en tu área de índice / estadificación. Lo que sea diferente, Git se presenta como un "cambio sin escenario".

Ahora, digamos que usted hizo D -su pregunta original sugiere que usted ha hecho esto-, pero usted tuvo más elementos orderados, es decir, copydos en el área de índice / estadificación, en el momento que había previsto. Su set permanente de files almacenados en D tiene un formulario que no pretendía.

El índice / área de ensayo, en este punto, es bastante similar a como estaba cuando ejecutó git commit . Entonces, ¿qué hay en las coincidencias D confirmación (exactamente) lo que está en el área de indexing / estadificación, lo que significa que no se organiza nada para la confirmación.

Dado que el tree de trabajo es su propia entidad separada, lo que hay en el tree de trabajo ahora mismo es lo que tenía antes de que usted hiciera D , por lo que los cambios no sondeados aún no están registrados. Pero los "cambios por etapas" se han convertido en una instantánea completa.

Esto significa que necesitas deshacerte del commit D , y ahí es donde entra el git reset . Si fueras de alguna manera hacer esto y no cambiar nada más , de modo que el master del nombre apunte a cometer C , obtenemos este gráfico:

  D [abandoned] / A--B--C <-- master 

Git ahora comparará lo que hay en C con lo que está en su área de índice / etapas, y le mostrará cosas "puestas en escena para la confirmación". De hecho, se mostrará como en etapas, exactamente lo que mostró como organizado justo antes de que hicieras D , porque "todo en el índice" (el siguiente compromiso propuesto) es lo que estaba allí cuando hiciste D

Realmente quieres deshacerte de este compromiso

Dado que las confirmaciones no se pueden modificar, si deja D en su lugar y agrega más confirmaciones, tendrá, en su historial de repositorys, esta instantánea no deseada. Así que tienes que descartarlo, pero no necesariamente tienes que hacer esto con el git reset . Sin embargo, puede ser más fácil si lo haces.

(En todas partes digo "tengo que hacerlo", por supuesto es opcional. Si quieres, puedes dejar intacto el commit D inútil / malo / negativo, pero sospecho que no tienes intención de hacerlo).

Cambiar lo que se escenifica

Ahora veamos cómo hacer que algunos cambios pasen desapercibidos.

Supongamos que haces este reinicio, que es en realidad git reset --soft HEAD^ o git reset --soft HEAD~1 . Esto le dice a Git que mueva al master hacia atrás un paso, sin tocar el área de índice / transición, y sin tocar el tree de trabajo. Ahora puedes usar git checkout -p -- <path> , como en la respuesta de AnimiVulpis . Esto le permitirá aplicar pequeños cambios a las copys de los files en el índice.

Si no hiciste el git reset , aún podrías ejecutar git checkout -p , pero querrías agregar el hash ID de commit C , o alguna forma de decirle a Git que mire dentro de commit C Eso podría ser tan simple como git checkout -p HEAD^ <path> .

Si haces cualquiera de estos commands git checkout -p (con o sin el git reset --soft inicial de git reset --soft ), puedes get la copy del file en el índice para tener el formulario que deseas. Sin embargo, es muy difícil determinar si tiene la forma que desea porque no puede ver la versión del file que está en el índice .

Viendo lo que está en el índice

Para ver qué hay en el índice, normalmente ejecutará git diff --cached (para comparar HEAD con index / stage-area) o git diff (para comparar index to work-tree). Esto no le muestra los contenidos directamente; en su lugar, le muestra cómo esos contenidos difieren de los contenidos de HEAD -que probablemente conozca- o de los contenidos del tree de trabajo, que puede ver.

Estos, de nuevo, son los "cambios por etapas" (contenido del índice en comparación con los contenidos del compromiso HEAD ), o los "cambios sin instancia" (contenido del tree de trabajo en comparación con el índice de contenido). Pero están en relación con el compromiso HEAD . Si aún no ha ejecutado git reset , debe tener cuidado de agregar "commit C versus commit D " to any changes shown as "commit D` vs index".

Así que esta es la razón por la que realmente querrás comenzar con el git reset --soft .

El valor pnetworkingeterminado para el git reset es --mixed

Si ejecuta git reset HEAD^ , sin --soft , Git borrará (reiniciará) su índice actual / área de ensayo, reemplazándolo al por mayor con el contenido de la confirmación anterior.

Git no tocará el tree de trabajo, por lo que si ha construido cuidadosamente el índice desde el tree de trabajo, puede volver a hacer todo ese trabajo ahora. Pero si eso fue mucho trabajo, probablemente no quieras hacer un restablecimiento --mixed .

Aún así, es una opción, y algo útil para recordar si accidentalmente (o en desesperación) ejecuta un git reset --mixed o pnetworkingeterminado.

Una vez que tenga el índice arreglado, puede comprometer

Una vez que haya configurado el área de indexing / estadificación de la manera que desea, ahora puede hacer la confirmación que tenía previsto hacer todo el time.

Si ha dejado la confirmación D en su lugar, para que el gráfico de confirmación sea:

 A--B--C--D <-- master 

puedes ejecutar git commit --amend lugar de un git commit regular de git commit . Lo que hace esto es hacer su nueva confirmación E , y apuntar hacia ella, casi de la manera normal, excepto por una cosa: la nueva comisión E tendrá, como su padre, la confirmación C Esto tiene el efecto de empujar a D hacia un lado y abandonarlo:

  D [abandoned] / A--B--C--E <-- master 

Pero si hiciste un git reset antes, ya sea que --soft o por defecto --mixed entonces ya has cometido un --mixed D hacia un lado y abandonado, por lo que puedes agregar el commit E de la forma habitual.

Solución viable: revés hunk ?

Ten cuidado con los errores de revés de SourceTree y GitKraken … tenían errores donde el file completo se revertiría si intentas revertir un trozo. Veo que GitKraken lo parchó el 9 de octubre de 2017 con la versión 3.1.1. pero no sé sobre el estado de SourceTree.