Crear una nueva bifurcación desde la confirmación específica en adelante

Quiero crear una nueva twig, comenzando desde una determinada confirmación. Sin embargo, quiero que todos los commits que se cometieron después sean parte de esa twig también.

Supongamos que es una branch-D con todas las confirmaciones.

 > branch-D commit-A ---- commit-B ---- commit-C ---- commit-D 

Quiero eliminar todo, desde commit-B a una nueva branch , vamos a nombrarlo branch-C , y solo mantener commit-A en la branch-D

 > branch-D commit-A > branch-C commit-B ---- commit-C ---- commit-D 

No he llevado nada a un repository remoto todavía. ¿Hay alguna forma de hacer esto?

Actualización : leí mal los diagtwigs y, como resultado, confundí los nombres de las twigs. También como señala torek, una lectura "más correcta" de sus diagtwigs incluye algunas ambigüedades. Mantendré mi respuesta en su mayor parte como está (pero con nombres de sucursales enderezados) ya que creo que transmite los principios principales; y voy a hablar un poco sobre el uso de rebase si necesita editar aún más el historial de branchC ; pero para una respuesta más detallada, vea la respuesta de torek.


Primero déjame llegar a la solución que creo que estás buscando; pero sugiero leer más allá de eso, porque hay confusión conceptual en esta pregunta que valdría la pena aclarar.

Entonces tienes ahora

 A -- B -- C -- D <--(branchD) 

Creo que quieres terminar con solo A en branchD , y una nueva twig para B , C y D Así que el primer paso es, con branchD (o D ) desprotegido, crear la nueva twig

 git branch branchC 

A continuación, mueva la twig branchD nuevamente a A

 git reset --hard HEAD~3 

(Utilicé HEAD~3 porque en este ejemplo, ese sería un nombre para A Depende del número de confirmaciones que se extraigan del historial del master . Siempre estaría bien usar la ID de confirmación (SHA) de A en lugar de HEAD~3 ).

Después de estos pasos, tienes

 A <--(branchD) \ B -- C -- D <--(branchC) 

que se parece a lo que describiste, pero no logramos llegar de la forma que la descripción implicaba.

No moví commits; Moví twigs. Esto es mucho más sencillo en git, y refleja que los commits no "pertenecen" a las twigs en git como lo hacen en algunos sistemas. En consecuencia, la frase "ramificar en una confirmación previa pero include los siguientes commits en esa twig" realmente no tiene sentido en git.

Las ambigüedades en los diagtwigs de antes y después de la pregunta dejan abierta la posibilidad de que no haya entendido exactamente qué branchC debe include en su historia. Si branchC no incluye A , entonces debes reescribir B , C y D , y lo más fácil es hacerlo con git rebase -i . Después de crear branchC y mover branchD podrías decir

 git rebase -i branchD^ branchC 

Aquí branchD^ es un nombre posible para "la confirmación antes de A Si existe tal confirmación, puede tener otros nombres, quizás el master , o ciertamente su identificación de confirmación. Si no hay tal confirmación, entonces supongo que podría decirse

 git rebase -i --root branchC 

(pero en ese escenario, tratar de eliminar A de la historia de branchC probablemente no tiene sentido, así que dudo que eso es lo que está pasando aquí).

El command rebase mostrará un editor de text con una list TODO con una input para cada confirmación. Puede eliminar la input para commit A de la list, luego save y salir. Esto no molestará a branchD – aún señalará A – pero creará un nuevo historial para branchC al copyr B , C y D entonces tu tendrías

 x -- B' -- C' -- D' <--(branchC) \ A <--(branchD) 

En cualquier caso, hacer lo que quiera significa eliminar B , C y D del historial de branchD . Mencionaste que estas confirmaciones no se han push , por lo que no debería ser un problema; pero esa es una distinción importante a tener en count. Cada vez que desee eliminar un compromiso del historial de una sucursal, es más fácil hacerlo si la sucursal del control remoto aún no conoce las confirmaciones en cuestión.

Lo peculiar de Git es que los nombres de las sucursales no afectan a lo que está en el historial de commits, excepto que le permiten a usted, y a cualquier otra persona, que es muy importante, encontrar los commits. El secreto es que en Git, un compromiso es, potencialmente, en muchas twigs al mismo time .

Es importante destacar, sin embargo, que ninguna confirmación, una vez hecha, puede cambiarse alguna vez. Se puede copyr en una confirmación (nueva, ligeramente diferente), pero no se puede cambiar.

Además, aunque se puede forzar a cualquier nombre de sucursal a apuntar a cualquier confirmación, existe una "dirección normal" para el movimiento del nombre de la sucursal: normalmente, solo avanzan . Avanzar, como veremos en un momento, significa que cualquier compromiso que utilizaron para señalar, todavía está "en" esa twig. Así que una vez que hayas entregado tus compromisos a otra persona, algún otro Git, y les pidas que llamen a los que se comprometen con la branch-D , es difícil hacer que el otro Git se retracte de eso. Por lo tanto:

No he llevado nada a un repository remoto todavía. ¿Hay alguna forma de hacer esto?

Sí, y puede haber una manera muy fácil: "no has empujado nada todavía" significa que eres el único que tiene estos compromisos, así que, como los encuentres, así es como todos los encuentran, porque tú eres " todo el mundo". 🙂 No es necesario que alguien más cambie nada.

Siempre y cuando ya estén dispuestos de la manera que desea, solo necesita reorganizar la forma en que encuentra estos compromisos.

