Cómo hacer que una twig existente sea huérfana en git

¿Hay alguna manera de hacer que una twig existente sea huérfana en git?

git checkout --orphan parece solo crear un nuevo huérfano?

¿Entiendo bien que quieres que la sucursal huérfana ya tenga un historial de compromisos? Si es así, aquí hay una solución.

Primero debe elegir un compromiso para comenzar la nueva sucursal en. En mi ejemplo, esto será HEAD~2 , sha1=df931da .

Diga, tenemos un repository simple. git log --oneline --graph --decorate muestra lo siguiente:

 * 4f14671 (HEAD, master) 4 * 1daf6ba 3 * df931da 2 * 410711d 1 

Ahora, ¡acción!

 # Move to the point where we want new branch to start. ➜ gitorphan git:(master) git checkout HEAD~2 

Aquí y más adelante, la parte ➜ gitorphan git:(master) es la request de zsh y no una parte del command.

 # make an orphan branch ➜ gitorphan git:(df931da) git checkout --orphan orphanbranch Switched to a new branch 'orphanbranch' # first commit in it ➜ gitorphan git:(orphanbranch) ✗ git commit -m'first commit in orphan' [orphanbranch (root-commit) f0d071a] first commit in orphan 2 files changed, 2 insertions(+) create mode 100644 1 create mode 100644 2 # Check that this is realy an orphan branch ➜ gitorphan git:(orphanbranch) git checkout HEAD^ error: pathspec 'HEAD^' did not match any file(s) known to git. # Now cherry-pick from previous branch a range of commits ➜ gitorphan git:(orphanbranch) git cherry-pick df931da..master [orphanbranch 7387de1] 3 1 file changed, 1 insertion(+) create mode 100644 3 [orphanbranch 4d8cc9d] 4 1 file changed, 1 insertion(+) create mode 100644 4 

Ahora, la twig orphanbranch tiene una instantánea del tree de trabajo en df931da en una única confirmación y se compromete aún más tal como estaban en el maestro.

 ➜ gitorphan git:(orphanbranch) git log --oneline 4d8cc9d 4 7387de1 3 f0d071a first commit in orphan 

Estás en lo correcto con respecto a la git checkout --orphan crea solo nuevas twigs huérfanas. El truco es que este process deja el índice sin perturbaciones. Por lo tanto, la respuesta de Nick Volynkin funcionará, siempre y cuando tu Git no sea demasiado antiguo.

Si desea conservar el post de confirmación original, puede replace su:

 $ git commit -m'first commit in orphan' 

con:

 $ git commit -C master~2 

Si tu Git es lo suficientemente viejo como para que no tengas el git checkout --orphan , es mejor que lo hagas:

 $ commit=<hash> # or, eg, commit=$(git rev-parse master~2) $ git branch newbranch $( \ git log --no-walk --pretty=format:%B $commit | \ git commit-tree -F - "${commit}^{tree}" \ ) $ git checkout newbranch $ git cherry-pick $commit..master # may want -x; see below 

donde eliges el punto de partida de git log o usando la syntax ~ con un nombre de twig existente (esto continúa usando master~2 como en la respuesta de Nick).

Si todo lo que quería era una receta, eso debería ser el truco, pero si quiere saber qué está sucediendo y por qué funciona (y cuando no funciona), siga leyendo. 🙂

Cosas que debes saber sobre sucursales

Antes de continuar, parece una buena idea definir algunos elementos y describir lo que está sucediendo.

Nombres de twigs frente al gráfico de confirmación

Primero, hagamos una distinción clara entre un nombre de twig, como master o newbr , y varias partes del gráfico de confirmación . El nombre de una twig simplemente apunta a una confirmación, designada como sugerencia de punta o punta de twig , dentro del gráfico:

 *--o--o---o--o <-- master \ / o--o--o--o <-- brA \ o <-- brB 

Este gráfico tiene tres sugerencias de twigs, apuntadas por master , brA y brB . La ascendencia de la punta de brB , por ejemplo, se remonta en una línea ondulada, siempre moviéndose hacia la izquierda y algunas veces hacia arriba, hacia la (única) confirmación raíz * (a diferencia de todas las otras confirmaciones no root). El hecho de que commit * no tenga commits a su izquierda -ningún padre se compromete a apuntar-a-lo que lo convierte en un commit raíz.

