Cambia de forma retrospectiva los commit de twig y reword

Tengo este repository, origin/master

Tenía un boleto JIRA, X-123. Creé una twig llamada X-123, realicé algunos commits contra ella, que se han enviado a origin/X-123 . Y luego, una request de extracción realizada desde el origen / X-123 a la twig principal, que se fusionó.

El trabajo está completo, excepto que ahora, en retrospectiva, resulta que el trabajo que hice realmente debería haber sido contra el ticket Y-789. Idealmente quiero:

  • reformular mis compromisos de "X-123 blah" a "Y-789 blah"
  • renombrar mi sucursal de X-123 a Y-789
  • asegúrese de que la request de extracción se vincule con el ticket JIRA Y-789 en lugar de X-123

En este momento me conformaría con el último de esos; ¿algunas ideas? Como todo el trabajo ya se ha fusionado con el maestro, es posible que cierre la puerta después de que el caballo se haya cerrado. Estoy tentado de revertir todo el trabajo que hice contra X-123, luego lo repito contra Y-789, pero parece que debería haber algún paso fácil aquí (¿no puede ser así de inusual?).

Probablemente haya una forma mejor, específica de Jira, para lidiar con esto, pero he aquí cómo puedes hacerlo todo en Git.

Renombrar una twig-un nombre local-es trivial:

 git branch -m <newname> 

mientras estás en eso, o:

 git branch -m <oldname> <newname> 

Sin embargo, hacer cambios a las confirmaciones existentes es imposible; y renombrar la twig no sirve de nada. ¡Pero no desesperes! 🙂

Algunos antecedentes importantes

Primero, un pequeño aparte:

Tengo este repository, origin/master

origin/master no es un repository. Es un nombre típico para la memory de su Git: "Esto es lo que vi en otro Git, que estoy llamando origin , la última vez que llamé a ese otro Git en el teléfono de Internet y le pregunté sobre sus sucursales. tenía un master que se cometió a234b67... o lo que sea, así que a234b67... origin/master en mi repository para recordar que su master = a234b67... ".

Tu Git hace esto con cada twig que ve en ese otro Git: si tienen una twig llamada X-123 , obtienes una copy llamada origin/X-123 . Eso incluye cuando tu Git le pide a su Git que cree un X-123 . Tan pronto como digan "OK", su Git sabe que su Git ahora tiene este X-123 (¡y que su ID hash es lo que su Git les dijo que usen para crearlo!).

En segundo lugar, observemos aquí que un nombre de twig , en su repository, como master o X-123 es una forma abreviada de un nombre completo que Git llama una reference . Cualquier nombre completo que comience con refs/heads/ es una twig , por lo que su master es realmente refs/heads/master y su X-123 es realmente refs/heads/X-123 .

Estas llamadas twigs de seguimiento remoto como origin/master y origin/X-123 son references cuyo nombre completo comienza con refs/remotes/ y continúa para include el origin/ parte. Su Git simplemente cambia el nombre de los nombres de las sucursales de un Git extranjero ( refs/heads/master ) para hacer sus nombres de seguimiento remoto ( refs/remotes/origin/master ).

(Para el caso, los nombres de las tags son solo references en refs/tags/ ).

Más tarde, su Git quita los refs/heads/ o refs/remotes/ parte para fines de visualización. Es por eso que normalmente no ves estos prefijos. A veces, por la razón que sea, Git solo quita refs/ y ves remotes/origin/master . En algunos casos, conocer el nombre completo es importante. Probablemente no vengan aquí, pero vale la pena saberlo.

No puede modificar confirmaciones, pero puede copyrlas

Ya sea que cambie el nombre de su sucursal local de X-123 (refs / heads / X-123) a Y-789 (refs / heads / Y-789), el nombre en sí mismo simplemente asigna un hash de confirmación: ac0ffee4deadcafe... o lo que sea. Cambiar el nombre deja el mismo hash de confirmación en el nuevo nombre. Ahora debe hacer copys de todas las confirmaciones, excepto la fusión.

La herramienta principal que tienes para copyr confirma uno a la vez es git cherry-pick . Supongamos que crea una nueva twig Y-789 que todavía no tiene copys de las confirmaciones antiguas ni la fusión. (Estoy asumiendo que su desarrollo se separó del master según su descripción, pero si no, simplemente use el otro nombre aquí). Dibujemos esto como un fragment del gráfico de compromiso:

 ...--o--*-----m------M <-- master, origin/master \ / A--B--C--D <-- X-123, origin/X-123 

