¿Cómo preparar diferencias claras, cuando las ediciones `git add –patch` son incómodas?

SITUACIÓN : Al hacer una request de extracción, quiero que el receptor pueda entender qué cambios hace. Encuentro que aplastarlos en un solo compromiso puede ser confuso, especialmente si:

  1. hay modificaciones en el código que también se mueven; diff lo representa como eliminación y adición al por mayor, sin resaltar las ediciones.

  2. el código se agrega a una serie de secciones similares, por ejemplo, declaraciones de casos, ifs en cascada, producciones de yacc. diff a menudo reconstruye el cambio como secciones superpuestas (por ejemplo, en lugar de agregar una sección, usa el comienzo de la sección anterior, agrega una nueva otro comienzo, luego usa el acabado de esa sección previa); agrega la nueva terminación de codean, y en algunos casos, selecciona algunas similitudes menores, luego elimina e inserta una masa de código idéntico. (Me doy count de que diff usa LCS y es sorprendentemente rápido, pero a veces su resultado es difícil de entender, incluso cuando se considera que diff no es sensible a la syntax y no puede reconocer las "secciones" de código que se ven).

Por cierto: yo uso git diff --color-words --ignore-space-change , que es genial, pero también mal reconstruye, puedo ocultar detalles – y me preocupa que el destinatario pueda usar git diff simple de git diff y ver algo bastante diferente (pueden rebuild de manera diferente).

TAREA : OK, entonces la solución obvia a esto es dividir la Solicitud de extracción en confirmaciones separadas. A veces, estas pueden ser las confirmaciones reales con las que comencé, por lo que todo lo que necesito hacer es no volver a usar la base / squash en primer lugar. Pero estoy descubriendo que incluso entonces, las diferencias pueden ser confusas (especialmente por la razón (2) anterior), y necesito separarlas más.

  1. La forma más obvia de hacerlo es usar git add --patch/-p . Sin embargo, es difícil trabajar con los parches para superponer los cambios; puede dividir e incluso eliminar los obstáculos, pero es un poco preocupante pensar en términos de invertir las diferencias cuando el cambio que desea combina sum, eliminación y código común.

  2. Lo que en realidad he hecho es editar el file directamente: borrar la parte que no quiero y confirmarla; luego, deshacer esa eliminación (con mi editor) y cometer eso. Trabajar en términos de la fuente real es mucho más claro e intuitivo que trabajar en términos de diferencias, pero parece que estoy luchando contra git y hacerlo mal (también, parece propenso a los crashs confiar en el editor deshacer).

  3. En cambio, se me ocurre en primer lugar git stash el file y preparar el primer commit borrando la parte que no quiero; luego, git stash apply para "deshacer" esa eliminación para preparar el segundo commit. Pero no estoy seguro de que puedas hacer eso en medio de una rebase (aún no lo he probado).

PREGUNTA : Me está llevando horas hacer esto … Creo que mejoraré con la práctica, pero … ¿Estoy en el path correcto? ¿Hay una mejor manera? ¿Puedes evitar diferencias mal reconstruidas en primer lugar? ¿Estoy trabajando demasiado duro para tener claridad?

(Para ser justos, esto fue muchas ediciones sobre códigos sutiles y complejos hechos hace un time, y pasar estas horas reveló una comprensión más profunda).

En function de estas respuestas , después de iniciar una database interactiva ( get rebase -i ... ) y una date de confirmación, realice una confirmación:

 git reset HEAD^ # reverts index to previous commit (not change files) # so it's as if you are just about to add and commit git stash # save git stash apply # get it back ...edit the file, deleting the changes you don't want in the first commit git add . git commit -m "...first changes..." git stash apply # get it back again (ie undo the above delete) ...(I needed to resolve a merge conflict) git add . git commit -m "...second changes..." git rebase --continue 

Una lástima que no haya una git stash copy que guarda sus cambios sin revertir. Puede haber una manera más suave de hacer esto.

Lo sorprendente para mí es que puedes usar toda la potencia de git allí mismo en medio de una rebase interactiva . Puede ignorar el antiguo commit que "se supone que debe" editar, y en su lugar agregar dos commits; puedes esconder y aplicar Probablemente necesite estudiar cómo se implementa realmente la database, y dejar de pensar en ella como una abstracción. En realidad, la página de manual de rebase tiene un encabezado para dividir las confirmaciones .

¿Por qué no utilizar los commits para deshacer los cambios (en lugar de los escondidos), ya que los tenemos? Hay dos problemas: hacer reference a la confirmación y get los files (tree de trabajo) y el índice en el estado correcto.

  1. haciendo reference al compromiso Podríamos cortar y pegar el hash del compromiso. O bien, cree una label temporal, con git tag tmp (eliminar con la git tag -d tmp ). O bien, cuente los commits n desde la twig y use branch~n . O bien, para la confirmación que la rebase está modificando en este momento, use el hash que guardó, con cat .git/rebase-merge/amend (pero incómodo y un detalle de implementación no documentado – cat .git/rebase-merge/amend información aquí ).

  2. files e índice Mi comprensión actual: el reset y el process de checkout no cambiarán HEAD cuando especifique un file (routes). Cuando se usa así, reset cambia el índice solamente; checkout cambia tanto el índice como los files. Para simplemente cambiar solo un file, puede pegarlo con git show <commit>:file > file (tenga en count la syntax impar para los files en lugar de -- ).

Poniendo todo junto:

 git checkout -b newbranch # I'm on a dev branch already; make a new one git rebase -i master # only the commits not part of master ...mark one with `edit` or `e`... git tag tmp git reset HEAD^ # changes index only, as if we had just edited ...edit myfile, deleting what is to be split into another commit... git add . git commit -m "first commit" git tag tmp2 git checkout tmp -- myfile # get file and index before above edit git reset tmp2 # ...so need to reset *index* to first commit # 1. index is same as "first commit" # 2. file is same as commit we wanted to split # (the diff is what we deleted above) git add . git commit -m "second commit" git rebase --continue git tag -d tmp tmp2 # clean up 

El segundo commit es un poco más simple si usamos 'git show', porque no necesitamos git reset tmp2 :

 git show tmp:myfile > myfile # clobber file, but not index git add . git commit -m "second commit" 

¡Es difícil saber qué está pasando en todo esto! Algunas forms de verificar el estado actual:

 git log -1 # see HEAD git diff # between files and index git diff --cached HEAD # between index and HEAD git show-ref tmp # see tag 

De todos modos, todo esto parece mucho más complicado que simplemente * deshacer * dentro de mi editor, lo cual hice en primer lugar. Pero apuesto a que esta mejor comprensión de reset y checkout será útil …