remote example git git-clone

example - git clone ssh



¿Cómo actualizar un clon git superficial? (2)

Fondo

(para tl; dr, ver #preguntas a continuación)

Tengo múltiples gones repositorios git poco profundos. Estoy usando clones poco profundos porque es mucho más pequeño en comparación con un clon sucio. Cada uno se clona haciendo sobre el git clone --single-branch --depth 1 <git-repo-url> <dir-name> .

Esto funciona bien, excepto que no veo cómo actualizarlo.

Cuando estoy clonando por una etiqueta, la actualización no es significativa, ya que una etiqueta está congelada en el tiempo (como lo entiendo). En este caso, si quiero actualizar, esto significa que quiero clonar con otra etiqueta, así que simplemente rm -rf <dir-name> y clonar nuevamente.

Las cosas se complican más cuando he clonado la CABEZA de una rama maestra y luego la quiero actualizar.

Intenté git pull --depth 1 pero, aunque no debo enviar nada al repositorio remoto, se queja de que no sé quién soy.

git fetch --depth 1 , pero aunque parece que se actualiza algo, verifiqué que no está actualizado (algunos archivos en el repositorio remoto tienen un contenido diferente al de mi clon).

Después de https://stackoverflow.com/a/20508591/279335 , probé git fetch --depth 1; git reset --hard origin/master git fetch --depth 1; git reset --hard origin/master , pero dos cosas: primero no entiendo por qué se necesita git reset , segundo, aunque los archivos parecen estar actualizados, algunos archivos antiguos permanecen y git clean -df no se elimina estos archivos

Preguntas

Deje un clon creado con git clone --single-branch --depth 1 <git-repo-url> <dir-name> . Cómo actualizarlo para lograr el mismo resultado que rm -rf <dir-name>; git clone --single-branch --depth 1 <git-repo-url> <dir-name> rm -rf <dir-name>; git clone --single-branch --depth 1 <git-repo-url> <dir-name> ? ¿O es rm -rf <dir-name> y clonar de nuevo la única forma?

Nota

Esto no es un duplicado de Cómo actualizar un submódulo clonado superficial sin aumentar el tamaño del repositorio principal , ya que la respuesta no cumple mis expectativas y estoy usando repositorios simples, no submódulos (que no conozco).


[ligeramente rediseñado y formateado] Dado un clon creado con git clone --single-branch --depth 1 url directory , ¿cómo puedo actualizarlo para lograr el mismo resultado que rm -rf directory ; git clone --single-branch --depth 1 url directory rm -rf directory ; git clone --single-branch --depth 1 url directory ?

Tenga en cuenta que --single-branch es el valor predeterminado cuando se utiliza --depth 1 . La rama (única) es la que das con -b . Hay un lado aparte que va sobre el uso de -b con etiquetas, pero lo dejaré para más adelante. Si no usa -b , su Git le pregunta al Git "corriente arriba", el Git en la url , a qué rama se ha desprotegido, y simula que usó -b thatbranch . Esto significa que es importante tener cuidado al usar --single-branch sin -b para asegurarse de que la rama actual de este repositorio en sentido ascendente sea sensata, y por supuesto, cuando use -b , asegúrese de que el argumento de la rama realmente da nombre a una rama, no a una etiqueta.

La respuesta simple es básicamente esta, con dos cambios leves:

Después de https://.com/a/20508591/279335 , probé git fetch --depth 1; git reset --hard origin/master git fetch --depth 1; git reset --hard origin/master , pero dos cosas: primero no entiendo por qué se necesita git reset , segundo, aunque los archivos parecen estar actualizados, algunos archivos antiguos permanecen y git clean -df no se elimina estos archivos

Los dos cambios leves son: asegúrate de usar origin/ branchname en origin/ branchname lugar, y agrega -x ( git clean -d -f -x o git clean -dfx ) al paso de git clean . En cuanto a por qué , eso se pone un poco más complicado.

Que esta pasando

Sin --depth 1 , el paso git fetch llama al otro Git y obtiene de él una lista de nombres de rama y los correspondientes identificadores de hash de confirmación. Es decir, encuentra una lista de todas las sucursales de upstream y sus confirmaciones actuales. Luego, debido a que tiene un --single-branch una --single-branch , su Git arroja todo menos la única rama, y ​​trae todo lo que Git necesita para volver a conectar ese compromiso actual a los compromisos que ya tiene en su repositorio.

Con --depth 1 , tu Git no se molesta en conectar el nuevo compromiso con los antiguos históricos en absoluto. En su lugar, obtiene solo un compromiso y los otros objetos Git necesarios para completar ese compromiso. A continuación, escribe una entrada adicional de "injerto superficial" para marcar ese compromiso como un nuevo compromiso de pseudo-raíz.

Clonación regular (no superficial) y recuperación

Todo esto está relacionado con cómo se comporta Git cuando usas un clon normal (no superficial, no de una sola rama): git fetch invoca al Git corriente arriba, obtiene una lista de todo y luego te trae todo lo que no haces. Ya tengo . Esta es la razón por la que un clon inicial es tan lento, y una recuperación a actualización es generalmente tan rápida: una vez que se obtiene un clon completo, las actualizaciones rara vez tienen mucho que aportar: tal vez algunas confirmaciones, quizás unos pocos cientos, y La mayoría de esos compromisos tampoco necesitan mucho más.

La historia de un repositorio se forma a partir de las confirmaciones. Cada confirmación nombra a su confirmación principal (o para combinaciones, confirmaciones principales, plural), en una cadena que va hacia atrás desde "la última confirmación", a la confirmación anterior, a una confirmación más ancestral, y así sucesivamente. La cadena finalmente se detiene cuando llega a una confirmación que no tiene padre, como la primera confirmación realizada en el repositorio. Este tipo de confirmación es una confirmación de raíz .

Es decir, podemos dibujar una gráfica de confirmaciones. En un repositorio realmente simple, el gráfico es solo una línea recta, con todas las flechas apuntando hacia atrás:

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

El nombre master apunta al cuarto y último compromiso, que apunta al tercero, que apunta al segundo, que apunta al primero.

Cada confirmación lleva consigo una instantánea completa de todos los archivos que entran en esa confirmación. Los archivos que no se modifican en absoluto se comparten a través de estas confirmaciones: la cuarta confirmación simplemente "toma prestada" la versión sin cambios de la tercera confirmación, que la "toma prestada" de la segunda, y así sucesivamente. Por lo tanto, cada confirmación nombra todos los "objetos Git" que necesita, y Git encuentra esos objetos localmente, porque ya los tiene, o usa el protocolo fetch para traerlos del otro Git ascendente. Hay un formato de compresión llamado "empaque", y una variante especial para la transferencia de red llamada "paquetes finos", que permite a Git hacerlo aún mejor / más sofisticado, pero el principio es simple: Git necesita todo, y solo, aquellos objetos que van con los nuevos compromisos se está recuperando. Tu Git decide si tiene esos objetos y, si no, los obtiene de su Git.

Un gráfico más complicado y más completo generalmente tiene varios puntos en los que se ramifica, algunos donde se fusiona y varios nombres de ramas que apuntan a diferentes consejos de ramas:

o--o <-- feature/tall / o--o--o---o <-- master / / o--o <-- bug/short

En este caso, Branch bug/short se vuelve a combinar en master , mientras que Branch feature/tall todavía está en desarrollo. El nombre bug/short puede (probablemente) ahora ser eliminado por completo: no lo necesitamos más si hemos terminado de hacer confirmaciones en él. La confirmación en la punta del master nombra dos confirmaciones anteriores, incluida la confirmación en la punta del bug/short , por lo que al buscar el master buscaremos el bug/short confirmación.

Tenga en cuenta que tanto el gráfico simple como el ligeramente más complicado tienen, cada uno, un solo compromiso de raíz. Eso es bastante típico: todos los repositorios que tienen confirmaciones tienen al menos una confirmación raíz, ya que la primera confirmación siempre es una confirmación raíz; pero la mayoría de los repositorios tienen un solo root commit también. Sin embargo, puede tener diferentes confirmaciones de raíz, como en este gráfico:

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

o este:

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

De hecho, el que tiene solo un master probablemente se hizo fusionando orphan con master , y luego eliminando el nombre orphan .

Injertos y reemplazos

Git ha tenido durante mucho tiempo soporte (posiblemente inestable) para injertos , que fue reemplazado por (mucho mejor, en realidad sólido) para reemplazos genéricos. Para comprenderlos concretamente debemos agregar, a lo anterior, la noción de que cada compromiso tiene su propia ID única. Estas identificaciones son los hashes SHA-1 grandes y feos de 40 caracteres, face0ff... y así sucesivamente. De hecho, cada objeto Git tiene una ID única, aunque para fines gráficos, todo lo que nos importa son las confirmaciones.

Para dibujar gráficos, esos ID de hash grandes son demasiado dolorosos de usar, por lo que podemos usar los nombres de una letra A a Z lugar. Usemos este gráfico de nuevo, pero pongamos los nombres de una letra:

E--H <-- feature/tall / A--B--D---G <-- master / / C--F <-- bug/short

Commit H refiere de nuevo a commit E ( E es el padre de H ). Cometer G , que es un compromiso de fusión ( significa que tiene al menos dos padres) se refiere a D y F , y así sucesivamente.

Tenga en cuenta que los nombres de las ramas, feature/tall , master y bug/short , cada uno de ellos apuntan a una única confirmación . El nombre bug/short puntos bug/short para cometer F Esta es la razón por la que commit F está en rama bug/short ... pero también lo es commit C Commit C está en bug/short porque es accesible desde el nombre. El nombre nos lleva a F , y F nos lleva a C , por lo que C está en bifurcación de rama bug/short .

Tenga en cuenta, sin embargo, que commit G , la punta del master , nos hace cometer F Esto significa que commit F también está en el master rama. Este es un concepto clave en Git: los compromisos pueden estar en una , muchas o incluso ninguna rama. Un nombre de rama es simplemente una forma de comenzar dentro de un gráfico de confirmación. Hay otras formas, como nombres de etiquetas, refs/stash (que te llevan al alijo actual: cada alijo es en realidad un par de confirmaciones) y los reflogs (que normalmente están ocultos a la vista, ya que normalmente son un desorden).

Esto también, sin embargo, nos lleva a injertos y reemplazos. Un injerto es solo un tipo limitado de reemplazo, y los depósitos poco profundos usan una forma limitada de injerto. 1 No describiré los reemplazos completamente aquí, ya que son un poco más complicados, pero en general, lo que Git hace por todo esto es usar el injerto o el reemplazo como "en lugar de". Para el caso específico de las confirmaciones , lo que queremos aquí es poder cambiar, o al menos, pretender cambiar, la identificación principal o las identificaciones de cualquier confirmación ... y para repositorios poco profundos, queremos poder fingir que El cometer en cuestión no tiene padres.

1 La forma en que los repositorios superficiales utilizan el código de injerto no es inestable. Para el caso más general, recomendé el uso de git replace lugar, ya que también era y no es inestable. El único uso recomendado para los injertos es, o al menos lo fue, hace años, colocarlos en su lugar el tiempo suficiente para ejecutar git filter-branch para copiar un historial alterado e injertado, después del cual simplemente debe descartar el historial injertado por completo. También puede usar el git replace para este propósito, pero a diferencia de los injertos, puede usar el git replace permanente o semipermanente, sin necesidad de git filter-branch .

Haciendo un clon superficial

Para hacer un clon superficial de profundidad-1 del estado actual del repositorio ascendente, seleccionaremos uno de los tres nombres de rama ( feature/tall , master o bug/short y lo traduciremos a un ID de confirmación. Luego, escribiremos una entrada de injerto especial que diga: "Cuando vea ese compromiso, pretenda que no tiene ningún compromiso principal, es decir, es un compromiso de raíz".

Digamos que elegimos master . El nombre master apunta a cometer G , así que para hacer un clon superficial de commit G , obtenemos commit G desde el Git ascendente como de costumbre, pero luego escribimos una entrada especial de injerto que afirma que G no tiene padres. Lo pusimos en nuestro repositorio, y ahora nuestro gráfico se ve así:

G <-- master, origin/master

Esas identificaciones de los padres todavía están realmente dentro de G ; es solo que cada vez que usamos Git o nos mostramos el historial, no "injerta" nada en absoluto de forma inmediata, de modo que G parece ser una confirmación de raíz, para fines de seguimiento del historial.

Actualizando un clon superficial que hicimos antes

Pero, ¿qué pasa si ya tenemos un clon (profundidad-1 superficial) y queremos actualizarlo ? Bueno, eso no es realmente un problema. Digamos que hicimos un clon superficial de la corriente ascendente cuando el master apuntaba a cometer B , antes de las nuevas ramas y la corrección de errores. Eso significa que actualmente tenemos esto:

B <-- master, origin/master

Si bien el padre real de B es A , tenemos una entrada de injerto de clon poco profundo que dice "fingir que B es un" root commit ". Ahora vamos a git fetch --depth 1 , que busca al master del upstream (lo que llamamos origin/master y ve a comprometerse con G Tomamos el commit G de la corriente arriba, junto con sus objetos, pero deliberadamente no agarramos los commit D y F Luego, actualizamos nuestras entradas de injerto de clonación superficial para decir "fingir que G es una confirmación de raíz":

B <-- master G <-- origin/master

Nuestro repositorio ahora tiene dos confirmaciones de raíz: el nombre master (todavía) apunta a cometer B , cuyos padres (todavía) pretendemos que no existen, y el nombre origin/master señala a G , cuyos padres que pretendemos no existen.

Es por eso que necesitas git reset

En un repositorio normal, puedes usar git pull , que en realidad es git fetch seguido por git merge . Pero git merge requiere historial, y nosotros no tenemos ninguno: hemos falsificado Git out con falsas confirmaciones de root, y no tienen historial detrás de ellos. Así que debemos usar git reset lugar.

Lo que hace git reset es un poco complicado, ya que puede afectar hasta tres cosas diferentes: un nombre de rama , el índice y el árbol de trabajo . Ya hemos visto cuáles son los nombres de las sucursales: simplemente apuntan a un compromiso (uno, específico), que llamamos la punta de la rama. Eso deja el índice y el árbol de trabajo.

El árbol de trabajo es fácil de explicar: es donde están todos sus archivos. Eso es: ni más ni menos. Está allí para que puedas usar Git: Git tiene que ver con almacenar todos los compromisos realizados, para siempre, para que todos puedan recuperarse. Pero están en un formato inútil para los simples mortales. Para ser utilizado , un archivo, o más típicamente, el valor total de los archivos de un compromiso, debe extraerse a su formato normal. El árbol de trabajo es donde ocurre eso, y luego puede trabajar en él y hacer nuevos compromisos al usarlo.

El índice es un poco más difícil de explicar. Es algo propio de Git: otros sistemas de control de versiones no tienen uno, o si tienen algo así, no lo exponen. Git lo hace. El índice de Git es esencialmente donde mantienes el próximo compromiso de realizar, pero eso significa que comienza manteniendo el compromiso actual que has extraído en el árbol de trabajo, y Git lo usa para hacer que Git sea más rápido. Vamos a decir más sobre esto en un momento.

Lo que hace git reset --hard es afectar a los tres : nombre de rama, índice y árbol de trabajo. Mueve el nombre de la rama para que apunte a un compromiso (probablemente diferente). Luego actualiza el índice para que coincida con ese compromiso y actualiza el árbol de trabajo para que coincida con el nuevo índice.

Por lo tanto:

git reset --hard origin/master

le dice a Git que busque origin/master . Desde que ejecutamos nuestra git fetch , eso ahora apunta a cometer G Git hace que nuestro maestro, nuestra rama actual (y única), también apunte a confirmar G , y luego actualice nuestro índice y árbol de trabajo. Nuestra gráfica ahora se ve así:

B [abandoned - but see below] G <-- master, origin/master

Ahora master y origin/master tanto el nombre commit G , y commit G es el que está registrado en el árbol de trabajo.

Por qué necesitas git clean -dfx

La respuesta aquí es un poco complicada, pero por lo general es "no" (es necesario que se git clean ).

Cuando necesita git clean , es porque usted, o algo que ejecutó, agregó archivos a su árbol de trabajo por lo que no le ha contado a Git. Estos son archivos sin seguimiento y / o ignorados . El uso de git clean -df eliminará archivos sin git clean -df (y directorios vacíos); agregar -x también eliminará los archivos ignorados.

Para obtener más información sobre la diferencia entre "sin seguimiento" e "ignorado", consulte esta respuesta .

Por qué no necesitas git clean : el índice

Mencioné anteriormente que normalmente no es necesario ejecutar git clean . Esto se debe al índice. Como dije anteriormente, el índice de Git es principalmente "el próximo compromiso a realizar". Si nunca agrega sus propios archivos, si solo está usando git checkout para verificar varios compromisos existentes que haya tenido todo el tiempo, o que haya agregado con git fetch ; o si está utilizando git reset --hard mover el nombre de una rama y también cambiar el índice y el árbol de trabajo a otra confirmación; entonces, lo que sea que esté en el índice ahora mismo está ahí porque una verificación de git checkout anterior (o git reset ) lo puso en el índice, y también en el árbol de trabajo.

En otras palabras, el índice tiene un resumen o manifest breve (y rápido para que Git pueda acceder) que describe el árbol de trabajo actual. Git usa eso para saber qué hay en el árbol de trabajo ahora. Cuando le pide a Git que cambie a otro compromiso, a través de git checkout o git reset --hard , Git puede comparar rápidamente el índice existente con el nuevo compromiso. Cualquier archivo que haya cambiado , Git debe extraer de la nueva confirmación (y actualizar el índice). Cualquier archivo que se haya agregado recientemente , Git también debe extraer (y actualizar el índice). Cualquier archivo que haya desaparecido ( que está en el índice existente, pero no en el nuevo compromiso), Git debe eliminar ... y eso es lo que hace Git. Git actualiza, agrega y elimina esos archivos en el árbol de trabajo, como lo indica la comparación entre el índice actual y el nuevo compromiso.

Lo que esto significa es que si necesitas git clean , debes haber hecho algo fuera de Git que haya agregado archivos. Estos archivos agregados no están en el índice, por lo tanto, por definición , no se siguen y / o se ignoran. Si simplemente no están rastreados, git clean -f los eliminará, pero si se ignoran, solo git clean -fx los eliminará. (Usted desea -d solo para eliminar los directorios que están o se quedan vacíos durante la limpieza).

Abandonados cometidos y recolección de basura.

Mencioné, y dibujé en el gráfico de poca profundidad actualizado, que cuando git fetch --depth 1 y luego git reset --hard , terminamos abandonando la confirmación de gráfico de poca profundidad anterior-1. (En el gráfico que dibujé, este era el compromiso B ). Sin embargo, en Git, los compromisos abandonados rara vez se abandonan realmente, al menos, no de inmediato. En cambio, algunos nombres especiales, como ORIG_HEAD los conservan durante un tiempo, y cada referencia —las ramas y las etiquetas son formas de referencia— lleva consigo un registro de "valores anteriores".

Puedes mostrar cada reflog con git reflog refname . Por ejemplo, git reflog master muestra no solo qué nombre de master confirmación ahora , sino también qué cometido ha nombrado en el pasado . También hay un reflog para HEAD sí mismo, que es lo que muestra git reflog por defecto.

Las entradas de Reflog eventualmente caducan. Su duración exacta varía, pero por defecto son elegibles para el vencimiento después de 30 días en algunos casos y 90 días en otros. Una vez que caducan, esas entradas de reflexión ya no protegen los compromisos abandonados (o, para las referencias de etiquetas anotadas, el objeto de etiqueta anotada: se supone que las etiquetas no deben moverse, por lo que este caso no debe ocurrir, pero si lo hace, si lo hace) Git para mover una etiqueta, simplemente se maneja de la misma manera que todas las demás referencias).

Una vez que cualquier objeto Git (commit, etiqueta anotada, "árbol" o "blob" (archivo)) no tenga referencias, Git puede eliminarlo de verdad. 2 Es solo en este punto que los datos del repositorio subyacente para las confirmaciones y los archivos desaparecen. Incluso entonces, solo sucede cuando algo ejecuta git gc . Por lo tanto, un repositorio de poca profundidad actualizado con git fetch --depth 1 no es exactamente lo mismo que un clon nuevo con --depth 1 : el repositorio de poca profundidad probablemente tenga algunos nombres persistentes para las confirmaciones originales, y no eliminará los objetos extra del repositorio hasta que esos nombres caduquen o sean borrados.

2 Además de la verificación de referencia, los objetos obtienen un tiempo mínimo antes de que caduquen también. El valor predeterminado es dos semanas. Esto evita que git gc elimine los objetos temporales que Git está creando, pero aún no ha establecido una referencia. Por ejemplo, al realizar una nueva confirmación, Git primero convierte el índice en una serie de objetos de tree que se refieren entre sí pero no tienen una referencia de nivel superior. Luego crea un nuevo objeto de commit que se refiere al árbol de nivel superior, pero nada se refiere todavía a la confirmación. Por último, actualiza el nombre de la rama actual. Hasta que el último paso termine, ¡los árboles y el nuevo compromiso son inalcanzables!

Consideraciones especiales para los --single-branch y / o poco profundos

Noté arriba que el nombre que le das a git clone -b puede referirse a una etiqueta . Para los clones normales (no superficiales o no de una sola rama), esto funciona como uno esperaría: obtienes un clon regular, y luego Git hace un git checkout por el nombre de la etiqueta. El resultado es la CABEZA separada habitual, en un clon perfectamente ordinario.

Sin embargo, con los clones poco profundos o de una sola rama, hay varias consecuencias inusuales. Estos son todos, hasta cierto punto, resultado de que Git haya dejado ver la implementación.

Primero, si usa --single-branch , Git altera la configuración normal de fetch en el nuevo repositorio. La configuración de fetch normal depende del nombre que elija para el control remoto , pero el valor predeterminado es origin por lo que solo usaré origin aquí. Se lee:

fetch = +refs/heads/*:refs/remotes/origin/*

De nuevo, esta es la configuración normal para un clon normal (no de rama única). Esta configuración le dice a git fetch qué buscar , que es "todas las ramas". --single-branch embargo, cuando usas --single-branch , obtienes una línea de búsqueda que se refiere solo a una rama:

fetch = +refs/heads/zorg:refs/remotes/origin/zorg

Si estás clonando la rama zorg .

Cualquiera que sea la rama que clones, esa es la que entra en la línea de fetch . Cada git fetch futuro obedecerá esta línea, 3 para que no puedas buscar ninguna otra rama. Si desea obtener otras sucursales más adelante, deberá modificar esta línea o agregar más líneas.

En segundo lugar, si usa --single-branch y lo que clona es una etiqueta , Git colocará una línea de fetch bastante extraña. Por ejemplo, con git clone --single-branch -b v2.1 ... obtengo:

fetch = +refs/tags/v2.1:refs/tags/v2.1

Esto significa que no obtendrá sucursales y, a menos que alguien haya movido la etiqueta, ¡ 4 git fetch no hará nada !

En tercer lugar, el comportamiento de la etiqueta predeterminada es un poco extraño debido a la forma en que git clone y git fetch obtienen las etiquetas. Recuerde que las etiquetas son simplemente una referencia a un compromiso en particular, al igual que las ramas y todas las demás referencias. Sin embargo, hay dos diferencias clave entre las ramas y las etiquetas: se espera que las ramas se muevan (y las etiquetas no) y las ramas se renombran (y las etiquetas no).

Recuerde que a lo largo de todo lo anterior, seguimos encontrando que el otro master de Git (ascendente) se convierte en nuestro origin/master , y así sucesivamente. Este es un ejemplo del proceso de cambio de nombre. También vimos, brevemente, exactamente cómo funciona el cambio de nombre, a través de la línea fetch = : nuestro Git toma sus refs/heads/master y lo cambia a nuestros refs/remotes/origin/master . Este nombre no solo tiene un aspecto diferente ( origin/master ), sino que, literalmente , no puede ser el mismo que cualquiera de nuestras sucursales. Si creamos una rama denominada origin/master , 5 el "nombre completo" de esta rama es en realidad refs/heads/origin/master que es diferente de los otros nombres completos refs/remotes/origin/master . Solo cuando Git usa el nombre más corto, tenemos una rama (regular, local) denominada origin/master y otra rama diferente (seguimiento remoto) llamada origin/master . (Es muy parecido a estar en un grupo donde todos se llaman Bruce ).

Las etiquetas no pasan por todo esto. La etiqueta v2.1 se llama refs/tags/v2.1 . Esto significa que no hay manera de separar "su" etiqueta de "su" etiqueta. Puedes tener tu etiqueta o su etiqueta. Mientras nadie mueva una etiqueta, esto no importa: si ambos tienen la etiqueta, deben apuntar al mismo objeto . (Si alguien comienza a mover etiquetas, las cosas se ponen feas).

En cualquier caso, Git implementa la obtención "normal" de etiquetas mediante una regla simple: 6 cuando Git ya tiene un commit, si algunos de los nombres de las etiquetas se comprometen, Git también copia la etiqueta. Con clones ordinarios, el primer clon obtiene todas las etiquetas y, a continuación, las operaciones de git fetch posteriores obtienen las nuevas etiquetas. Sin embargo, un clon superficial, por definición, omite algunos compromisos, es decir, todo lo que se encuentra debajo de cualquier punto de injerto en la gráfica. Esos compromisos no recogerán las etiquetas. No pueden : para tener las etiquetas, deberías tener las confirmaciones. Git no está permitido (excepto a través de los injertos poco profundos) tener el ID de una confirmación sin tener realmente la confirmación.

3 Puede dar a git fetch algunos refspec (s) en la línea de comandos, y estos anularán el valor predeterminado. Esto se aplica solo a una búsqueda predeterminada. También puede usar varias líneas fetch = en la configuración, por ejemplo, para obtener solo un conjunto específico de ramas, aunque la forma normal de "eliminar la restricción" de un clon de una sola rama inicialmente es volver a colocar los +refs/heads/*:refs/remotes/origin/* habituales +refs/heads/*:refs/remotes/origin/* fetch line.

4 Dado que las etiquetas no deben moverse, solo podemos decir "esto no hace nada". Sin embargo, si se mueven, el signo + en la refspec representa la bandera de fuerza, por lo que la etiqueta termina moviéndose.

5 No hagas esto. Es confuso. Git lo manejará muy bien (la rama local está en el espacio de nombres local, y la rama de seguimiento remoto está en el espacio de nombres de seguimiento remoto), pero es realmente confuso.

6 Esta regla no coincide con la documentación. Probé contra Git versión 2.10.1; Gits mayores podrían usar un método diferente.


En el proceso de actualización de clones poco profundos en sí mismo, vea commit 649b0c3 del formulario Git 2.12 (Q1 2017).
Ese commit es parte de:

Commit 649b0c3 , commit f2386c6 , commit 6bc3d8c , commit 0afd307 (06 de diciembre de 2016) por Nguyễn Thái Ngọc Duy ( pclouds ) . Consulte commit 1127b3c , commit 381aa8e (06 de diciembre de 2016) por Rasmus Villemoes ( ravi-prevas ) . (Fusionada por Junio ​​C Hamano - gitster - in commit 3c9979b , 21 de diciembre de 2016)

shallow.c

Este paint_down() es parte del paso 6 de 58babff (shallow.c: los 8 pasos para seleccionar nuevos compromisos para .git / shallow - 2013-12-05) .
Cuando obtenemos un repositorio de poca profundidad, necesitamos saber si una de las referencias nuevas / actualizadas necesita nuevas "confirmaciones de poca profundidad" en .git/shallow (porque no tenemos suficiente historial de esas referencias) y cuál.

La pregunta en el paso 6 es: ¿qué (nuevos) compromisos mínimos se requieren en otros para mantener la accesibilidad en todo el repositorio sin cortar nuestra historia?
Para responder, marcamos todas las confirmaciones accesibles desde refs existentes con UNINTERESTING (" rev-list --not --all "), marcamos confirmaciones superficiales con BOTTOM, luego, para cada refs nueva / actualizada, recorra el gráfico de confirmación hasta que lleguemos a UNINTERESTING o BOTTOM, marcando el árbitro en el commit mientras caminamos.

Después de que todo el caminar está hecho, comprobamos los nuevos compromisos poco profundos. Si no hemos visto ninguna referencia nueva marcada en un nuevo compromiso superficial, sabemos que todas las referencias nuevas / actualizadas son accesibles solo con nuestra historia y .git/shallow .
El compromiso superficial en cuestión no es necesario y se puede tirar.

Por lo tanto, el código.

El bucle aquí (para caminar a través de confirmaciones) es básicamente:

  1. obtener una confirmación de la cola
  2. ignorar si es VISTO o INVERTIDO
  3. márcalo
  4. pasar por todos los padres y ..
    • 5.aa marcarlo si nunca esta marcado antes
    • 5.b ponerlo de nuevo en la cola

Lo que hacemos en este parche es soltar el paso 5a porque no es necesario.
La confirmación marcada en 5a se vuelve a poner en la cola, y se marcará en el paso 3 en la siguiente iteración. El único caso en el que no se marcará es cuando el compromiso ya esté marcado como ININTERESTING (5a no verifica esto), que se ignorará en el paso 2.