¿Son los deltas de files del package de Git en lugar de instantáneas?

Una de las diferencias key entre Git y la mayoría de los otros sistemas de control de versiones es que los otros tienden a almacenar confirmaciones como una serie de deltas, sets de cambios entre una confirmación y la siguiente. Esto parece lógico, ya que es la cantidad más pequeña posible de información sobre una confirmación. Pero cuanto más largo sea el historial de compromisos, más cálculos se requieren para comparar ranges de revisiones.

Por el contrario, Git almacena una instantánea completa de todo el proyecto en cada revisión . La razón por la que esto no hace que el tamaño del repository crezca dramáticamente con cada confirmación es que cada file en el proyecto se almacena como un file en el subdirectory Git, llamado así por el hash de su contenido. Entonces, si el contenido no ha cambiado, el hash no ha cambiado, y la confirmación solo apunta al mismo file. Y hay otras optimizaciones también.

Todo esto tenía sentido para mí hasta que tropecé con esta información sobre los files del package , en la que Git coloca los datos periódicamente para ahorrar espacio:

Para ahorrar ese espacio, Git utiliza el file de package. Este es un formatting en el que Git solo saveá la parte que ha cambiado en el segundo file, con un puntero al file similar.

¿No se trata básicamente de volver a almacenar deltas? Si no, ¿cómo es diferente? ¿Cómo evita esto someter a Git a los mismos problemas que otros sistemas de control de versiones?

Por ejemplo, Subversion usa deltas, y revertir 50 versiones significa deshacer 50 diffs, mientras que con Git solo puede tomar la instantánea adecuada. A less que git también almacene 50 diffs en los packages-files … ¿hay algún mecanismo que diga "después de un pequeño número de deltas, almacenaremos una instantánea completamente nueva" para que no acumulemos un set de cambios demasiado grande? ¿De qué otra forma podría evitar Git las desventajas de los deltas?

Resumen:
Los files del package de Git están cuidadosamente construidos para usar de manera efectiva cachings de disco y proporcionar patrones de acceso "agradables" para commands comunes y para leer objects recientemente referencedos.


El formatting de file del package de Git es bastante flexible (ver Documentación / técnica / package-format.txt , o The Packfile en The Git Community Book ). Los files de package almacenan objects de dos forms principales: "sin degradar" (tomar los datos del object sin procesar y desinflar-comprimirlo) o "deltificar" (formar un delta contra algún otro object y luego desinflar-comprimir los datos delta resultantes). Los objects almacenados en un package pueden estar en cualquier order (no deben (necesariamente) orderarse por tipo de object, nombre de object o cualquier otro atributo) y los objects delificados pueden realizarse contra cualquier otro object adecuado del mismo tipo.

El command pack-objects de Git usa varias heurísticas para proporcionar una excelente localidad de reference para commands comunes. Estas heurísticas controlan tanto la selección de objects base para objects delificados como el order de los objects. Cada mecanismo es en su mayoría independiente, pero comparten algunos objectives.

Git forma largas cadenas de objects comprimidos delta, pero la heurística trata de asegurarse de que solo los objects "viejos" estén en los extremos de las cadenas largas. La memory caching base delta (cuyo tamaño está controlado por la variable de configuration core.deltaBaseCacheLimit ) se usa automáticamente y puede networkingucir enormemente el número de "reconstrucciones" requeridas para los commands que necesitan leer una gran cantidad de objects (por ejemplo, git log -p ).

Heurística de compression delta

Un repository típico de Git almacena una gran cantidad de objects, por lo que no puede compararlos razonablemente para encontrar los pares (y las cadenas) que producirán las representaciones delta más pequeñas.

La heurística de selección de base delta se basa en la idea de que las buenas bases delta se encontrarán entre objects con nombres de file y tamaños similares. Cada tipo de object se procesa por separado (es decir, un object de un tipo nunca se usará como base delta para un object de otro tipo).

A los fines de la selección de base delta, los objects se orderan (principalmente) por nombre de file y luego por tamaño. Una window en esta list orderada se usa para limitar el número de objects que se consideran bases delta potenciales. Si no se encuentra una representación 1 delta "suficientemente buena" para un object entre los objects en su window, entonces el object no se comprimirá delta.

