¿Qué hace realmente la configuration autocrıtica de Git?

He leído muchas otras preguntas relacionadas con la configuration auto-crlf en Git y no estoy preguntando qué configuration auto-crlf debería usar, o cómo normalizar mis finales de línea en un proyecto. Mi pregunta tiene que ver con la comprensión de la configuration auto-crlf.

Fondo rápido:

Comencé un proyecto en Linux pero ahora también estoy empezando a trabajar en él desde un sistema Windows. En el repository y en Linux mis files usan terminaciones de línea LF. Sin embargo, a pesar de tener auto-crlf configurado en "verdadero" en mi sistema Windows (desde antes de clonar el proyecto), Git considera ciertos files "modificados" si la única diferencia son los finales de línea.

Solo considera los files "modificados" si abro un file, realizo un cambio, guardo y luego deshago todos los cambios (CRTL + Z o un deshacer manual) y vuelvo a save. Cada utilidad de diff que uso me dice que los finales de línea son la única diferencia (LF en el repository y CRLF en local).

Hasta hace poco, siempre pensé que esta configuration afectaba los files que se consideraban modificados además de la conversión. Pero después de leer por segunda vez la descripción junto con el comportamiento que estoy experimentando, estoy empezando a pensar que solo convierte las terminaciones de línea en la confirmación / salida y no tiene nada que ver con determinar qué files se han modificado.

Aquí es donde estoy leyendo la descripción de esta configuration.

¿Se supone que esta configuration también afectará qué files se consideran modificados además de manejar la conversión?

EDITAR:

Solo quería agregar a mi situación particular de "Antecedentes" a cualquiera que comparta un comportamiento similar. Después de leer la respuesta de toreks, pude determinar que mi IDE estaba "agregando" files a Git luego de savelos automáticamente. Esto estaba causando que cambiara el "mtime", que era la raíz del comportamiento "aparentemente" impar.

La verdadera respuesta es complicada y se mete en la naturaleza dual del índice de Git, que es tanto un "área de preparación" como un "caching".

También vale la pena pensar en los filters de manchas y filters limpios de Git aquí. Básicamente, todas las conversiones LF / CRLF son una forma de borrado y limpieza.

Hay tres elementos de preocupación en todo momento

Siempre que trabaje en un repository de Git, hay tres cosas que debe tener en count:

  • La confirmación actual , también conocida como HEAD . (El file .git/HEAD almacena parte o la totalidad de esta información: generalmente contiene el nombre de una twig y, a continuación, el nombre de la twig contiene el rest de la información, concretamente la identificación del hash de confirmación actual. En el modo "HEAD desconectado" , .git/HEAD sí contiene la ID de hash).

    Como todas las confirmaciones son, por definición, de solo lectura, la identificación de hash es suficiente para describirla por completo. Una vez que Git ha resuelto HEAD a una identificación hash, Git puede acceder a los files almacenados.

  • El índice Si bien la mejor descripción casual del índice es "lo que entrará en el siguiente compromiso", la forma real del índice es bastante complicada, por lo que no vamos a tener los detalles por un momento. Aquí también es donde el índice comienza a jugar su papel de "caching".

  • El tree de trabajo . Como su nombre lo indica, aquí es donde realizas tu verdadero trabajo. Tiene todos los files en su formatting normal, para que todos sus progtwigs y herramientas trabajen con ellos.

    El "formatting normal" es la frase key aquí: el formatting normal en un sistema Unix-ish es que las líneas terminan en nueva línea, mientras que el formatting normal para algunos elementos de Windows es que esa línea está terminada en CRLF o '\r\n' . (Vamos a pretender aquí que todos los files de Windows son así, aunque de hecho solo la mayoría de los files son, siendo los files binarys el primer punto obvio).

    Si piensa en manchas y filters limpios, el file en el tree de trabajo está en la forma "difuminada". Es decir, si tiene algo como Git-LFS en funcionamiento, Git-LFS puede modificar la versión del tree de trabajo del file para que difiera en gran medida de la versión comprometida. (En particular, Git-LFS engaña a Git para que guarde solo un puntero al file real, y luego Git-LFS recupera el file real, y presumiblemente demasiado grande para GitHub o lo que sea, de otro lado, así que ¿qué hay en su trabajo? ¡el tree aquí no está realmente registrado!)