Esta confirmación raíz está en todas las twigs. Otros commits están en múltiples twigs también. Hay un commit de fusión en master , que trae commits de brA in, aunque brA tiene dos commits que master no hace, por ejemplo. Para seguir al master return a la raíz, debe ir directamente a la izquierda, y hacia abajo y a la izquierda en la fusión, y luego hacia atrás y hacia la izquierda, donde brA divide.

Tenga en count que podemos tener múltiples nombres de sucursales que apunten a una única confirmación o nombres de twigs que apunten a confirmaciones de "sugerencia" que estén integradas en otra twig:

 *--o--o---o--o <-- master \ / o--o--o <-- brA \ o <-- brB, brC 

Aquí hemos "rebobinado" branch brA por una confirmación, de modo que la confirmación de la fila central del lado derecho es la punta de brA , aunque se trata de una confirmación desde la punta de brB . Hemos agregado una nueva twig, brC , que apunta al mismo compromiso que brB (por lo que es un consejo dos veces, por así brB ; esperemos que este compromiso no sea un consejo en el sentido británico-inglés de la palabra "basura"). : "ugh, este compromiso es un consejo absoluto ! ").

El DAG

El gráfico tiene una serie de nodos o , cada uno de los cuales apunta a algunos padres que generalmente están a su izquierda. Las líneas (o flechas, realmente) que conectan los nodos son bordes dirigidos: calles unidireccionales o líneas de ferrocarril, si se quiere, conectando nodos secundarios en el gráfico a sus padres.

Los nodos, más los enlaces de borde dirigido de niño a padre, forman el gráfico de compromiso . Debido a que este gráfico está dirigido (de niño a padre) y acíclico (una vez que abandona un nodo, nunca puede volver a él), esto se denomina G raf cíclico D A Dirigido o DAG. Los DAG tienen todo tipo de buenas properties teóricas, la mayoría de las cuales podemos ignorar para esta respuesta SO.

Los DAG pueden tener subgrafos desconectados

Ahora consideremos este gráfico alternativo:

 *--o--o---o--o <-- master \ / o--o--o <-- brA *--o--o--o <-- orph 

Esta nueva twig, cuya sugerencia se llama orph , tiene su propia raíz y está completamente desconectada de las otras dos twigs.

Tenga en count que las raíces múltiples son una condición previa necesaria para tener subcharts disjuntos (no vacíos), pero dependiendo de cómo quiera ver estos charts, pueden no ser suficientes. Si fuéramos a fusionar (el commit de punta de) brA en orph 1 obtendríamos esto:

 *--o--o---o--o <-- master \ / o--o--o <-- brA \ *--o--o--o---o <-- orph 

y los dos "fragments de charts" ahora están unidos. Sin embargo, existen sub-charts (como los que comienzan con orph^1 y brA , los dos padres de orph ) que son disjuntos. (Esto no es particularmente relevante para crear twigs huérfanas, es algo que debes entender sobre ellas).


1 Modern Git rechaza un bash casual de hacer tal fusión, ya que las dos twigs no tienen una base de fusión. Las versiones anteriores de Git hacen la fusión, no necesariamente con resultados razonables.


git checkout --orphan

La twig orph es el tipo de twig que hace la git checkout --orphan : una twig que tendrá una raíz nueva, desconectada.

La forma en que llega es hacer un nombre de twig que apunta a no comprometerse en absoluto. Git llama a esto una "twig no nacida", y las sucursales en este estado tienen solo una especie de existencia a medias, porque Git filtra la implementación.

Ramas por nacer

Un nombre de sucursal, por definición, siempre apunta a la confirmación de la mayor cantidad de sugerencias en esa twig. Pero esto deja a Git con un problema, especialmente en un nuevo repository totalmente nuevo que no tiene ningún compromiso: ¿dónde puede master punto?