El tamaño de la window está controlado por la opción --window= de git pack-objects , o la variable de configuration pack.window . La profundidad máxima de una cadena delta está controlada por la opción --depth= de git pack-objects , o la variable de configuration pack.depth . La opción --aggressive de git gc aumenta mucho el tamaño de la window y la profundidad máxima para intentar crear un file de package más pequeño.

La sorting de nombre de file agrupa los objects para las inputs con nombres idénticos (o al less finales similares (por ejemplo, .c )). El tipo de tamaño va de mayor a menor, por lo que los deltas que eliminan datos son preferibles a los deltas que agregan datos (ya que los deltas de eliminación tienen representaciones más cortas) y los objects mayores más antiguos (generalmente más nuevos) tienden a representarse con compression simple.

1 Lo que califica como "lo suficientemente bueno" depende del tamaño del object en cuestión y su posible base delta, así como de cuán profunda sería la cadena delta resultante.

Heurística de orderamiento de objects

Los objects se almacenan en los files del package en un order de "reference más reciente". Los objects necesarios para rebuild la historia más reciente se colocan antes en el package y estarán muy juntos. Esto generalmente funciona bien para cachings de disco del sistema operativo.

Todos los objects de confirmación se orderan por date de confirmación (la más reciente primero) y se almacenan juntos. Esta colocación y orderamiento optimiza los accesos al disco necesarios para recorrer el gráfico del historial y extraer información básica de compromiso (por ejemplo, git log ).

Los objects de tree y blob se almacenan comenzando con el tree desde la primera confirmación almacenada (más reciente). Cada tree se procesa en profundidad, almacenando cualquier object que no haya sido almacenado. Esto coloca todos los treees y blobs necesarios para rebuild el compromiso más reciente en un solo lugar. Todos los treees y blobs que aún no se han guardado pero que son necesarios para confirmaciones posteriores se almacenan a continuación, en el order de confirmación orderado.

El order final de los objects se ve ligeramente afectado por la selección base delta en el sentido de que si se selecciona un object para la representación delta y su object base aún no se ha almacenado, entonces su object base se almacena inmediatamente antes del object delificado. Esto evita posibles errores de caching de disco debido al acceso no lineal requerido para leer un object base que se habría "guardado" de forma natural más adelante en el file del package.

El uso del almacenamiento delta en el file del package es solo un detalle de la implementación. En ese nivel, Git no sabe por qué o cómo algo cambió de una revisión a la siguiente, sino que simplemente sabe que el blob B es bastante similar al blob A, excepto por estos cambios C. Por lo tanto, solo almacenará blob A y cambiará C (si elige hacerlo, también podría elegir almacenar blob A y blob B).

Al recuperar objects del file del package, el almacenamiento delta no está expuesto a la persona que llama. La persona que llama aún ve blobs completos. Entonces, Git funciona de la misma manera que siempre, sin la optimization de almacenamiento delta.

Como mencioné en " ¿Qué son los packages delgados de git? "

Git realiza la deslocalización solo en packages

Detallé la encoding delta utilizada para los files del package en " ¿Está estandarizado el algorithm de diferencia bit de git (almacenamiento delta)? "

Tenga en count que la configuration core.deltaBaseCacheLimit que controla el tamaño pnetworkingeterminado para el file del package pronto se verá afectada de 16MB a 96MB, para Git 2.0.x / 2.1 (Q3 2014).

Consulte la confirmación 4874f54 de David Kastrup (mayo de 2014):

Bump core.deltaBaseCacheLimit a 96m

El valor por defecto de 16m causa una fuerte contorsión para cadenas delta grandes combinadas con files grandes.

Aquí hay algunos puntos de reference (variante de pu de git blame ):

 time git blame -C src/xdisp.c >/dev/null 

para un repository de Emacs reempaquetado con git gc --aggressive (v1.9, que da como resultado un tamaño de window de 250) ubicado en un disco SSD.
El file en cuestión tiene aproximadamente 30000 líneas, 1Mb de tamaño y un historial con aproximadamente 2500 confirmaciones.

 16m (previous default): real 3m33.936s user 2m15.396s sys 1m17.352s 96m: real 2m5.668s user 1m50.784s sys 0m14.288s