Tenga en count que el índice se encuentra "entre" la confirmación HEAD solo lectura y el tree de trabajo. Esto significa que los files se pueden copyr de HEAD a index, o de index a work-tree, o de work-tree a index. (No se pueden copyr desde el índice a HEAD excepto al hacer una nueva confirmación , que luego se convierte en la confirmación actual, porque todas las confirmaciones son de solo lectura).

Los files comprometidos de solo lectura solo pueden tener un formatting

Esto es bastante obvio, pero vale la pena mencionarlo. Si los files dentro del repository tienen formatting de finalización de nueva línea, no coinciden con el formatting normal para Windows. Algo tiene que traducirse de ida y vuelta.

La traducción se hace, como señala el libro de Pro Git, durante la copy de files dentro y fuera del índice. Pero hay tres posibles lugares: si copymos de HEAD a index, eso pone un file (copy de a) en el índice; si copymos de index a work-tree, eso hace una copy en esa dirección; y si copymos del tree de trabajo al índice, eso hace una copy en la otra dirección. Ahora el formatting real del índice y las copys que nos importan comienzan a ser importantes.

El formatting del índice

El formatting del índice es complicado. Para ver el índice actual en este momento en forma legible por humanos, ejecute git ls-files --stage --debug , que arroja una gran cantidad de información. (Aunque incluso con --debug esto omite algunos detalles.) Las partes más cruciales e interesantes son las que ves incluso sin --debug , por ejemplo:

 100644 4646ce575251b07053f20285be99422d6576603e 0 xdiff/xutils.h 

El primer valor es el "modo" de un file (siempre 100644 o 100755 para un file normal), el segundo es un ID de hash Git, el tercero es un número de etapa (normalmente cero) y el último es el nombre del file .

Esta ID hash es, al less inicialmente, la misma que la ID hash en la confirmación original. Como ese file confirmado es de solo lectura, ese identificador de hash representa el file en su forma de almacenamiento permanente, no su forma de tree de trabajo.

Lo que esto significa a su vez es que el file está almacenado en el índice en su forma "limpia" (con CRLF convertido en solo LF, o Git-LFS reemplazando todo el file con un puntero). De hecho, los datos limpios ya están escritos previamente en el repository de Git, y el índice almacena solo su hash blob. Este es uno de los trucos para hacer que Git vaya rápido: la input de índice solo tiene la ID de hash (y nombre de ruta, y modo, y número de etapa, y todos esos --debug salida de --debug ).

Lo que esto significa también es que cualquier borrado (para convertir LF en CRLF, o recuperar files reales de Git-LFS) ocurre durante la copy del índice al tree de trabajo. Cualquier limpieza, para convertir CRLF en solo LF o almacenar un nuevo file fuera de Git y actualizar el puntero, ocurre durante la copy del tree de trabajo al índice.

Finalmente, qué más significa esto que Git no puede decir fácilmente, solo desde el file del tree de trabajo, si la versión de índice del file está actualizada o no. ¿La versión del tree de trabajo está modificada? La única forma de estar seguro es hacer una nueva limpieza completa, y ver si obtiene la misma ID hash para los datos resultantes; o haga una nueva extracción completa, y vea si obtiene el mismo file de tree de trabajo. Pero este process es lento: en realidad puede tomar decenas de milisegundos , incluso si no tiene que pasar por Git-LFS y recuperar o almacenar una copy del file real en otro lugar. Multiplique por muchos files, y es demasiado lento. (En un repository realmente grande, el git checkout de git checkout de una confirmación puede tomar literalmente segundos , y esto significaría que el git status y otros commands similares serían tan lentos).

