Citando a Linus Torvalds cuando se le preguntó cuántos archivos puede manejar Git durante su Tech Talk en Google en 2007 (43:09):
... Git rastrea tu contenido. Nunca rastrea un solo archivo. No puedes rastrear un archivo en Git. Lo que puede hacer es rastrear un proyecto que tiene un solo archivo, pero si su proyecto tiene un solo archivo, asegúrese de hacerlo y puede hacerlo, pero si rastrea 10,000 archivos, Git nunca los verá como archivos individuales. Git piensa todo como el contenido completo. Toda la historia en Git se basa en la historia de todo el proyecto ...
(Transcripciones aquí ).
Sin embargo, cuando se sumerge en el libro de Git , lo primero que le dicen es que un archivo en Git puede ser o bien un seguimiento o sin seguimiento . Además, me parece que toda la experiencia de Git está orientada a la versión de archivos. Cuando se usa git diff
o la git status
salida se presenta por archivo. Al usarlo git add
, también puede elegir por archivo. Incluso puede revisar el historial en un archivo y es muy rápido.
¿Cómo debe interpretarse esta declaración? En términos de seguimiento de archivos, ¿en qué se diferencia Git de otros sistemas de control de origen, como CVS?
fuente
Respuestas:
En CVS, el historial se rastreó por archivo. Una rama puede constar de varios archivos con sus propias revisiones, cada una con su propio número de versión. CVS se basó en RCS ( Revision Control System ), que rastreó archivos individuales de manera similar.
Por otro lado, Git toma instantáneas del estado de todo el proyecto. Los archivos no son rastreados y versionados independientemente; Una revisión en el repositorio se refiere al estado de todo el proyecto, no a un archivo.
Cuando Git se refiere al seguimiento de un archivo, significa simplemente que se incluirá en el historial del proyecto. La charla de Linus no se refería a los archivos de seguimiento en el contexto de Git, sino que contrastaba el modelo CVS y RCS con el modelo basado en instantáneas utilizado en Git.
fuente
$Id$
en un archivo. Lo mismo no funciona en git, porque el diseño es diferente.Estoy de acuerdo con brian m. La respuesta de Carlson : Linus realmente distingue, al menos en parte, entre los sistemas de control de versiones orientados a archivos y los de confirmación. Pero creo que hay más que eso.
En mi libro , que está estancado y puede que nunca termine, traté de encontrar una taxonomía para los sistemas de control de versiones. En mi taxonomía, el término para lo que nos interesa aquí es la atomicidad del sistema de control de versiones. Vea lo que actualmente es la página 22. Cuando un VCS tiene una atomicidad a nivel de archivo, de hecho hay un historial para cada archivo. El VCS debe recordar el nombre del archivo y lo que se le ocurrió en cada punto.
Git no hace eso. Git solo tiene un historial de commits: el commit es su unidad de atomicidad, y el historial es el conjunto de commits en el repositorio. Lo que recuerda un commit son los datos, un árbol completo lleno de nombres de archivos y el contenido que acompaña a cada uno de esos archivos, además de algunos metadatos: por ejemplo, quién realizó el commit, cuándo y por qué, y el ID de hash interno de Git del commit del padre commit. (Es este padre, y el gráfico de aciclismo dirigido formado al leer todos los commits y sus padres, ese es el historial en un repositorio).
Tenga en cuenta que un VCS puede estar orientado a la confirmación, pero aún así almacenar datos archivo por archivo. Es un detalle de implementación, aunque a veces es importante, y Git tampoco lo hace. En cambio, cada confirmación registra un árbol , con el objeto del árbol que codifica nombres de archivos , modos (es decir, ¿este archivo es ejecutable o no?), Y un puntero al contenido real del archivo . El contenido en sí se almacena de forma independiente, en un objeto blob . Al igual que un objeto commit, un blob obtiene una identificación hash que es exclusiva de su contenido, pero a diferencia de un commit, que solo puede aparecer una vez, el blob puede aparecer en muchos commits. Entonces, el contenido del archivo subyacente en Git se almacena directamente como un blob, y luego indirectamente en un objeto de árbol cuya ID de hash se registra (directa o indirectamente) en el objeto de confirmación.
Cuando le pides a Git que te muestre el historial de un archivo usando:
lo que Git realmente está haciendo es recorrer el historial de confirmaciones , que es el único historial que tiene Git, pero no mostrarle ninguna de estas confirmaciones a menos que:
(pero algunas de estas condiciones se pueden modificar a través de
git log
opciones adicionales , y hay un efecto secundario muy difícil de describir llamado Simplificación del historial que hace que Git omita algunas confirmaciones del recorrido del historial por completo). El historial de archivos que ve aquí no existe exactamente en el repositorio, en cierto sentido: en cambio, es solo un subconjunto sintético del historial real. ¡Obtendrá un "historial de archivos" diferente si usa diferentesgit log
opciones!fuente
Lo confuso está aquí:
Git a menudo usa hashes de 160 bits en lugar de objetos en su propio repositorio. Un árbol de archivos es básicamente una lista de nombres y hashes asociados con el contenido de cada uno (más algunos metadatos).
Pero el hash de 160 bits identifica de forma exclusiva el contenido (dentro del universo de la base de datos git). Entonces, un árbol con hashes como contenido incluye el contenido en su estado.
Si cambia el estado del contenido de un archivo, su hash cambia. Pero si su hash cambia, el hash asociado con el contenido del nombre del archivo también cambia. Lo que a su vez cambia el hash del "árbol de directorios".
Cuando una base de datos git almacena un árbol de directorios, ese árbol de directorios implica e incluye todo el contenido de todos los subdirectorios y todos los archivos que contiene .
Está organizado en una estructura de árbol con punteros (inmutables, reutilizables) a blobs u otros árboles, pero lógicamente es una instantánea única del contenido completo de todo el árbol. La representación en la base de datos git no es el contenido de datos planos, pero lógicamente son todos sus datos y nada más.
Si serializa el árbol en un sistema de archivos, elimina todas las carpetas .git y le dice a git que agregue el árbol nuevamente a su base de datos, terminaría sin agregar nada a la base de datos: el elemento ya estaría allí.
Puede ser útil pensar en los hashes de git como un puntero contado de referencia a datos inmutables.
Si creó una aplicación en torno a eso, un documento es un grupo de páginas, que tienen capas, que tienen grupos, que tienen objetos.
Cuando desee cambiar un objeto, debe crear un grupo completamente nuevo para él. Si desea cambiar un grupo, debe crear una nueva capa, que necesita una nueva página, que necesita un nuevo documento.
Cada vez que cambia un solo objeto, genera un nuevo documento. El documento antiguo sigue existiendo. El documento nuevo y el antiguo comparten la mayor parte de su contenido: tienen las mismas páginas (excepto 1). Esa página tiene las mismas capas (excepto 1). Esa capa tiene los mismos grupos (excepto 1). Ese grupo tiene los mismos objetos (excepto 1).
Y por lo mismo, me refiero lógicamente a una copia, pero en términos de implementación es solo otro puntero contado de referencia al mismo objeto inmutable.
Un repositorio git se parece mucho a eso.
Esto significa que un conjunto de cambios git dado contiene su mensaje de confirmación (como un código hash), contiene su árbol de trabajo y contiene sus cambios principales.
Esos cambios principales contienen sus cambios principales, todo el camino de regreso.
La parte del repositorio de git que contiene la historia es esa cadena de cambios. Esa cadena de cambios lo hace en un nivel superior al árbol de "directorio": desde un árbol de "directorio", no se puede acceder de forma exclusiva a un conjunto de cambios y a la cadena de cambios.
Para averiguar qué le sucede a un archivo, comience con ese archivo en un conjunto de cambios. Ese conjunto de cambios tiene una historia. A menudo en ese historial, existe el mismo archivo con nombre, a veces con el mismo contenido. Si el contenido es el mismo, no hubo cambios en el archivo. Si es diferente, hay un cambio y se debe trabajar para determinar exactamente qué.
A veces el archivo se ha ido; pero, el árbol de "directorio" podría tener otro archivo con el mismo contenido (mismo código hash), por lo que podemos rastrearlo de esa manera (nota; es por eso que desea un commit-to-move un archivo separado de un commit-to -editar). O el mismo nombre de archivo, y después de verificar el archivo es lo suficientemente similar.
Entonces git puede juntar un "historial de archivos".
Pero este historial de archivos proviene del análisis eficiente del "conjunto de cambios completo", no de un enlace de una versión del archivo a otra.
fuente
"git no rastrea archivos" básicamente significa que las confirmaciones de git consisten en una instantánea del árbol de archivos que conecta una ruta en el árbol a un "blob" y un gráfico de confirmación que rastrea el historial de confirmaciones . Todo lo demás se reconstruye sobre la marcha mediante comandos como "git log" y "git blame". Esta reconstrucción se puede decir a través de varias opciones de lo difícil que debería ser buscar cambios basados en archivos. La heurística predeterminada puede determinar cuándo un blob cambia de lugar en el árbol de archivos sin cambios, o cuándo un archivo está asociado con un blob diferente al anterior. Los mecanismos de compresión que usa Git no se preocupan mucho por los límites de blob / archivo. Si el contenido ya está en algún lugar, esto mantendrá el crecimiento del repositorio pequeño sin asociar los diversos blobs.
Ahora ese es el repositorio. Git también tiene un árbol de trabajo, y en este árbol de trabajo hay archivos rastreados y no rastreados. Solo los archivos rastreados se registran en el índice (área de almacenamiento provisional? Caché?) Y solo lo que se rastrea allí ingresa al repositorio.
El índice está orientado a archivos y hay algunos comandos orientados a archivos para manipularlo. Pero lo que termina en el repositorio es solo confirmaciones en forma de instantáneas del árbol de archivos y los datos de blobs asociados y los antepasados de la confirmación.
Dado que Git no rastrea los historiales de archivos y los cambios de nombre y su eficiencia no depende de ellos, a veces debe intentarlo varias veces con diferentes opciones hasta que Git produzca el historial / diferencias / culpas que le interesan para historiales no triviales.
Eso es diferente con sistemas como Subversion que registran en lugar de reconstruir historias. Si no está registrado, no puedes escucharlo.
Realmente construí un instalador diferencial en un momento que solo comparó los árboles de lanzamiento al registrarlos en Git y luego producir un script que duplica su efecto. Como a veces se movieron árboles enteros, esto produjo instaladores diferenciales mucho más pequeños que sobrescribir / eliminar todo lo que habría producido.
fuente
Git no rastrea un archivo directamente, pero rastrea instantáneas del repositorio, y estas instantáneas consisten en archivos.
Aquí hay una manera de verlo.
En otros sistemas de control de versiones (SVN, Rational ClearCase), puede hacer clic derecho en un archivo y obtener su historial de cambios .
En Git, no hay un comando directo que haga esto. Ver esta pregunta . Se sorprenderá de cuántas respuestas diferentes hay. No hay una respuesta simple porque Git no solo rastrea un archivo , no de la manera en que lo hace SVN o ClearCase.
fuente
git log
o algún programa construido sobre eso (o algún alias que hace lo mismo). Pero incluso si hubiera muchas formas diferentes, como Joe dice, eso también es cierto para mostrar el historial de la sucursal. (tambiéngit log -p <file>
está integrado y hace exactamente eso)El "contenido" de seguimiento, por cierto, es lo que llevó a no rastrear directorios vacíos.
Por eso, si obtiene el último archivo de una carpeta, la carpeta en sí se elimina .
Ese no siempre fue el caso, y solo Git 1.4 (mayo de 2006) hizo cumplir esa política de "seguimiento de contenido" con commit 443f833 :
Eso se hizo eco años después, en enero de 2011, con commit 8fe533 , Git v1.7.4:
Mientras tanto, con Git 1.4.3 (septiembre de 2006), Git comienza a limitar el contenido no rastreado a carpetas no vacías, con commit 2074cb0 :
El seguimiento del contenido es lo que permitió a git culpar, desde el principio (Git 1.4.4, octubre de 2006, commit cee7f24 ) sea más eficiente :
Ese (contenido de seguimiento) también es lo que puso git add en la API de Git, con Git 1.5.0 (diciembre de 2006, commit 366bfcb )
Eso es lo que hizo
git add --interactive
posible, con el mismo Git 1.5.0 ( commit 5cde71d )Esa es también la razón por la cual, para eliminar recursivamente todo el contenido de un directorio, debe pasar la
-r
opción, no solo el nombre del directorio como<path>
(todavía Git 1.5.0, commit 9f95069 ).Ver el contenido del archivo en lugar del archivo en sí es lo que permite un escenario de fusión como el descrito en commit 1de70db (Git v2.18.0-rc0, abril de 2018)
Commit 37b65ce , Git v2.21.0-rc0, diciembre de 2018, recientemente mejoró la resolución de conflictos en conflicto.
Y commit bbafc9c firther ilustra la importancia de considerar el contenido del archivo , al mejorar el manejo de los conflictos rename / rename (2to1):
fuente