¿Cómo saber qué cambios en una twig después de las fusiones del maestro?

Bifurqué al master por una twig. De vez en cuando, fusiono el master en mi twig para get actualizaciones.

  /--B--D--G--H--J--L--M--> dev (HEAD) / / / -----A--C--E--F--I--K---> master 

¿Cómo puedo mostrar los cambios solo en mi sucursal, excluyendo los de las fusiones? Es decir, mostrar diferencia solo en los compromisos B , D , H , L , M

git diff A no funciona porque incluye cambios combinados desde el master .

Por cierto, agradeceré si alguien conoce una forma rápida de encontrar A sin tener que desplazarse por el logging.

No está claro para mí precisamente lo que estás buscando. Pero ten en count que:

  • Confirma instantáneas de la tienda. Esto significa que commit K tiene un tree fuente completo que es independiente de lo que está en commits A , B , C , y así sucesivamente. Del mismo modo, commit J tiene una instantánea completa, independientemente de lo que esté en A , K o cualquier otra confirmación.

    (La palabra "independiente" aquí significa que si le pides a Git que recupere el compromiso J , no importaría si de alguna manera hubieras logrado alterar el compromiso K después de hacer J En realidad no es posible alterar ningún compromiso, nunca; git commit --amend parece cambiar un compromiso, pero realmente no lo hace).

  • Usando git diff en dos confirmaciones específicas, Git extrae cada una de las dos instantáneas y las compara.

Diferencias simples

Por lo tanto, git diff KM le mostrará qué es diferente entre la punta actual del master (commit K ) y la punta actual del dev (commit M ).

También puedes deletrear este git diff master dev .

Eso puede ser lo que quieres ver (una vez más, no es muy claro para mí). Entonces respuesta 1: git diff master dev .

Múltiples diferencias independientes

Por otro lado, tal vez lo que quiere es mostrar un diff para el commit B , un diff para el commit D , un diff para el commit H , un diff para el commit L , y un diff para el commit M Es decir, desea ver cada compromiso sin fusión, uno a la vez, en comparación con su padre (único).

Podrías hacer git diff AB , y git diff BD , y así sucesivamente. Pero también puedes git show B y git show D , y así sucesivamente. Esto le muestra el compromiso como un cambio, en lugar de una instantánea.

Es posible que se pregunte cómo es posible si confirma instantáneas de tienda, en lugar de cambios. (Esto hace que muchas personas se levanten, ya que la mayoría de los otros sistemas de control de versiones sí almacenan los cambios). La respuesta a esta aparente contradicción es que el git show busca el mismo gráfico que usted dibujó.

Mira de nuevo el compromiso B ¿Qué compromiso viene antes? Es decir, ¿qué compromiso está a su izquierda, siguiendo las líneas hacia la izquierda? Solo hay un ancestro posible para el compromiso B , y ese es su padre soltero, cometer A Así que git show B :

  1. extrae la instantánea para la confirmación A , luego
  2. extrae la instantánea para el compromiso B , luego
  3. diffs esas dos instantáneas.

De forma similar, solo hay un ancestro (padre) inmediato para el compromiso M , y ese es el compromiso L Así que git show M :

  1. extrae la instantánea para L , luego
  2. extrae la instantánea para M , luego
  3. diffs esas dos instantáneas.

Si esto es lo que quiere, la pregunta interesante es: ¿cómo se encuentran los ID de cada confirmación en la secuencia B , D , H , L y M ? La respuesta a esto es un poco complicado, pero el command key es git rev-list , que es esencialmente el mismo command que git log . Lo que estos commands ( git log y git rev-list ambos) hacen es recorrer el gráfico de confirmación . Es decir, elige un punto de partida, en este caso, asigna M , la punta del desarrollador, y dile a Git que retroceda en los commits, mirando a los padres de cada commit.

El problema es que cuando aciertas un commit de fusión, como commit J , Git regresa con todos sus padres. Desea restringir que Git encuentre solo el padre que era la punta del desarrollador de twig cuando realizó la fusión. Git tiene una bandera para esto, deletreado --first-parent . Esto le dice a git rev-list que siga solo al primer padre de cada commit de fusión.

También queremos omitir las fusiones, por lo que podemos agregar --no-merges (esto no afecta el process de retroceso, solo limita los ID de revisión impresos para excluir las fusiones).

Esto lleva a la respuesta 2a: git rev-list --first-parent --no-merges ^A dev (llegaremos a la parte "no A" más adelante).

Ahora, realmente usar esto con git rev-list es un poco molesto, porque ahora tenemos que tomar cada identificación de commit y luego ejecutar git show en ella. Sin embargo, hay una manera mucho más sencilla, porque git rev-list y git log son esencialmente el mismo command , y git log -p muestra cada commit como un parche , haciendo casi lo mismo que git show .

