Tratando de entender git tracking, staging, etc.

He visto muchos hilos en esto, pero las respuestas tienden a ser inconsistentes y contradictorias, así que si es posible, busco una explicación lo más corta posible en términos muy claros:

Según mi comprensión actual, tenemos un directory de trabajo de files con los que estamos haciendo nuestro proyecto, un área de ensayo y un repository . El área de ensayo es, técnicamente, también un índice y utiliza un caching , solo una gran list en algún lugar de todos los files del directory de trabajo que están progtwigdos para una confirmación , que copy los cambios incrementales en el repository.

Así que, por ejemplo, tengo un file, test.txt , escribo "1234" dentro de él y lo agrego al área de ensayo y luego lo test.txt al repository, así que asumo que la totalidad de test.txt se guarda en algún file repo en alguna parte. Luego edito el file y cambio el text a "1235" y confirmo ese cambio. Supongo que el repository ahora no guarda otra copy de "1235", sino que simplemente señala algo así como "el cuarto carácter cambió de '4' a '5'" o algo así.

De todos modos, antes de comenzar esto, test.txt fue test.txt . Luego, cuando git add test.txt , ahora se convierte en un file rastreado , un nuevo file y un file en etapas que está en el índice.

Entonces, si hago git commit -m "some message" el file se convierte en … ¿qué? ¿Comprometido? ¿Rastreado pero no organizado?

Y luego, si edito el file nuevamente, se rastrea y modifica , pero no necesariamente a less que lo vuelva a agregar, etc.

Intento entender qué se define como qué dónde. Es mi entendimiento correcto hasta ahora? ¿Dónde necesito corrección?

Antes de sumergirme en el rest de esto, la definición de file rastreado es trivial: se rastrea un file si y solo si está actualmente en el índice.

Eso es todo lo que necesita probar: "¿Está la ruta P en el índice?" Si es así, P es rastreado. Si no, P no se rastrea (y, por lo tanto, simplemente no se rastrea, o tal vez no se rastrea y también se ignora).

¡La parte difícil es descubrir qué hay en el índice!

Preguntas y respuestas directas

Según mi comprensión actual, tenemos un directory de trabajo de files con los que estamos haciendo nuestro proyecto, un área de ensayo y un repository . El área de ensayo es técnicamente también un índice

Sí. El repository en sí es un tipo de database; dentro de esta database tenemos cuatro types de objects. Lo más interesante en este punto son los objects de compromiso , 1 que actúan como instantáneas, creadas guardando -como una copy permanente, con algo de información adicional- lo que esté en el índice en el momento en que ejecute git commit . El segundo más interesante es el object blob . Los blobs tienen múltiples usos, pero el principal es almacenar el contenido del file, que veremos en un momento.

Crucialmente, siempre hay un compromiso actual . Puede nombrar de varias maneras, pero la única forma en que siempre funciona es la palabra HEAD . El símbolo @ también suele funcionar (solo falla en versiones verdaderamente antiguas de Git). Sin embargo, el compromiso que es "actual" cambia con el time.

… y usa un caching ,

La memory caching es solo un tercer término para el área índice / estadificación.

solo una gran list en alguna parte de todos los files del directory de trabajo que están progtwigdos para una confirmación , que copy los cambios incrementales en el repository.

Esto no es del todo correcto.

El formatting de material en el índice / área de ensayo / caching generalmente no es interesante (y está mal documentado y sujeto a cambios también), pero como ya se señaló, cada confirmación actúa como una instantánea completa, no como un set de cambios.

Así que, por ejemplo, tengo un file, test.txt , escribo "1234" dentro de él y lo agrego al área de ensayo y luego lo test.txt al repository, así que asumo que la totalidad de test.txt se guarda en algún file repo en alguna parte.

Los datos 1234\n , es decir, se guardan en un object , específicamente uno de los objects blob mencionados anteriormente. Eso no necesariamente significa file . Los objects de repository tienen un formatting interno, sobre el cual se hacen relativamente pocas promises. Si desea profundizar en estos detalles, debe saber que pueden almacenarse sueltos (uno por file en files separados) o empaquetados (muchos en un file de package, con un índice de package que no está relacionado con el índice / área de ensayo) / índice de caching).

Le prometen que puede extraer cualquier object, por ID, a su forma original, usando git cat-file --batch (esto produce datos sin procesar y es un poco complicado de usar) o git cat-file -p (esto produce una variante bastante impresa y es fácil de usar). Para los objects tag y commit, los datos originales generalmente ya se pueden imprimir, por lo que git cat-file -p <object-id> imprime como está. Los objects de tree se mezclan, por lo que git cat-file -p convierte contenido binary conocido en text. Los objects Blob se guardan exactamente como están. 2

Luego edito el file y cambio el text a "1235" y confirmo ese cambio. Supongo que el repository ahora no guarda otra copy de "1235", sino que simplemente señala algo así como "el cuarto carácter cambió de '4' a '5'" o algo así.