El hecho es que una twig no nacida no puede apuntar a ninguna parte, y como Git implementa los nombres de las twigs registrándolas como un par de <nombre, ID de compromiso> 2 , simplemente no puede registrar la twig hasta que haya una confirmación. La solución de Git a este dilema es hacer trampa: el nombre de la sucursal no entra en absoluto en los loggings de sucursal, sino en el logging HEAD .

The HEAD , en Git, registra el nombre de la sucursal actual. Para el modo "HEAD separado", HEAD registra la identificación de confirmación real, y de hecho, así es como Git determina si un repository / tree de trabajo está en modo "HEAD separado": si su file HEAD contiene un nombre de twig, no es separado, y si contiene una identificación de compromiso, se separa. (No se permiten otros estados).

Por lo tanto, para crear una "twig huérfana", o durante ese período incómodo cuando todavía no hay compromiso para el master , Git almacena el nombre en HEAD , pero en realidad no crea el nombre de la twig todavía. (Es decir, no hay input en .git/refs/heads/ para ello, y no hay línea en .git/packed-refs para él).

Como efecto secundario peculiar, esto significa que solo puedes tener una twig no nacida. El nombre de la twig no nacida se almacena en HEAD . Verificando otra twig, con o sin --orphan , o cualquier commit por ID – cualquier acción que actualice HEAD – elimina todos los rastros de la twig no nacida. (Un nuevo git checkout --orphan supuesto, lo reemplaza con el rastro de la nueva twig no nacida).

Una vez que haces un primer compromiso, la nueva twig surge, porque …


2 Con references "desempaquetadas", el nombre es solo una ruta en el sistema de files: .git/refs/heads/master . El ID de compromiso es simplemente el contenido de este file. Las references empaquetadas se almacenan de forma diferente, y Git está desarrollando otras forms de manejar la asignación de nombre a ID, pero esta es la más básica y todavía se necesita para permitir que Git funcione.

Hay dos forms obvias de mantener las twigs no nacidas, pero Git no usa ninguna de ellas. (Para el logging, estos son: crear un file vacío, o usar el especial "hash nulo." El truco de file vacío tiene un defecto obvio: será muy frágil frente a fallas de command o computadora, mucho más que el uso el hash nulo.)


El process de compromiso

En general, el process de hacer un nuevo compromiso, en Git, es así:

  1. Actualice y / o rellene el índice, también denominado área de ensayo o caching: git add various files. Este paso crea los objects blob de Git, que almacenan el contenido real del file.

  2. Escriba el índice en uno o más objects de tree ( git write-tree ). Este paso crea, o en algunos casos raros reutiliza, al less un tree (nivel superior). Ese tree tiene inputs para cada file y subdirectory; para los files, enumera el blob-ID, y para los subdirectorys, enumera (después de crear) el tree que contiene los treees y los files del subdirectory. Tenga en count, por cierto, que esto deja el índice sin perturbaciones, listo para el siguiente compromiso.

  3. Escribe un object de commit ( git commit-tree ). Este paso necesita un montón de elementos. Para nuestros propósitos, los más interesantes son el object de tree (único) que acompaña a este compromiso (ese es el que acabamos de get del paso 2) y una list de ID de confirmación de los padres.

  4. Escriba la ID del nuevo compromiso en el nombre de la sucursal actual.

El paso 4 es cómo y por qué los nombres de las sucursales siempre apuntan a la confirmación de sugerencia. El command git commit obtiene el nombre de la twig de HEAD . También, durante el paso 3, obtiene el ID principal primario (o el primero , y normalmente el único) ID de confirmación de la misma manera: lee HEAD para get el nombre de la twig, luego lee el ID de confirmación de sugerencia de la twig. (Para las MERGE_HEAD fusión, lee las ID adicionales de los padres, generalmente solo una, de MERGE_HEAD ).

El commit de Git sabe, por supuesto, sobre las twigs no nacidas y / o huérfanas. Si HEAD dice refs/heads/master , pero el master twig no existe … ¡bueno, entonces, el master debe ser una twig no nacida! Entonces este nuevo commit no tiene ID padre. Todavía tiene el mismo tree que siempre, pero es una nueva confirmación de raíz. Todavía obtiene su ID escrito en el file de sucursal, que tiene el efecto secundario de crear la sucursal .