Vamos a dibujar su serie de commits existente como "Git Way":

 ... <--A <--B <--C <--D <-- branch-D 

En Git, cada compromiso se identifica de manera única por su ID de hash. Estas cosas son grandes y feas y aparentemente aleatorias (aunque en realidad son completamente deterministas), por lo que casi nunca las usamos, o usan una forma abreviada como d1c9d3a . En lugar de eso, llamemos al último compromiso D

Dentro del compromiso D , hay otra identificación hash, identificando el compromiso principal de D Digamos que es c033bae , pero llamemos a "commit C ". Entonces decimos que D apunta a C

De manera similar, C señala a B , que apunta a A , lo que apunta a … bueno, tal vez la sugerencia de compromiso del master -no dijiste, pero supongamos que por ahora:

 ...--o--o <-- master \ A--B--C--D <-- branch-D 

Esta es una forma más compacta de dibujarlos. Los compromisos siempre, necesariamente, apuntan hacia atrás , por lo que realmente no necesitamos las flechas internas, sabemos que siempre retroceden. Sin embargo, los nombres de las branch-D , como master y branch-D … bueno, podemos hacer que estos apunten a cualquier parte . Lo que queremos hacer es hacerles apuntar a la "comisión de punta" de una twig.

Git encuentra commits comenzando desde el que señala el nombre de la twig: D , para la branch-D , o la final o en la primera fila para el master . Luego mira al padre del compromiso actual, para retroceder. Luego mira al padre del padre, y así sucesivamente. Por lo tanto, las dos confirmaciones se encuentran tanto en el master como en la branch-D : podemos encontrarlas comenzando desde el master , o podemos encontrarlas comenzando desde la branch-D y trabajando hacia atrás cuatro pasos.

Esto significa que la image que queremos se ve, quizás, así:

 ...--o--o <-- master \ A <-- branch-D \ B--C--D <-- branch-C 

Aquí, los commits que están en master también están en ambas twigs, y commit A , que ahora es la punta de la branch-D , todavía está en la branch-C también. Simplemente no es la punta de la branch-C más.


Por otro lado, tal vez la image que queremos sea así:

  ?--?--? <-- branch-C / ...--o--o <-- master \ A <-- branch-D \ B--C--D <-- ??? 

Es decir, tenemos que responder una pregunta. El nombre branch-C señalará algún compromiso particular. Cuando retrocedamos tres pasos, ¿deberíamos llegar al compromiso A ? ¿O deberíamos llegar en su lugar al último commit en master ?


Si la primera image es la correcta, la respuesta es fácil: crea el nuevo nombre, branch-C , apuntando a cometer D ; luego fuerce el nombre existente branch-D para volver a cometer A Para hacer esto:

 git branch branch-C branch-D # copy the hash ID from branch-D to new branch-C 

y luego, dependiendo de qué twig está desprotegida en este momento, ya sea:

 git reset --hard <hash-of-A> # move current branch; re-set index and work-tree 

o:

 git branch -f branch-D <hash-of-A> # force branch-D to point to A 

Tenga en count que antes de usar git reset --hard , es una buena idea asegurarse de no tener nada modificado que quiera save. Si bien las confirmaciones son más o less permanentes (puede recuperarlas, normalmente durante al less 30 días, incluso si las git reset --hard de todos los nombres de las sucursales), el índice y el tree de trabajo que git reset --hard clobbers no lo hacen .

Sin embargo, si desea esa segunda image, si desea que el padre de la confirmación B sea ​​algo distinto a la confirmación A , tendrá que copyr la confirmación B en una nueva confirmación diferente, que es "como B , pero …" . Las diferencias entre el B original y la copy includeán el ID del hash padre cambiado.

Para eso , necesitarás usar git cherry-pick o un equivalente (como git rebase , que básicamente es una operación de recolección en bloque que copy muchas confirmaciones). Para hacer eso, podrías:

 git checkout -b branch-C master 

dando:

 ...--o--o <-- branch-C (HEAD), master \ A--B--C--D <-- branch-D 

y luego ejecute tres commands git cherry-pick para copyr B , C y D ; o más simple: esto usa la notación <commit>~<number> :

  git cherry-pick branch-D~3..branch-D 

para copyr los tres a la vez. Esto produce:

  B'-C'-D' <-- branch-C (HEAD) / ...--o--o <-- master \ A--B--C--D <-- branch-D 

En este punto, es seguro forzar a la branch-D a apuntar a cometer A :

 git branch -f branch-D branch-D~3 

Puede hacerlo mediante ID de hash, como en los ejemplos anteriores:

 git branch -f branch-D <hash-of-A> 

Todo lo que estamos haciendo con la branch-D~3 es decirle a Git: contar tres pasos principales, uno a la vez . Así que empezamos en D , contamos un paso atrás hacia C , contabilizamos otro paso hacia B y contabilizamos un tercer paso hacia A

Puedes usar:

 git checkout hashOfCommitA 

entonces

 git checkout -b NewBranchName 

Ahora tienes una nueva twig con commit A. Luego puedes volver a cometer D

 git checkout nameOfFirstBranch 

y luego revertir commit A desde esta twig

 git revert hashOfCommitA 

Ahora tienes twigs en una de ellas, solo hay commit A while en la otra son commits b, c y d.

La forma más directa de hacerlo sería crear 2 nuevas twigs fuera de la confirmación antes del commit-A. A continuación, elige con precisión cherry-commit commit-A a 1 branch y cherry-pick confirma B, C, D a 2nd branch.