Esto lleva a la respuesta 2b: git log -p --first-parent --no-merges ^A dev . No necesariamente necesitamos el --no-merges aquí tampoco; vea abajo.

Merge commits y combs diffs

La única cosa especial que hace git show , que no es git diff , es manejar el caso de mostrar una confirmación de fusión , como commit J Commit J tiene dos padres, es decir, confirma H y K (dicho sea de paso, esto implica que usted hizo commit K antes de hacer commit J :-)). Si ejecuta git diff HJ , Git extraerá las instantáneas de H y J y las comparará. Si ejecuta git diff KJ , Git extraerá las instantáneas de K y J y las comparará. Pero si ejecuta git show J , Git hará lo siguiente:

  1. extraer la instantánea para H , luego
  2. extraer la instantánea para K , luego
  3. extraer la instantánea para J (la fusión), y finalmente
  4. producir lo que Git llama una diferencia combinada .

La diferencia combinada del paso 4 intenta mostrar, de forma compacta, los cambios de H y K a J Al compactar los cambios, Git arroja cualquier file donde la versión en H o K coincide con la versión en J

Es decir, supongamos que el file README.txt tiene algún cambio de H a J Pero supongamos que README.txt es el mismo en K y J En otras palabras, cuando hiciste la git merge , estabas recogiendo cambios de K para hacer J , y no hubo cambios en README.txt desde el otro lado de la fusión. Esto significa que README.txt coincide exactamente con un "lado de input" y, por lo tanto, la diferencia combinada ignora por completo el file .

Esto significa que las diferencias combinadas a menudo no muestran nada en absoluto, aunque la combinación detectó algunos cambios en la nueva instantánea. Para ver esos cambios, debe hacer dos diffs, no solo uno. Debe hacer una diferencia de H a J , y otra diferencia de K a J , en lugar de confiar en la diferencia combinada.

Al usar git log -p , también puede ver los diffs combinados para las fusiones, agregando -c o --cc a las opciones. Pero si no pides esto, lo que hace Git es realmente ridículamente simple: simplemente no se molesta en mostrar una diferencia en absoluto .

Entonces esto lleva a la respuesta 2c: git log -p --first-parent ^A dev . Todo lo que hicimos fue eliminar el --no-merges : ahora veremos el post de logging de cada fusión, pero no la diferencia.

¿Qué es esto? ¿ ^A cosa?

Esto también se relaciona con tu otra pregunta:

Por cierto, agradeceré si alguien conoce una forma rápida de encontrar A sin tener que desplazarse por el logging.

La respuesta a esto es hacer un nombre simbólico para commit A Encuentre su ID una vez y luego elija un nombre, como dev o master (¡pero no los use ya que están en uso!).

Eso es todo lo que dev y master son : son solo nombres simbólicos para los commits, más la propiedad adicional de que, como nombres de las twigs, puedes git checkout el nombre simbólico y terminar "on" en la twig. También puede darle a A una sucursal. Tendrá que asegurarse de no git checkout esta twig y realizar commits en ella, ya que si hace eso, crecerá una nueva twig, en lugar de simplemente dejar el nombre de la twig apuntando a cometer A

Alternativamente, puede hacer un nombre de label que apunte a cometer A Eso es casi exactamente lo mismo que un nombre de twig. Las dos diferencias son:

  1. Es un nombre de label: no puede verificarlo como una twig, y ​​por lo tanto no puede cambiarlo accidentalmente.
  2. Es un nombre de label: si git push --tags lo enviarás en sentido ascendente como una label, y luego todos los demás lo tendrán también.

En este caso, el punto 1 está a su favor y el punto 2 probablemente no, por lo que depende de usted si la ventaja (no puede cambiarlo accidentalmente) justifica el riesgo (podría publicarlo accidentalmente).

Si tiene la ID de A , entonces, puede:

 $ git tag A <id-of-commit-A> 

y ahora tienes el nombre A (Más tarde puede git tag -d A para eliminarlo, aunque si lo publicó accidentalmente, es probable que continúe recuperándolo de su flujo ascendente).

Volviendo a la pregunta de la cadena ^A en los commands de git log , all ^A hace es decirle a git rev-list (y por lo tanto a git log ) que pare de caminar a través del gráfico de compromiso al llegar al compromiso A La confirmación tampoco se muestra (para git rev-list , no impresa; para git log , no se muestra en la salida de logging). El prefijo ^ symbol es la abreviatura de "not", es decir, "get me all commits alcanzable desde dev , pero no accesible desde A ". Al agregar el primer padre, Git recorre solo el primer padre de cada combinación, de modo que no entramos en commits fusionados de master .

(La syntax de ^A dev puede escribirse A..dev . Tenga en count que esto funciona con ambos git log y git rev-list , pero significa algo muy diferente para git diff )