Atajo de Git para pagar, tirar, pagar, fusionar / rebase

Digamos que tengo 2 twigs, master y codyDev. Antes de comenzar a hacer cambios en codyDev, hago lo siguiente para asegurarme de estar comenzando con la última confirmación maestra en codyDev:

git checkout master
git pull
git checkout codyDev
si cambia, git merge master

¿Hay un atajo para los primeros 3 pasos, así que no tengo que salir de mi twig codyDev? Tiendo a descubrir que el maestro no se ha actualizado y el process de pago / extracción / pago fue de 3 commands innecesarios.

Tenga en count que hice Google para tratar de encontrar una respuesta a mi pregunta, pero parece que hay una amplia gama de soluciones posibles y algo complicadas. El que parecía más atractivo era algo relacionado con el efecto del git fetch origin master:master de git fetch origin master:master , pero las explicaciones que leí no eran muy claras porque los ejemplos eran un poco más complicados. También me encontré preguntándome "¿por qué una búsqueda en lugar de un tirón" (es decir git pull origin master:master )?

Puede actualizar su twig codyDev con los cambios más recientes desde el master sin cambiar las twigs:

 git checkout codyDev # ignore if already on codyDev git fetch origin # updates tracking branches, including origin/master git merge origin/master # merge latest master into codyDev 

El truco aquí es que para fusionar el último master en codyDev , en realidad no necesitas actualizar tu twig master local. En su lugar, puede actualizar el origin/master derivación de rastreo remoto (a través de la git fetch ) y luego combinarlo.

También puede actualizar directamente la twig master local usando git fetch sin verificar realmente la twig master local:

 # from branch codyDev git fetch origin master:master 

Sin embargo, esto solo funcionará si la combinación es un avance rápido de la twig master local. Si la fusión no es un avance rápido, entonces esto no funcionará, porque se necesita un directory de trabajo en caso de que surjan conflictos de fusión.

Lea aquí para más información.

Puede agregar un alias a su ~/.gitconfig .

 [alias] start = !git checkout master && git pull origin master && git checkout -b 

O ejecute esto para agregarlo desde la línea de command:

  git config --global alias.start '!git checkout master && git pull origin master && git checkout' 

Además, podría no estar claro desde el código anterior, pero asegúrese de que haya un espacio después de -b .

También puede omitir -b y pasarlo para que tenga la opción de operar en una sucursal existente. En ese caso, tendrías:

 [alias] start = !git checkout master && git pull origin master && git checkout 

Y luego llamarlo sería

 git start -b newBranch 

O

  git start existingBranch 

Puede save esto en su ~./bash_profile y hacer um . Básicamente puedes search el origin/master y verifica si hay algún cambio. En caso afirmativo, realice los tres pasos que mencionó.

 # update local master um(){ git fetch origin num_changes=$(git rev-list HEAD...origin/master --count) echo "Num of changes: ${num_changes}" if [ "$num_changes" -gt "0" ] then current_branch=$(git rev-parse --abbrev-ref HEAD) git checkout master git pull echo $current_branch git checkout $current_branch fi } 

Todas las soluciones "perfectas" requieren algo al less un poco complicado, o la nueva característica de Git (desde la versión 2.5) git add-worktree . El método de Tim Biegeleisen es el método más simple, y tal vez el mejor, pero no exactamente el mismo que su flujo de trabajo actual, aunque tal vez su flujo de trabajo actual no sea ideal de todos modos.

Hay varias keys aquí:

  • En primer lugar, git pull es solo un atajo. Significa "(ligeramente) git fetch && git $something , donde $something es merge o rebase . El que obtienes-fusionar vs rebase-depende de cómo hayas configurado tu repository y twigs de Git.

    El paso de git fetch es donde tu Git realmente recoge nuevas confirmaciones de origin , si hay alguna para recoger.

  • En segundo lugar, la git merge puede hacer algo llamado fusión de avance rápido . Este es un nombre inapropiado, porque un avance rápido no es en realidad una fusión. Y, aunque git rebase solo hace una rebase, si no tienes commit tuyos, el efecto es el mismo que el de un avance rápido.

  • En tercer lugar, debe comprender cómo Git gestiona su gráfico de compromiso y las tags de las sucursales. Esto va a unir la búsqueda y el avance rápido no del todo.

  • En cuarto lugar (y tal vez la parte más complicada), Git git fetch hace algo interesante usando lo que Git llama refspecs . Esta es la razón por la cual el git fetch origin master:master es interesante, y también responde la pregunta:

    "¿por qué una búsqueda en lugar de un tirón" (es decir, git pull origin master:master )?

    (de hecho, no existe tal cosa, porque git pull omite las references de búsqueda fetch cuando ejecuta git fetch para ti).