No, esto está bastante mal. El nuevo compromiso tiene una copy nueva y completa del nuevo contenido. Además, cada blob es una instantánea completa de esa versión particular de ese contenido de file particular, independientemente del nombre del file. (Si estos dos blobs son objects sueltos, y ambos son files muy grandes, por ejemplo, 4,5 GB de datos de DVD cada uno, y no muy comprimibles), entonces ha utilizado 9 GB de espacio en disco para estos dos objects sueltos. sección de compression y embalaje de objects a continuación para cuando esto es o no es un problema).

Sin embargo, si almacena el mismo contenido en dos confirmaciones separadas, ya sea con el mismo nombre o un nombre diferente, almacena el blob solo una vez. Esto se mantiene incluso si almacena un file dos veces en una única confirmación. Por ejemplo, la identificación hash de cualquier file que 3b18e512dba79e4c8300dd08aeb37f8e728b8dad solo el text Hello world (y una nueva línea) es 3b18e512dba79e4c8300dd08aeb37f8e728b8dad :

 $ echo hello world | git hash-object -t blob --stdin 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 

Si crea seis files que contienen solo una línea hello world , su tree objects (vea la nota al pie 1) tendrá seis nombres asociados con esta identificación hash:

 $ for i in 1 2 3 4 5 6; do > echo hello world > copy_$i.txt; git add copy_$i.txt > done $ git commit -m 'six is just one' [master (root-commit) 5a66ef1] six is just one 6 files changed, 6 insertions(+) create mode 100644 copy_1.txt create mode 100644 copy_2.txt create mode 100644 copy_3.txt create mode 100644 copy_4.txt create mode 100644 copy_5.txt create mode 100644 copy_6.txt $ git cat-file -p HEAD^{tree} 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad copy_1.txt 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad copy_2.txt 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad copy_3.txt 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad copy_4.txt 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad copy_5.txt 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad copy_6.txt 

y ahí estamos: seis files, un object.

Sin embargo, aparte de eso, digamos que su file test.txt tiene 1234 más una nueva línea. Su ID de hash es entonces:

 $ echo 1234 | git hash-object -t blob --stdin 81c545efebe5f57d4cab2ba9ec294c4b0cadf672 

Cada file en el universo, más precisamente, el universo de todos los files que alguna vez estará en su repository 3 , que tiene el contenido 1234\n tiene este hash. No importa si se llama test.txt . No importa quién lo hizo o cuándo. No importa si está almacenado en un disco local o en la nube. 4 Lo único que importa es que 1234\n hash al número anterior.

Mientras tanto, 1235\n hashes a un número diferente:

 $ echo 1235 | git hash-object -t blob --stdin d729899c33fcf5c75fda5369a64898c85a46bcf7 

Por lo tanto, sus 1235\n contenidos entran en este otro blob.

Si el blob ya está en la database, no pasa nada interesante: simplemente reutilícelo. Si no está en la database, Git lo agrega a la database. El blob debe tener una ID hash única, diferente de cualquier otro object en la database. Siempre lo hace, sin embargo; ver la nota 3 de nuevo.


1 Para completar, los cuatro types de objects son label o label anotada ; cometer tree ; y blob . Una confirmación es típicamente bastante corta: testing git cat-file -p HEAD para ver tu confirmación actual. Tenga en count que se refiere, por ID de hash, a exactamente un tree . Este tree a su vez se refiere a cada uno de sus files, dando sus nombres y sus identificadores hash blob. Si tiene subdirectorys, se almacenan como subtreees.

2 Puede habilitar conversiones particulares, como transformaciones de final de línea, utilizando .gitattributes y otros trucos. Estas conversiones ocurren durante las operaciones de index-vs-work-tree; la representación en repo siempre coincide exactamente con la representación en el índice, por razones técnicas (en particular, el índice almacena solo el ID de hash del object, por lo que el object debe estar en "formatting repo" en este punto).

3 Así es como puedes convertir el SHA-1 en un problema para Git. Simplemente busque dos files diferentes cuyo blob-hash sea el mismo, y ya no podrá almacenar ambos files en el mismo repository. Como el blob-hash de Git no es un hash SHA-1 directo de los dos files, el file de ejemplo proporcionado por los investigadores no es en sí un problema para Git. Algún otro par de files sería.

La posibilidad de que dos ID de object colisionen random es (actualmente, con SHA-1) 1 en 2 160 , que es tan pequeña que podemos ignorarla. Sin embargo, debido a la paradoja del cumpleaños , es aconsejable mantener la cantidad de objects en cualquier repository de Git por debajo de unos cuatrillones . Si el object promedio tomara solo un byte, se encontraría con problemas después de unos miles de terabytes, es decir, unos pocos exabytes. Obviamente, el tamaño promedio del object es mayor, por lo tanto, calcule muchos exabytes del tamaño del repository antes de que surjan problemas, excepto, por supuesto, para el agrietamiento por ingeniería genética de Git.

4 Git no almacena literalmente cosas en el almacenamiento en la nube, ya que le "gustan" los filesystems locales, aunque no hay ninguna razón por la que no pueda usar un sistema de files local respaldado por la nube. De hecho, Dropbox y demás intentan hacer exactamente eso, pero también insisten en resolver acciones tomadas en diferentes computadoras usando files del mismo nombre, lo que interfiere bastante con la operación de Git, ya que Git necesita mantener sus propios metadatos sobre su interno. files. Si usas Git-LFS, tiene su propio truco para download files grandes en áreas de almacenamiento separadas, usando filters "limpios" y "difuminados" y algunos astutos files .gitattributes .