Almacenamiento en caching al rescate … una especie de

La respuesta de Git a este dilema de performance es simplemente evitarlo por completo si es posible. No construyas una nueva entidad de repository y hash; no tome el object de repository existente y vuelva a expandirlo. Lo que hace Git es almacenar información sobre el file del tree de trabajo en el índice:

  ctime: 1500043102:605208000 mtime: 1500043102:605208000 

Estos dos sellos de time son el "time de cambio del inodo" y el "time de modificación del inodo", que Git copy del resultado de la llamada del sistema stat o lstat en el file del tree de trabajo. Siempre que el sistema subyacente actualice las marcas de time del tree de trabajo cada vez que cambie el file del tree de trabajo, Git solo puede comparar las marcas de time actuales en el file de tree de trabajo con las marcas de time guardadas en el índice. (Git también guarda el tamaño del file del tree de trabajo, de la misma manera). Si las marcas de time coinciden, el file debe estar "limpio". Si las marcas de time en el file del tree de trabajo son más nuevas que las que están en la memory caching, el file puede estar sucio y debemos hacer un trabajo adicional para averiguarlo con certeza. (En la práctica, las marcas de time en el file de índice también entran en juego aquí, ya que un segundo es un time muy largo en términos de cálculo. Consulte este enlace para get más detalles).

Nada de esta magia respeta la configuration actual de CRLF

Si cambia core.autocrlf o el text de un file o la mancha y / o limpia los filters para algunos files en particular, esto afecta la forma en que el file se copyrá desde el índice al tree de trabajo, o desde el tree de trabajo para el índice. Pero no tiene ningún efecto sobre los datos de caching almacenados en el file de índice. Esto significa que Git pensará, posiblemente incorrectamente, que el file del tree de trabajo está "limpio", cuando no lo está.

Solo considera los files "modificados" si abro un file, realizo un cambio, guardo y luego deshago todos los cambios (CRTL + Z o un deshacer manual) y vuelvo a save.

Escribir en el file cambia las marcas de time en el file del tree de trabajo, de modo que Git hará más trabajo al comparar el file del tree de trabajo con la versión del índice.

Recapping cuando ocurren las conversiones

… Estoy empezando a pensar que [Git] solo convierte las terminaciones de línea en el compromiso / pago …

Eso es principalmente correcto. La conversión de CRLF a LF solo ocurrirá:

  • en git add , que copy del tree de trabajo al índice, o cualquier cosa que invoca git add o su código subyacente (incluida la adición de git commit -a o git commit [--only | --include] -- <paths> )
  • siempre que el file esté marcado para este tipo de "limpieza": está clasificado como text y usted ha habilitado la conversión de CRLF a LF.

Mientras tanto, la conversión de solo LF a CRLF ocurre:

  • en git checkout , cuando copy del índice al tree de trabajo, o algunos otros casos relacionados más oscuros ( git read-tree -u por ejemplo)
  • siempre que el file esté marcado para este tipo de "borrado": está clasificado como text y usted ha habilitado la conversión de LF a CRLF para él.

Tenga en count que si un file se clasifica o no como text depende de muchas configuraciones. En general, lo que está en .gitattributes anula core.* configuration core.* , Pero si no se establece nada en .gitattributes , se aplicará la configuration core.* .

Algunas de las otras herramientas, como git show y git cat-file -p , ahora pueden hacer conversiones de text a través de opciones (en los viejos times git show <commit>:<path> mostraba solo los datos limpios, nunca los borrados formar). Y desde hace bastante time, git merge ha respaldado el concepto de "renormalización": hacer un check-out virtual más check-in antes de diferenciar y combinar-diffs-de la confirmación básica y las dos confirmaciones-para-fusionar.