Por lo tanto, está haciendo el primer commit en la nueva twig huérfana que realmente crea la twig.

Cosas que debes saber sobre cherry-pick

La order de Git de seleccionar cherry-pick es muy simple en teoría (la práctica se complica un poco a veces). Volvamos a nuestros charts de ejemplo e ilustramos una operación típica de selección de cereza. Esta vez, para hablar de algunas de las confirmaciones específicas dentro del gráfico, les daré letras de un solo nombre:

 ...--o--o--A--B--C <-- mong \ o--o <-- oose 

Digamos que nos gustaría seleccionar el compromiso B de branch mong en branch oose . Es fácil, solo hacemos:

 $ git checkout oose; git cherry-pick mong~1 

donde mong~1 designa commit B (Esto funciona porque mong designa commit C , y el padre de C es B , y mong~1 significa "mover hacia atrás uno de los padres se compromete a lo largo de la línea principal de los enlaces de primer padre. Del mismo modo mong~2 designa commit A , y mong~3 designa el o justo antes de A , y así sucesivamente. Siempre que no atravesamos un commit de fusión, que tiene múltiples padres, aquí todo es muy simple).

¿Pero cómo funciona realmente git cherry-pick ? La respuesta es: primero ejecuta git diff . Es decir, construye un parche , del tipo mostrado por git log -p o git show .

Los commits tienen treees completos

Recuerde (de nuestra discusión anterior) que cada compromiso tiene un object de tree adjunto. Ese tree contiene todo el tree fuente a partir de ese compromiso: una instantánea de todo lo que estaba en el área de índice / etapas cuando hicimos esa confirmación.

Esto significa que el compromiso B tiene un tree de trabajo completo y completo asociado. Pero queremos seleccionar cuidadosamente los cambios que hicimos en B , no el tree de B Es decir, si cambiamos README.txt , queremos get el cambio que hicimos: no la versión anterior de README.txt , y no la versión nueva, solo los cambios.

La forma en que encontramos esto es que pasamos del compromiso B a su padre, que es el compromiso A Commit A también tiene un tree de trabajo completo. Simplemente ejecutamos git diff en los dos commits, lo que nos muestra lo que cambiamos en README.txt , junto con cualquier otro cambio que hayamos realizado.

Ahora que tenemos el parche / parche, volvemos a donde estamos ahora: la oose punta de la twig o los files que tenemos en nuestro tree de trabajo y en nuestro área de índice / etapas que corresponden a ese compromiso. (El command git cherry-pick se rechazará de forma pnetworkingeterminada si nuestro índice no coincide con nuestro tree de trabajo, por lo que sabemos que son iguales). Ahora Git simplemente aplica (como con git apply ) el parche que recién obtenido mediante commits difusos A y B

Por lo tanto, cualesquiera que sean los cambios que hagamos para pasar de A a B , los haremos ahora, a nuestro commit / index / work-tree actual. Si todo va bien, esto nos da files modificados, que Git git add automáticamente a nuestro índice; y luego Git ejecuta git commit para hacer una nueva confirmación, usando el post de logging de commit B Si ejecutamos git cherry-pick -x , Git agrega la frase "recogido en cereza de …" al post de logging de nuestro nuevo commit.

( Sugerencia: generalmente desea usar -x . Probablemente debería ser el valor pnetworkingeterminado. La principal exception es cuando no va a mantener la confirmación original que acaba de seleccionar. También se puede argumentar que usar cherry-pick es por lo general, es incorrecto; es una indicación de que hiciste algo mal antes, de verdad, y ahora tienes que escribir sobre él, y el papeleo no puede sostenerse en el largo ploop, pero eso es para otra publicación [larga] por completo).

Recolección de cerezas en una twig huérfana

VonC notó que en Git 2.9.1 y posterior, git cherry-pick trabaja en una twig huérfana; en una próxima versión, funciona tanto para secuencias como para compromisos individuales. Pero hay una razón por la que esto ha sido imposible por tanto time.

Recuerde, cherry-pick convierte un tree en un parche , al diferir un commit contra su padre (o, en el caso de un commit de fusión, el padre que elija con la opción -m ). A continuación, aplica ese parche a la confirmación actual. Pero una twig huérfana -una twig que aún no hemos creado- no tiene compromisos, por lo tanto, no hay compromiso actual, y, al less en un sentido filosófico, ningún índice ni tree de trabajo. Simplemente no hay nada para parchear .

De hecho, sin embargo, podemos (y Git ahora lo hace) omitir esto por completo. Si alguna vez tuviéramos un compromiso actual (si teníamos algo verificado en algún momento), entonces todavía tenemos, en este momento, un índice y un tree de trabajo, sobrante del "compromiso actual" más reciente, cada vez que lo teníamos. .

Esto es lo que git checkout --orphan orphanbranch hace. Revisa alguna confirmación existente y, por lo tanto, rellena el índice y el tree de trabajo. A continuación, se git checkout --orphan newbranch y git commit y la nueva confirmación utiliza el índice actual para crear, o incluso reutilizar, un tree. Ese tree es el mismo tree que el compromiso que habías revisado antes de que git checkout --orphan orphanbranch . 3

Aquí es donde también viene la parte principal de mi receta para muy antiguo: Git:

 $ commit=$(git rev-parse master~2) $ git branch newbranch $( \ git log --no-walk --pretty=format:%B $commit | \ git commit-tree -F - "${commit}^{tree}" \ ) $ git checkout newbranch 

Primero encontramos la confirmación deseada y su tree: el tree asociado con el master~2 . (En realidad, no necesitamos la commit variable, pero escribirla de esta manera nos permite cortar y pegar un hash de la salida de git log , sin tener que contar qué tan atrás está de la punta del master o de cualquier twig que estemos va a usar aquí.)

El uso de ${commit}^{tree} le dice a Git que encuentre el tree real asociado con la confirmación (esta es la syntax estándar de gitrevisions ). El command git commit-tree escribe una nueva confirmación en el repository, utilizando este tree que acabamos de suministrar. Los padres de la nueva confirmación provienen de las ID padre que suministramos usando -p options: no usamos ninguno, por lo que la nueva confirmación no tiene padres, es decir, es una confirmación de la raíz.

El post de logging para este nuevo compromiso es lo que proporcionamos en la input estándar. Para get este post de logging, usamos git log --no-walk --pretty=format:%B , que solo imprime el text completo del post en la salida estándar.

El command git commit-tree produce como resultado el ID de la nueva confirmación:

 $ ... | git commit-tree "master~2^{tree}" 80c40c288811ebc44e0c26a5b305e5b13e8f8985 

(cada ejecución produce una ID diferente a less que todas se ejecuten en el mismo período de un segundo, ya que cada una tiene un set diferente de marcas de time, la ID real no es muy importante aquí). Damos esta identificación a la git branch para crear un nuevo nombre de sucursal que apunta a esta nueva confirmación raíz, ya que su sugerencia se confirma.

Una vez que tengamos la nueva confirmación raíz en una nueva twig, podemos completar la nueva twig y estamos listos para seleccionar cuidadosamente las confirmaciones restantes.


3 De hecho, puedes combinarlos como de costumbre:

 git checkout --orphan orphanbranch master~2 

que primero comtesting (coloca en el índice y en el tree de trabajo) el contenido de la confirmación identificada por el master~2 , luego configura HEAD para que esté en la twig no nacida orphanbranch .


Usar git cherry-pick en una twig huérfana no es tan útil como nos gustaría

Aquí tengo una versión más nueva de Git (falla algunos de sus propios test-dies durante t3404-rebase-interactive.sh-pero de lo contrario parece más que nada aceptable):

 $ alias git=$HOME/.../git $ git --version git version 2.9.2.370.g27834f4 

Echemos un vistazo, con --orphan , master~2 con nuevo nombre orphanbranch :

 $ git checkout --orphan orphanbranch master~2 Switched to a new branch 'orphanbranch' $ git status On branch orphanbranch Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: a.py new file: ast_ex.py [snip] 

Debido a que esta es una nueva twig, a Git le parece que todo es nuevo. Si ahora trato de hacer git cherry-pick master~2 o master~1 :

 $ git cherry-pick master~2 error: Your local changes would be overwritten by cherry-pick. hint: Commit your changes or stash them to proceed. fatal: cherry-pick failed $ git cherry-pick master~1 error: Your local changes would be overwritten by cherry-pick. hint: Commit your changes or stash them to proceed. fatal: cherry-pick failed 

Lo que tendría que hacer es limpiar todo, en cuyo caso aplicar el cambio de master~3 a master~2 sería poco probable que funcione, o simplemente hacer un git commit inicial de git commit todos modos, para hacer un nuevo commit raíz basado en el tree del master~2 .

Conclusión

Si tienes la git checkout --orphan , solo utiliza eso para verificar el destino commit oldbranch~N (o por hash ID, que puedes cortar y pegar desde la salida de git log ):

 $ git checkout --orphan newbranch oldbranch~N 

luego haga el nuevo compromiso inmediatamente, como dijo Nick Volynkin (puede copyr su post):

 $ git commit -C oldbranch~N 

para crear la twig; y luego use git cherry-pick con oldbranch~N..oldbranch para get las confirmaciones restantes:

 $ git cherry-pick oldbranch~N..oldbranch 

(Y tal vez use -x , dependiendo de si planea oldbranch los commits de oldbranch .) Recuerde, oldbranch~N..oldbranch excluye el commit oldbranch~N sí, pero eso es realmente bueno porque ese es el que creamos como la nueva raíz cometer.

Aquí hay una manera simple de hacerlo

 git branch -m master old_master git checkout --orphan master 

-m = mover la twig a un nuevo nombre
finalización de pago – verificar el nuevo maestro como huérfano

La respuesta de Nick Volynkin implica hacer al less un commit en la nueva twig huérfana.
Esto se debe a que un git cherry-pick df931da..master sin ese primer compromiso habría llevado a " Can't cherry-pick into empty head ".

Pero ya no, con git 2.9.X / 2.10 (Q3 2016).

Ver commit 0f974e2 (06 Jun 2016) por Michael J Gruber ( mjg ) .
(Fusionada por Junio ​​C Hamano – gitster – in commit 25227f0 , 06 jul 2016)

cherry-pick : permite recoger las twigs no nacidas

" git cherry-pick A " trabajó en una twig no nacida, pero " git cherry-pick A..B " no.

Eso significa que la solución se convierte en:

 # make an orphan branch ➜ gitorphan git:(df931da) git checkout --orphan orphanbranch Switched to a new branch 'orphanbranch' # Now cherry-pick from previous branch a range of commits ➜ gitorphan git:(orphanbranch) git cherry-pick df931da..master 

No hay necesidad de hacer un commit primero, antes de hacer cherry-picking.

Supongamos que ha finalizado una nueva sucursal y ha realizado dos confirmaciones de la siguiente manera. 13hh93 es la sum de comprobación para el pago y 54hdsf es la sum de comprobación para la última confirmación:

master => new_branch_1 (13hh93) => new_branch_2 => new_branch_3 (54hdsf)

Luego proceda de la siguiente manera. El paso 1 va al inicio de la compra. El paso 2 crea una twig huérfana de él. El paso 3 aplica el rest de la twig a su sucursal huérfana.

1) git checkout 13hh93 2) git checkout new_orphan_branch –orphan 3) git diff 13hh93 54hdsf | git aplicar

La selección selectiva para la sucursal huérfana funciona siempre que no haya confusiones de fusión. Merge commits hace que esto se interrumpa en el medio.

error: Commit 732bf15efcb59c416eeade17eee9fcb8d50bc5ed es una fusión, pero no se dio la opción -m. fatal: error de selección de cereza

Si agrega la opción -m al cherry-pick obtiene:

error: se especificó la línea principal, pero commit 0f45aa84b33b7a9bfeb41845c723dc51f82f1cf0 no es una combinación. fatal: error de selección de cereza

El command cherry-pick no le gusta tener un punto de fusión pnetworkingeterminado para los ranges que cubren más de una confirmación que es una mezcla de fusiones y no fusiones.