Comprender (y ver) el índice

Si quiere ver el índice directamente, Git tiene lo que llama un command de fontanería , es decir, un command no destinado a los humanos, para hacer esto: git ls-files . Pruébelo, especialmente con el argumento de la --stage , pero tenga en count que en un gran repository produce demasiada producción.

Por lo general, es mejor ver el índice de forma indirecta . Recuerde, siempre hay una confirmación actual (HEAD o @). Siempre hay un tree de trabajo actual que puedes ver también. Supongamos que tiene estos dos files:

  HEAD index work-tree --------- --------- --------- README.md README.md README.md test.txt test.txt test.txt 

Puede ejecutar dos git diff s, uno para comparar HEAD vs index y otro para comparar index vs work-tree. Esto es lo que hace el git status .

Si las tres versiones de ambos files son exactamente iguales, y ejecuta el git status , no dice nada en absoluto.

Si cambia la versión del tree de test.txt de test.txt y ejecuta el git status , no encuentra diferencia en HEAD vs index, pero encuentra que el index frente al work-tree son diferentes. Por lo tanto, le informa que ha realizado cambios no test.txt en test.txt .

Si copy la nueva versión del tree de trabajo en el índice y ejecuta nuevamente el git status , ahora el índice y el tree de trabajo coinciden, pero el primer índice diff-HEAD vs-ya no coincide. Entonces, ahora el git status dice que has realizado cambios por etapas .

Si agrega un nuevo file z al tree de trabajo, la image se ve así:

  HEAD index work-tree --------- --------- --------- README.md README.md README.md test.txt test.txt test.txt z 

Ahora el git status dice que tienes un file sin seguimiento , porque z no está en el índice. Usa git add z para copyrlo al índice y obtienes esta image:

  HEAD index work-tree --------- --------- --------- README.md README.md README.md test.txt test.txt test.txt zz 

Ejecuta el git status nuevamente y hace dos git diff s nuevamente. Ahora z está en el índice (se realiza un seguimiento) pero no está en HEAD, por lo que es un file nuevo y está organizado. Es lo mismo en el índice y en el tree de trabajo, por lo que el git status no lo menciona por segunda vez. 5


5 Realmente me gusta el resultado del git status --short , que muestra los dos diffs a la vez para cada file. Los files sin seguimiento obtienen dos signos de interrogación, mientras que los files rastreados obtienen una o dos letras, ubicadas en las dos primeras columnas, para describir HEAD-vs-index e index-vs-work-tree.

Compresión y embalaje de objects

notas algo así como "el cuarto personaje cambió de '4' a '5'" …

Git hace este tipo de cosas, pero, en una desviación de la mayoría de los sistemas de control de versiones, hace este tipo de compression delta en un nivel inferior al de blobs.

Los objects sueltos simplemente se comprimen con zlib. Puede encontrar estos objects en .git/objects/ , bajo sus nombres de hash-ID (con los primeros dos caracteres separados para hacer un directory, para que los directorys no sean demasiado grandes). Abra uno de estos files, léalo y de-zlib-compress, y verá el formatting de datos de objects sueltos.

Sin embargo, cuando hay suficientes objects sueltos para que valga la pena, Git empaca los objects sueltos. Este empaque usa un algorithm heurístico (porque hacer un trabajo perfecto requiere demasiado cálculo), pero básicamente equivale a encontrar objects que "huelen lo suficiente" para sugerir que la encoding delta de un object basado en algún otro object hará un package más pequeño file.

Si el empaquetador de objects elige estos dos files, notará que uno es 1234\n y el otro es 1235\n y que puede representar el segundo object como "replace el 4 ° carácter del object anterior". No hay una promise particular de que 1235\n se basará en 1234\n -puede ir en el otro order- pero como regla, Git intenta mantener los objects "más recientes" en la forma "less comprimida" en la teoría de que el más reciente se accede a la historia con más frecuencia que en la historia anterior.

Tenga en count que un object se puede basar en un object anterior que se basa en objects anteriores. Esto se llama una cadena o una cadena delta : debemos expandir cada object hasta la base para aplicar cada delta a su vez con el fin de llegar al último object de la cadena. El deltificador de Git limitará las longitudes de cadena delta; vea el argumento --depth a las varias cosas que lo invocan.

(En este caso particular, el ID del object es mucho más largo que el contenido del object, por lo que no hay ningún beneficio en tratar de hacer un delta aquí. Sin embargo, el principio se aplica a files más grandes. Tenga en count también que la compression delta siempre debe aplicarse antes de cualquier compression binaria: la compression delta se basa en los valores de entropía de Shannon más pequeños en los files de input, mientras que los compresores como gzip y bzip2 funcionan al exprimir dicha entropía).

El formatting de los files del package ha cambiado varias veces. Ver también los deltas de files de package de Git en lugar de instantáneas?

Intereting Posts