Una ronda o representa una confirmación ordinaria. Uno de ellos, * , se rellena para marcarlo: esa es la base de fusión entre lo que era el master anterior, cuando apuntaba a confirmar m , y lo que todavía es la punta de X-123 y origin/X-123 . Otro, m , tiene una letra para indicar que es donde estaba el master antes X-123 se fusionara X-123 , y el último, M , se marca para mostrar que es la confirmación de fusión, a qué master y punto de origin/master . Finalmente, cuatro de ellos tienen tags ABCD para que podamos referirnos a ellos como los originales que planeamos copyr.

Lo que queremos ahora es hacer la serie de confirmaciones Y-789 , que serán copys de la serie ABCD . Así que hacemos una nueva label, Y-789, apuntando a la base de fusión:

 ...--o--* <-- Y-789 (HEAD) |\ | ----m------M <-- master, origin/master \ / A--B--C--D <-- X-123, origin/X-123 

Como lo indica el (HEAD) , queremos estar en esta nueva twig ahora.

A continuación, debemos copyr A pero, en la copy, cambiar ligeramente su post de compromiso, de modo que en lugar de X-123 blah , diga Y-789 blah . Para hacer eso:

 git cherry-pick --edit <hash ID of A> 

Esto actualiza nuestro índice y tree de trabajo para copyr todo lo que haya cambiado en A y configurar una plantilla de post de compromiso, sin realizar la confirmación aún, luego invocar la git commit de una manera que nos permita editar el post. (Sin --edit , esto hace la confirmación. Podríamos entonces git commit --amend para arreglar su post, pero eso hace que uno se "comprometa" por lo que no es tan eficiente, aunque puede que no nos importe, "basura" los commit son baratos en Git; siga leyendo para encontrar forms de automatizar esto).

Luego solo repetimos para las confirmaciones restantes. Esto construye una nueva cadena:

 ...--o--*--A'-B'-C'-D' <-- Y-789 (HEAD) |\ | ----m------M <-- master, origin/master \ / A--B--C--D <-- X-123, origin/X-123 

Ahora podemos hacer una nueva request de extracción solicitando fusionar D' (Y-789, que empujamos para que ahora haya un origin/Y-789 ) en el master . Para alguien que sabe cómo hacerlo (ver detalles a continuación), la fusión será trivial, ya que el master ya tiene los mismos cambios, cortesía de fusionar D ; pero si esta fusión se realiza como un estricto "agregar fusión de confirmación", el resultado será el siguiente:

 ...--o--*--A'-B'-C'---D' <-- Y-789, origin/Y-789 |\ \ | ----m------M--N <-- master, origin/master \ / A--B--C--D <-- X-123, origin/X-123 

donde N es la nueva combinación de compromiso.

Ahora puede eliminar de forma segura la label X-123 (y hacerlo en el otro repository también para que git fetch --prune sus refs/remotes/origin/X-123 ), pero todas esas confirmaciones originales permanecerán en el gráfico , y tendrá el ABCD compromete para siempre.

Deshacerse de los originales es más difícil, porque existe una combinación de M . Se puede hacer, pero todos los que tienen esta fusión M que eres tú, y el otro Git en tu origin , y cualquier otro usuario de Git que haya elegido fusión compromete a M -ha para eliminar este compromiso de cada uno de sus repositorys que tiene eso.

Si haces esto, sin embargo, y luego fusionas D' en master , obtienes este gráfico:

 ...--o--*--A'-B'-C'-D' <-- Y-789, origin/Y-789 |\ \ | ----m-------N <-- master, origin/master \ A--B--C--D <-- X-123, origin/X-123 

Este gráfico se puede volver a dibujar para que sea less complicado y, de hecho, se verá como el gráfico original, excepto por el uso de Y-789 .

Si todos ahora borran sus nombres X-123 y origin/X-123 , Git eventualmente recolectará los commits de ABCD . Hasta entonces, nadie los verá normalmente: serán commits de zombies, acechando en cada copy del repository en caso de que alguien quiera resucitarlos.

