¿Son "git add file" y "git checkout – file" simétricos?

Tengo la siguiente comprensión del git add file y git checkout -- file (pero no estoy seguro de si es correcto).

Cada vez que editamos files con un editor de text, lo hacemos en el directory de trabajo. Cada vez que podemos mover el file al llamado staging area ejecutando git add file_name . Si editamos el file de nuevo (después de git add ) cambiamos el file en el directory de trabajo y de esta manera, en el directory de trabajo tenemos el file en un estado "nuevo" mientras que en el staging area el file está en el "viejo" " estado.

Cuando utilizamos git add nuevamente, llevamos el file en el área de transición al estado "nuevo" (el estado del directory de trabajo).

Si hacemos git checkout -- file_name , supongo que tomamos un file del área de preparación y lo usamos para sobrescribir el file en el directory de trabajo. De esta forma, podemos llevar el file en el directory de trabajo al estado "anterior". ¿Es correcto?

Lo que tampoco me queda claro es si copymos o movemos el file desde el área de preparación. En otras palabras, git checkout -- file cambia el estado del file en el staging area . ¿Podemos decir que después de que git checkout -- file el file en el área de ensayo cambie el estado del file a su estado anterior en el área de ensayo?

Es casi, pero no del todo, simétrico.

Es cierto que el git add file copy el file al escenario (también conocido como "índice"). Sin embargo, la forma en que lo hace es un poco extraño.

Dentro de un repository git, todo está almacenado como un "object" git. Cada object tiene un nombre único, su SHA-1 (esas cadenas de 40 caracteres como 753be37fca1ed9b0f9267273b82881f8765d6b23 -esto es de un .gitignore real que tengo aquí). El nombre se construye computando el hash en el contenido del file (más o less, hay algunos trucos para asegurarse de no hacer un file desde un tree de directory o commit, y causar una colisión hash, por ejemplo). Git supone que, independientemente de los contenidos, el SHA-1 será único: no habrá dos files, treees, confirmaciones o tags anotadas diferentes que coincidan con el mismo valor.

Los files (y los enlaces simbólicos) son objects de tipo "blob". Así que un file que está en el repository de git es hash, y en algún lugar, git tiene una asignación: "file llamado .gitignore " a "valor hash 753be37fca1ed9b0f9267273b82881f8765d6b23 ").

En el repository, los treees de directorys se almacenan como objects de tipo "tree". Un object en tree contiene una list de nombres (como .gitignore ), modos, types de objects (otro tree o un blob) y SHA-1:

 $ git cat-file -p HEAD: 100644 blob 753be37fca1ed9b0f9267273b82881f8765d6b23 .gitignore [snip] 

Un object de confirmación le otorga a usted (o git) un object de tree, que finalmente le proporciona los ID de blob.

El área de ensayo ("índice"), por otro lado, es simplemente un file, .git/index . Este file contiene 1 el nombre (en una forma graciosa ligeramente comprimida que aplana los treees de directorys), el "número de etapa" en el caso de conflictos de fusión y el SHA-1. El contenido del file real es, de nuevo, un blob en el git repo. (Git no almacena directorys en el índice: el índice solo tiene files reales, usando ese formatting plano).

Entonces, cuando lo haces:

 git add file_name 

Git hace esto (más o less, y estoy deliberadamente ignorando los filters):

  1. Calcule el hash para el contenido del file file_name ( git hash-object -t blob ).
  2. Si ese object aún no está en el repository, escríbalo en el repository (usando la opción -w para hash-object ).
  3. Actualice .git/index (o $GIT_INDEX_FILE ) para que tenga la asignación bajo el nombre file_name , al nombre que salió de git hash-object . Esta es siempre una input de "etapa 0" (que es la versión normal, sin conflicto de fusión).

Por lo tanto, el file no está realmente "en" el área de preparación, ¡está realmente "en" el repository en sí! Lo que está en el área de preparación es el nombre para la asignación de SHA-1.

Por el contrario, git checkout [<tree-ish>] -- file_name hace esto:

  1. Si se le da un <tree-ish> (nombre de commit, ID de object de tree, etc., básicamente, cualquier cosa que git pueda resolver en un tree), busque el nombre del tree encontrado convirtiendo el argumento en un object de tree. Utilizando el identificador de object así localizado, actualice el hash en el índice, como etapa 0. (Si file_name nombra un object de tree, git maneja recursivamente todos los files en el directory que representa el tree). Al crear inputs de etapa 0, cualquier combinación entra en conflicto file_name ahora se resuelve.

    De lo contrario, haga la búsqueda del nombre en el índice (no estoy seguro de lo que sucede si file_name es un directory, probablemente git lea el directory de trabajo). Convierta el nombre de file_name a un ID de object (que será un blob en este punto). Si no hay input en la etapa 0, salga el error con el post "no modificado", a less que se le dé -m , --ours , --theirs opciones. El uso de -m "desunirá" el file (eliminará la input de la etapa 0 y volverá a crear la combinación conflictiva 2 ), mientras que --ours y --theirs dejarán la input de la etapa 0 en su lugar (un conflicto resuelto queda resuelto).

  2. En cualquier caso, si esto aún no se ha borrado, use el blob SHA-1 (s) ubicado para extraer la copy repo (o copys, si el nombre del file_name nombra un directory) en el directory de trabajo.

Por lo tanto, la versión corta es "sí y no": el process de git checkout algunas veces modifica el índice, y algunas veces solo lo usa. Sin embargo, el file en sí nunca se almacena en el índice, solo en el repository. Si git add un file, cámbialo un poco más y vuelve a git add , esto deja atrás lo que git fsck encontrará como un "bloque que cuelga": un object sin reference.


1 Estoy omitiendo deliberadamente muchas otras cosas en el índice que están ahí para hacer que git funcione bien, y permiten --assume-unchanged etc. (Aquí no son relevantes para la acción de agregar / extraer).

2 Esta recreación respeta cualquier cambio en merge.conflictstyle , por lo que si decides que te gusta diff3 output y ya tienes una combinación conflictiva sin el estilo diff3 , puedes cambiar la configuration de git y usar git checkout -m para get un nuevo funcionamiento- directory fusionarse con el nuevo estilo.

Cuando agrega un file por git add que marcó que desea enviar el file exactamente con ese estado. Git restring ese estado del file y lo mantiene sin cambios mientras lo confirmas o restableces. Por lo tanto, todas las manipulaciones con file después de la puesta en escena serían con el file en el directory de trabajo, no en etapas.
Cuando ejecute git checkout git solo cambiará los files no procesados ​​a revisión HEAD. Para cambiar los files por etapas a su revisión HEAD, debe ejecutar git reset