Comencemos primero con el tercer elemento, porque en realidad es bastante fundamental. Si ya sabes todo esto, siéntete libre de saltar hacia adelante …

El gráfico y las tags

El gráfico de confirmación de Git es un G raph cíclico D irectado o DAG. Esto en realidad significa mucho, pero para un uso simple, podemos resumirlo de esta manera: cada commit tiene una ID -los grandes hash feos SHA-1 como 7452b4b5786778d5d87f5c90a94fab8936502e20 y cada confirmación registra su (s) ID de confirmación padre , que, en efecto, significa que cada compromiso "apunta hacia atrás" a su padre (s). La mayoría de las confirmaciones solo tienen un padre, pero las asignaciones de fusión tienen dos (o más, pero seguiremos con dos). También hay al less un commit: el primer commit que usted (o quien sea) haya hecho, que no tiene padre: no pudo haber tenido un padre, porque fue el primer commit.

Esto significa que podemos dibujar el gráfico de compromiso. Si ponemos commits más antiguos a la izquierda y más nuevos a la derecha, obtenemos algo como esto:

 o <- o <- o <-- master 

Este gráfico no tiene fusiones y solo tres confirmaciones: la primera, el segundo (que apunta al primero) y finalmente el tercero (que apunta al segundo). Cada confirmación es un poco aburrida, por lo que tenemos una pequeña ronda o , y cada una tiene una flecha apuntando hacia la izquierda, hacia su padre.

También lancé el nombre de la twig, master . Apunta al último compromiso en la twig: el tercer compromiso en el gráfico. Este último compromiso es la punta de la bifurcación, y el nombre de una bifurcación es solo un indicador de la confirmación de la punta.

Lo que esto significa es que si agregamos un nuevo compromiso, el nombre de la twig se mueve , por lo que aún apunta al compromiso más reciente (más reciente). Esta nueva confirmación apunta hacia donde el nombre de la twig solía apuntar:

 o <- o <- o <- o <-- master 

Todas las flechas internas son un poco aburridas también (sabemos que apuntan hacia la izquierda, a compromisos más antiguos) por lo que me gustaría dibujar estos charts de una forma más compacta:

 o--o--o--o <-- master 

Esto deja mucho más espacio para charts más complicados que se ramifican:

 o--o--o <-- master \ o--o <-- dev 

y luego tal vez fusionar de nuevo (aquí el desarrollador se "fusionó en" el master , es decir, alguien hizo el git checkout master && git merge dev ):

 o--o--o---o <-- master \ / o--o <-- dev 

También podrían seguir:

 o--o--o---o <-- master \ / o--o--o <-- dev 

(Después de que alguien se fusionó con dev convirtió en master , alguien -quizás la misma persona, tal vez otra persona- hizo otro commit en dev .)

La primera parte difícil

Ya mencionamos que el nombre de la twig, como master o dev , apunta a la confirmación de tip-most en esa twig. Pero las confirmaciones anteriores también están en la twig, y ​​una vez que tenemos una combinación de fusión, una confirmación con al less dos padres: dos flechas que apuntan hacia la izquierda, esto provoca que un grupo más de commits se encuentre repentinamente en la twig. Es decir, ahora hay un montón de compromisos en ambas twigs.

Veamos este gráfico nuevamente, pero use letras mayúsculas para cada confirmación, en lugar de solo una o :

 A--B--D---F <-- master \ / C--E--G <-- dev 