Automatizando la copy de git cherry-pick

Para automatizar la copy elegante, simplemente puede usar git rebase . Lo que hace git rebase es identificar algunos sets de compromisos, en nuestro caso, ABCD , y copyrlos . Cuando lo ejecuta como git rebase -i , primero aparece una session de editor en una serie de commands de pick , cada uno representa un solo git cherry-pick . Puedes cambiar cada pick para edit es el Big Hammer que te permite ejecutar git commit --amend o git commit --amend , que simplemente te permite editar el post de confirmación, tal como lo hicimos anteriormente.

Después de hacer todos los git rebase , git rebase mueve la label de la twig. Es decir, en lugar de comenzar una nueva twig Y-789 , Git realiza todas las confirmaciones nuevas en una twig temporal (sin nombre), luego mueve el nombre de la twig actual existente para apuntar a las copys.

En otras palabras, solo correríamos:

 git checkout X-123 git rebase -i --onto $(git merge-base X-123 master) master 

para decirle a Git que comience en X-123 , encuentre (con fines de copy) confirmaciones en X-123 que no están en el master , y luego copie esas confirmaciones para que aparezcan después de fusionar base * , al mismo time que nos permite cambiar la pick por reword a reword . Una vez que todo está hecho, Git moverá el nombre X-123 para apuntar a la copy final:

 ...--o--*--A'-B'-C'-D' <-- X-123 (HEAD) |\ | ----m------M <-- master, origin/master \ / A--B--C--D <-- origin/X-123 

Tenga en count que X-123 ha movido. Ahora queremos cambiarle el nombre a Y-789 y luego a git push -u origin Y-789 para crear Y-789 en el otro Git (y por lo tanto origin/Y-789 en el nuestro, y configurarlo como nuestro nuevo upstream). No tendremos que eliminar nuestro X-123 existente, ni nuestro origin/X-123 , solo tendremos que convencer al repository en sentido ascendente para que elimine su X-123 y luego usar git fetch --prune como es habitual.

Luego haremos la request de extracción y dejaremos que quien controle la fusión se ocupe de todo.

Fusionando D'

Si fusionar D' es fácil o difícil depende de estas cosas:

  • ¿Hubo algún conflicto de fusión al fusionar D en master ?
  • Quienquiera que haga esto se fusiona como para despojarse de compromiso M ? (Tenga en count que esto tiene consecuencias para todos "aguas abajo", incluyéndolo a usted: todos deben repetir esta eliminación. Eso podría ser muy fácil, o podría ser difícil).
  • ¿Alguien que está haciendo esto sabe sobre git merge -s ours ?
  • Si eliminamos M y hay conflictos, ¿sabe quién hace esto cómo simular git merge -s ours ?

Si no hubo conflictos de fusión y no se requirieron ajustes manuales, todo es realmente fácil. Simplemente mantén o quita M como git reset --hard (para despojarlo, necesitamos git reset --hard y debemos asegurarnos de que no haya commits después de M también, y luego están todos esos problemas aguas abajo, así que asegúrate de que esto es lo que usted quiere). Luego fusiona, y listo.

Si hubo conflictos de fusión, pero puedes git merge -s ours sin despojar a M , es fácil: simplemente git merge -s ours para decir "ya hicimos la fusión correctamente, así que ignoramos por completo todos los commits en el A'-B'-C'-D' cadena al hacer el resultado de la fusión.

Si hubo conflictos de fusión y la persona que realiza la fusión está eliminando M , es un poco más difícil. La idea general es quitar M de master pero mantener el commit y su tree (por ejemplo, hacer un nombre que apunte a él, o confiar en los reflogs y simplemente copyr y pegar el hash, o lo que sea). Luego ejecute git merge , que verá los mismos conflictos de fusión; pero luego elimine el resultado de fusión y reemplácelo con el tree de la confirmación guardada. O, de manera equivalente, uno puede usar el command git commit-tree plomería para hacer esto con less alboroto, pero eso se está adentrando bastante en Git arcana.

Tenga en count que todo esto supone que no realiza ningún cambio en el tree , es decir, esa git diff de git diff de la confirmación D original y la nueva confirmación D' estaría vacía: solo los posts (y las ID hash) han cambiado, no los treees fuente asociados con los compromisos.