Commit F es el compromiso de fusión. Observe cómo tiene dos enlaces hacia la izquierda, uno a D y uno a E (La flecha hacia E apunta hacia abajo y hacia la izquierda, y la flecha hacia C hacia B apunta hacia arriba y hacia la izquierda, pero aún apuntan hacia la izquierda, es decir, hacia alguna confirmación anterior). Los compromisos C y E están en la twig dev , pero ellos también están en master . Commit A está en master , pero también está en dev .

En Git, un commit puede estar en más de una twig.

El término más general aquí es "alcanzable": observamos las confirmaciones en términos de cómo (y si) podemos alcanzarlas mientras seguimos las flechas de dirección única que van desde las confirmaciones más recientes hasta las confirmaciones principales de los padres. Cuando lleguemos a una combinación, que tiene dos padres (o más, pero nuevamente nos quedaremos con dos), tomamos ambos paths simultáneamente .

La segunda parte difícil

Un nombre de sucursal simplemente apunta a la confirmación de sugerencia de la sucursal. Pero más de un nombre puede apuntar a algún compromiso. En el gráfico anterior, el master apunta a F y dev apunta a G Si revisamos Branch master , luego creamos una nueva feature branch, esta nueva twig también apuntará a F :

 A--B--D---F <-- master, feature \ / C--E--G <-- dev 

Si ahora hacemos un nuevo compromiso H , la feature bifurcación debe cambiar para apuntar a H , mientras que H necesita señalar de nuevo a F Es decir, necesitamos get esta nueva image (o alguna variación sobre ella, siempre hay muchas maneras de dibujar cada gráfico):

  H <-- feature / A--B--D---F <-- master \ / C--E--G <-- dev 

¿Cómo sabe Git mover la feature y no la master ?

Ambos apuntan a cometer F Si todo lo que Git sabía es que F era el compromiso actual, Git no sabría qué nombre de twig cambiar. Claramente, Git debe saber en qué twig estamos realmente. Y lo hace: para eso es el file HEAD . El file HEAD contiene el nombre de la twig actual.

HEAD contiene el nombre de la sucursal y el nombre de la sucursal contiene la ID de confirmación de punta. Esto es lo que significa estar "en una twig" en primer lugar: si está en la twig master , HEAD tiene el nombre master en ella. Si estás en desarrollo, HEAD tiene el nombre dev . Si está feature , HEAD tiene feature . Cualquier twig en la que estés, eso es lo que está en HEAD . 1

El command git checkout cambia el nombre de twig almacenado en HEAD . (También hace más, pero así es como cambia la twig en la que se encuentra). El command git commit , una vez que escribe una nueva confirmación, almacena la ID del nuevo commit en la twig actual, lea desde HEAD . Y, la ID del padre del nuevo compromiso es generalmente lo que sea que HEAD apunte a puntos, o más bien, lo que sea que comprometa a la twig a la que apunta HEAD -points-to. 2 Es decir, HEAD apunta (indirectamente) al actual, branch-tip, commit, y el nuevo commit apunta a eso ya que parent y git commit actualizan el nombre de la twig y ahora el nuevo commit es el nuevo branch-tip.


1 Una "HEAD separada", que suena aterradora, solo significa que HEAD tiene la identificación hash sin formatting de una confirmación en lugar de un nombre de twig.

2 Digo "normalmente" porque git commit --amend funciona al establecer el padre del nuevo commit al padre del commit actual, en lugar del propio commit actual. En efecto, en lugar de agregar un nuevo compromiso a la cadena, descarta el compromiso actual. El compromiso nuevo sigue siendo el final de la cadena, pero ahora el enlace principal del nuevo compromiso actual pasa por alto el compromiso "enmendado", haciendo que parezca que se ha ido.


Fusión

La fusión en sí misma puede ser complicada y desorderada, pero el acto de hacer un compromiso de fusión es extremadamente sencillo. Git realiza una nueva fusión de confirmación exactamente de la misma manera que realiza una confirmación regular (sin fusión), con la exception de que la nueva confirmación tiene dos (o más) identificaciones principales almacenadas en ella.

El primer ID padre es el mismo primario que siempre: la confirmación más reciente de la sucursal actual, es decir, el compromiso HEAD .

El segundo ID padre es el compromiso que acaba de fusionar: la punta de la otra twig.

Así es como Git hizo commit F en nuestro gráfico de ejemplo. Primero git checkout master , por lo que HEAD dijo que el master y el master dijeron D Luego ejecutamos git merge dev , y dev dijo E :

 A--B--D <-- master \ C--E <-- dev 

Git hizo lo que tenía que hacer para crear los files y atascarse en la nueva fusión. Este "material" consiste primero en encontrar la base de fusión : el compromiso en el que se unen las dos twigs. Eso es cometer B Git descubrió cómo fusionar los cambios en el master , de B a D , con los cambios en dev , de B a E Entonces Git hizo el nuevo compromiso F El primer padre de F es D y el segundo padre de E es E , y una vez que F fue hecho, Git escribió su ID en el master :

 A--B--D---F <-- master \ / C--E <-- dev 

Así es como funcionan las fusiones reales; ¿pero qué hay de los rápidos?

Git tuvo que hacer una fusión real aquí debido al compromiso D Hagamos un nuevo repository diferente y hagamos algunos commits:

 A--B <-- master \ C--D <-- dev 

Ahora supongamos que tenemos el git checkout master aquí, y ejecutamos git merge dev . La punta del D es D y la punta del master es B La base de fusión es donde las twigs se unen por primera vez, lo que confirma B nuevamente.

¿Notaste algo especial sobre B ?

Es la punta del master . El compromiso actual, B , también es la base de combinación. No hay cambios de B a B : B ya es él mismo. Hay cambios de B a D en el desarrollador, pero el master no tiene cambios propios.

Lo que esto significa es que Git puede "deslizar la label de la twig hacia adelante". En lugar de tener el punto master en B , Git puede simplemente deslizar la label hacia adelante a D (Esto va en contra de la dirección de las flechas, es decir, avanza en el time en lugar de hacia atrás, pero es fácil de encontrar para Git, ya que le dijimos a Git que mire a dev , que apunta a D Git solo tiene que notar que el actual commit, B , ya es un ancestro de D )

Entonces ahora Git hace un movimiento rápido de label:

 A--B \ C--D <-- dev, master 

y ahora puntos master para cometer D El gráfico ya no necesita el doblez, solo teníamos que dibujarlo de esta manera para dejar espacio al master para apuntar a B Ahora tenemos una cadena simple A<-B<-C<-D con ambas tags apuntando a D

Esto es lo que es un avance rápido: el acto de "deslizar una label de bifurcación hacia adelante, contra la dirección de las flechas padre". Tenga en count que no se puede hacer si hay una confirmación en el path:

 o--*--o <-- br1 \ o--o <-- br2 

No puede "deslizar br1 hacia adelante" para señalar la punta de br2 . Primero debe deslizarlo de return a la combinación de combinación * antes de poder deslizar hacia adelante. Si mueve br1 para apuntar a la misma confirmación que br2 , la confirmación final a lo largo de la línea superior ya no se puede br1 desde br1 , y deja de estar en la twig br1 . Si no hay otra label que apunte a la confirmación de sugerencia (o a una confirmación que apunte a ella), se "pierde".

git fetch y git push

Las operaciones de búsqueda y envío hacen el mismo tipo de cosas, por lo que podemos cubrir ambas aquí.

En ambos casos, su Git llamará a otro Git, generalmente a través de ssh:// o https:// , es decir, a través del teléfono de Internet. Tu Git y su Git luego tienen una pequeña charla, donde la tuya y la de ellos descubren qué compromisos tienes que no tienen, o viceversa. (Cuál depende de si está buscando o empujando).

Luego, después de que los dos Gits hayan acordado qué compromisos conocen, utilizando sus ID de hash SHA-1, que siempre son los mismos en ambos lados 3 , el "envío" de Git envía las confirmaciones y otros objects necesarios, y el "recibir" Git los guarda en su repository. Luego, al final de todo esto, el final que envie commits también ha enviado algunos pares <branch-name, tip-commit>. Cuando estás git fetch desde el origin , el Git de origin envía a tu Git estos pares de nombre / ID. (Cuando estás presionando, tu Git envía los pares, y aquí es donde se rompe deliberadamente la simetría entre ir y venir).

Echemos un vistazo exclusivamente al caso git fetch ahora. En configuraciones estándar normales, 4 su Git le envía todas sus sugerencias de sucursales y todas las confirmaciones y otros objects que su Git necesita para usarlas. Su Git luego cambia el nombre de las sugerencias de la sucursal: en lugar de " master sucursal", su Git lo llama " origin/master sucursal de seguimiento remoto". (Aquí supongo que el control remoto se llama origin .) En lugar de dev , su Git usa origin/dev . De hecho, hay una forma un poco más larga y más precisa para esto: en realidad es +refs/heads/*:refs/remotes/origin/* . Pero la key es que se produce este cambio de nombre, de branch a origin/branch , de modo que sus twigs se conviertan en sus twigs de seguimiento remoto .

En cualquier caso, cuando su Git actualiza sus twigs de seguimiento remoto, realiza una "actualización forzada" si es necesario. Es decir, cuando está actualizando su origin/master , su Git primero verifica si tiene origin/master en absoluto. Si no, simplemente lo crea, apuntando al mismo compromiso de sugerencia que recibimos de ellos. Si es así, su Git comtesting: ¿esto es un avance rápido? Si se trata de una operación de avance rápido, su Git actualiza su origin/master utilizando un avance rápido. Si no, tu Git dice: bueno, olvidaremos algunos commit (s) que solíamos tener en origin/master , y tomaremos su master y lo llamaremos origin/master todos modos. Vamos a restablecer nuestro origin/master como una "actualización forzada":

 Receiving objects: 100% (992/992), 370.76 KiB | 0 bytes/s, done. Resolving deltas: 100% (775/775), completed with 142 local objects. From [url] e0c1cea..9194226 maint -> origin/maint 6ebdac1..35f6318 master -> origin/master f2ff484..15890ea next -> origin/next + 7d82ce0...eb0e753 pu -> origin/pu (forced update) fa7f92e..2d15542 todo -> origin/todo 

Eso es lo que significa la "actualización forzada": que la actualización no era, de hecho, una operación de avance rápido.


3 Este truco casi mágico es la key para distribuir repositorys. Tanto Git como Mercurial usan este tipo de identificación hash universal para que todo funcione. Los detalles están más allá del scope de este artículo.

4 repositorys "Mirror" hacen esto de manera diferente, pero a través de refspecs.


refspecs

Estas actualizaciones están controladas por refspecs . Un refspec es, en su segunda forma más simple, 5 solo un par de nombres de twigs con dos puntos entre ellos: master:master o master:origin/master . El nombre a la izquierda es la fuente , y el nombre a la derecha es el destino .

Para fetch , la fuente es el Git remoto, y el destino es tu propio repository. (Por push , esto se invierte, por supuesto).

Un refspec puede comenzar con un signo más adelantado, como en +master:origin/master . Este signo más positivo es la "bandera de fuerza". Más precisamente, es un indicador de fuerza por refspec: puede establecerlo o dejarlo fuera de cada refspec (frente a la opción de fuerza global, que lo establece para cada refspec en la línea de command).

Si omites la bandera de fuerza, Git solo hará avances rápidos.

Esta es la key para usar git fetch para actualizar tus propias twigs. Una actualización rápida no pierde compromisos. Una actualización forzada podría perder compromisos. Entonces, si ejecuta git fetch con un par de nombres de twigs, como master:master , su Git solo actualizará su master para que coincida con el master del control remoto si esa actualización es un avance rápido .

Sin embargo, hay una complicación seria aquí. ¿Qué pasa si no es un avance rápido? ¿Qué sucede si los usuarios de Git en sentido ascendente han "rebobinado" la twig, la forma en que el repository Git para Git rebobina la twig pu (captación) todo el time? (Tenga en count la "actualización forzada" más arriba). Si su Git obtiene un montón de confirmaciones, pero luego ve que la actualización no es un avance rápido, su Git simplemente no actualiza sus tags, a less que haya establecido el indicador de fuerza.

Es por eso que Git usa twigs de seguimiento remoto. Como el origin/master twig de seguimiento remoto no es solo master , es seguro actualizar el origin/master , incluso como una actualización forzada. O bien tienes tus propias twigs que se aferran para confirmar que la stream ascendente está tratando de retraerse, o no. Si tienes esos commits en tus propias twigs, tu Git todavía tiene esos commits, porque tus twigs no se ven afectadas. Si no lo haces, Git supone que no te importa la retractación: tus rastreos de seguimiento remoto seguirán el control remoto.

(Si ejecutas git fetch dos veces, con unos pocos minutos entre ellas, y obtienes una actualización y luego se retracta … bueno, si no hubieras ejecutado esa primera git fetch en ese momento, nunca hubieras visto lo retraído compromete. Esto significa que no pueden ser tan importantes. Si los vieras y quisieras aferrarte a ellos, hubieras creado una sucursal).

Sin embargo, hay una complicación más: Git normalmente no dejará que git fetch -o git push , para el caso- actualice la twig actual (aquella cuyo nombre está almacenado en HEAD ). 6 La razón de esto tiene que ver con algo que omitimos, arriba: cuando git checkout una twig, Git no solo escribe a HEAD . También rellena el índice y el tree de trabajo desde la confirmación de sugerencia de esa twig. Si git fetch cambiara la sugerencia de punta, sin previo aviso, su índice y tree de trabajo se desincronizarían. Peor aún, si tiene algunos cambios en progreso, su índice no podrá alcanzar las nuevas confirmaciones.


5 La forma más simple es sin los dos puntos, por ejemplo, git push master . Lo que esto significa es más complicado, sin embargo, porque significa cosas diferentes en git fetch y git push . Para git fetch , significa "nada para actualizar localmente", excepto que desde la versión 1.8.4 de Git, Git todavía hace "actualizaciones oportunistas" de las sucursales de rastreo remoto. Para git push , esta forma de refspec más simple significa "usar el nombre en sentido ascendente", que generalmente es el mismo nombre que la sucursal local.

6 Es decir, si hay un tree de trabajo. Si el repository está "vacío" (no tiene tree de trabajo), git push permitirá que la twig actual se actualice.


Conclusión

Me he quedado sin energía 🙂 pero veamos si podemos resumir:

  • git checkout master : cambia el índice y el tree de trabajo a master y escribe master en HEAD
  • git pull : obtiene del control remoto de la twig actual (maestra) y luego ejecuta git merge :
    • El paso de fetch trae a su master , que se convierte en su origin/master . 7
    • El paso de merge combina realmente, o adelanta, a tu master para traer las confirmaciones que ahora tienes en tu origin/master .
  • git checkout codyDev : cambia el índice y el tree de trabajo a codyDev y escribe codyDev en HEAD
  • git merge master : realiza una git merge master real o de avance rápido de su master , que (debido a la actualización anterior) ahora se fusiona con, o se reenvía rápidamente, al master en el control remoto (presumiblemente origin ).

En lugar de hacer todo esto, puedes, como en la respuesta de Tim Biegeleisen, ejecutar git fetch o git fetch origin que trae todas sus twigs y actualiza tu origin/master remote-tracking branch, y luego git merge origin/master .

Lo que es diferente ahora es obvio: esto no actualiza tu master . ¿Lo necesitas? Hay otra diferencia, no tan obvia: el command git merge hará un post de confirmación pnetworkingeterminado que dice "fusionar el origen / maestro de la twig de rastreo remoto" en lugar de uno que dice "fusionar el maestro de la twig". ¿Te importa? (Puede editar el post para eliminar el adjetivo de seguimiento remoto y el origin/ parte, si le importa).


7 Suponiendo que tu Git es 1.8.4 o más nuevo. De lo contrario, se omite la actualización oportunista. La secuencia de commands de pull aún logra fusionar la confirmación correcta, pero su origin/master desplaza cada vez más atrás. (Si tienes un Git antiguo, actualízalo a un Git moderno).

    Intereting Posts