Git Diff de los mismos files en dos directorys siempre da como resultado "renombrado"

git diff –no-index –no-prefix –summary -U4000 directory1 directory2

Esto funciona como se espera, ya que devuelve una diferencia de todos los files entre los dos directorys. Los files que se agregan salen como se espera, los files que se eliminan también dan como resultado la salida diff esperada.

Sin embargo, debido a que el diff toma en count la ruta del file como parte del nombre del file, los files con el mismo nombre, en los dos directorys diferentes, dan como resultado una salida diff con el indicador renombrado en lugar de cambiado.

  1. ¿Hay alguna manera de decirle a git que no tenga en count la ruta completa del file en el file diff y que solo observe el nombre del file, como si los files provinieran del mismo directory?

  2. ¿Hay alguna manera de que git sepa realmente si se cambió el nombre de una copy del mismo file en un directory diferente? No veo cómo, a less que tenga una forma de comparar los files md5s de alguna manera o algo así (probablemente sea una mala suposition jajaja).

  3. El uso de twigs en lugar de directorys resolvería este problema fácilmente y, en caso afirmativo, ¿cuál es la versión de twig del command enumerado anteriormente?

Solutions Collecting From Web of "Git Diff de los mismos files en dos directorys siempre da como resultado "renombrado""

Hay muchas preguntas aquí, cuyas respuestas se entrelazan. Comencemos con el cambio de nombre y la detección de copys, luego pasamos a las twigs.

Renombrar detección

Sin embargo, debido a que el diff toma en count la ruta del file como parte del nombre del file, los files con el mismo nombre, en los dos directorys diferentes, dan como resultado una salida diff con el indicador renombrado en lugar de cambiado.

Esto no es del todo correcto. (El text a continuación está destinado a abordar sus artículos 1 y 2.)

Aunque está utilizando --no-index (presumiblemente, para hacer que Git trabaje en directorys fuera del repository), el código diff de Git se comporta de la misma manera en todos los casos. Para diferenciar (comparar) dos files en dos treees, Git primero debe determinar la identidad del file . Es decir, hay dos sets de files: los que están en el "lado izquierdo" o el tree de origen (el primer nombre del directory), y los que están en el "lado derecho" o el tree de destino (el segundo nombre del directory). Algunos files a la izquierda son el mismo file que algunos files a la derecha. Algunos files a la izquierda son files diferentes que no tienen un file correspondiente en el lado derecho, es decir, han sido eliminados . Finalmente, algunos files en el lado derecho son nuevos, es decir, han sido creados .

Los files que son "el mismo file" no necesitan tener el mismo nombre de ruta . En este caso, esos files han sido renombrados .

Así es como funciona en detalle. Tenga en count que el "nombre completo de la ruta" se modifica algo cuando se usa git diff --no-index dir1 dir2 : el "nombre completo de ruta" es lo que queda después de eliminar los prefijos dir1 y dir2 .

Al comparar los treees del lado izquierdo y derecho, los files que tienen los mismos nombres de ruta completos normalmente se consideran automáticamente como "el mismo file". Colocamos todos estos files en una queue de "files para diferir", y ninguno aparecerá como renombrado. Tenga en count la palabra "normalmente" aquí; volveremos sobre esto en un momento.

Esto nos deja con dos lists de files restantes:

  • paths que existen a la izquierda, pero no a la derecha: fuente sin destino
  • paths que existen a la derecha, pero no a la izquierda: destino sin fuente

Ingenuamente, podemos simplemente declarar que todos estos files del lado de la fuente han sido eliminados, y todos estos files de destino han sido creados. Puede indicar a git diff que se comporte de esta manera: configure el indicador --no-renames para desactivar la detección de cambio de nombre.

O bien, Git puede utilizar un algorithm más inteligente: configure el --find-renames y / o -M <threshold> para hacer esto. En las versiones 2.9 y posteriores de Git, la detección de cambio de nombre está activada por defecto.

Ahora, ¿cómo decidirá Git que un file fuente tiene la misma identidad que un file de destino? Ellos tienen diferentes paths; ¿A qué file de la derecha corresponde a a/b/c.txt a la izquierda? Puede ser d/e/f.bin , o d/e/f.txt , o a/b/renamed.txt , y así sucesivamente. El algorithm real es relativamente simple, y en el pasado no tomó el componente de nombre final en vigor (no estoy seguro si lo hace ahora, Git está en constante evolución):

  • Si hay files de origen y destino cuyos contenidos coinciden exactamente , vincúlelos. Debido a que Git contiene contenido, esta comparación es muy rápida. Podemos comparar el lado izquierdo a/b/c.txt por su identificación hash con cada file de la derecha, simplemente mirando todas sus identificaciones hash. Por lo tanto, primero revisamos todos los files fuente, buscamos los files de destino que coinciden, colocamos los nuevos pares en la queue de diferencias y los sacamos de las dos lists.

  • Para todos los files de origen y destino restantes , ejecute un algorithm eficiente, pero no apto para la salida de git diff , para calcular la "similitud de files". Un file de origen que es al less <threshold> similar a un file de destino provoca un emparejamiento, y ese par de files se elimina. El umbral pnetworkingeterminado es 50%: si ha habilitado la detección de cambio de nombre sin elegir un umbral en particular, dos files que todavía están en las lists en este punto, y son un 50% similares, se emparejan.

  • Cualquier file restante es eliminado o creado.

Ahora que hemos encontrado todos los emparejamientos, git diff procede a diferenciar los files emparejados con la misma identidad, y nos dice que los files eliminados se eliminan y se crean los files recién creados. Si los dos nombres de ruta para los mismos files de identidad son diferentes, git diff dice que el file se renombra.

El código de emparejamiento de file arbitrario es costoso (aunque el código del mismo nombre da un par es muy barato), por lo que Git tiene un límite sobre cuántos nombres se incluyen en estas lists de origen y destino de emparejamiento. Ese límite se configura a través de git config diff.renameLimit . El valor pnetworkingeterminado ha aumentado en los últimos años y ahora es varios miles de files. Puede establecerlo en 0 (cero) para hacer que Git use su propio máximo interno en todo momento.

Rompiendo pares

Arriba, dije que normalmente , los files con el mismo nombre se emparejan automáticamente. Esto suele ser lo correcto, por lo que es el valor pnetworkingeterminado de Git. En algunos casos, sin embargo, el file del lado izquierdo que se llama a/b/c.txt realidad no está relacionado con el file del lado derecho llamado a/b/c.txt , está realmente relacionado con el lado derecho a/doc/c.txt por ejemplo. Podemos decirle a Git que rompa el emparejamiento de files que son "demasiado diferentes".

Vimos el "índice de similitud" utilizado anteriormente para formar emparejamientos de files. Este mismo índice de similitud se puede usar para dividir files: -B20%/60% , por ejemplo. Los dos numbers no necesitan sumr 100% y puedes omitir uno o ambos: hay un valor pnetworkingeterminado para cada uno si configuras el modo -B .

El primer número es el punto en el cual un file por defecto ya emparejado se puede poner en las lists de detección de cambio de nombre. Con -B20% , si los files son 20% dis-similares (es decir, solo 80% similar), el file entra en la list "fuente para cambiar el nombre". Si nunca se toma como un cambio de nombre, puede volver a emparejarse con su destino automático, pero en este punto, el segundo número, el que está después de la barra, entra en vigencia.

El segundo número establece el punto en el que un emparejamiento definitivamente se rompe. Con -B/70% , por ejemplo, si los files son 70% dis-similares (es decir, solo 30% similar), el emparejamiento está roto. (Por supuesto, si el file se eliminó como fuente de cambio de nombre, el emparejamiento ya está roto).

Copiar detección

Además del emparejamiento habitual y la detección de cambio de nombre, puede pedirle a Git que encuentre copys de los files fuente. Después de ejecutar todo el código de vinculación habitual, que incluye search nombres y romper pares, si ha especificado -C , Git searchá files de destino "nuevos" (es decir, no emparejados) que en realidad se copyn de fonts existentes. Hay dos modos para esto, dependiendo de si especifica -C dos veces o agrega --find-copies-harder : uno considera solo los files fuente que se modifican (ese es el caso individual -C ), y uno que considera cada file fuente ( ese es el caso de dos -C o --find-copies-harder ). Tenga en count que este "era un file fuente modificado" significa, en este caso, que el file fuente ya está en la queue emparejada; si no, no está "modificado" por definición, y su file de destino correspondiente tiene una identificación hash diferente (nuevamente , esta es una testing de muy bajo costo, lo que ayuda a mantener una sola opción -C barata).

Las twigs no importan

El uso de twigs en lugar de directorys resolvería este problema fácilmente y, en caso afirmativo, ¿cuál es la versión de twig del command enumerado anteriormente?

Las twigs no hacen diferencia aquí.

En Git, el término twig es ambiguo. Ver ¿Qué queremos decir con "twig"? Para git diff , sin embargo, un nombre de twig simplemente se resuelve en una única confirmación, a saber, la confirmación de sugerencia de esa twig.

Me gusta dibujar las twigs de Git así:

 ...--o--o--o <-- branch1 \ o--o--o <-- branch2 

Cada ronda pequeña representa un compromiso. Los dos nombres de las twigs son simplemente pointers , en Git: apuntan a un compromiso específico . El nombre branch1 apunta a la confirmación más a la derecha en la línea superior, y el nombre branch2 apunta a la confirmación más a la derecha en la línea inferior.

Cada confirmación, en Git, señala a su padre o padres (la mayoría de los commits tienen solo un padre, mientras que un commit de fusión es simplemente un commit con dos o más padres). Esto es lo que forma la cadena de compromisos que también llamamos "una twig". El nombre de la twig apunta directamente a la punta de una cadena. 1

Cuando corres:

 $ git diff branch1 branch2 

todo lo que hace Git es resolver cada nombre a su compromiso correspondiente. Por ejemplo, si los nombres de branch1 commit 1234567... y los nombres de 89abcde... commit 89abcde... , esto hace lo mismo que:

 $ git diff 1234567 89abcde 

La diferencia de Git toma dos treees

A Git ni siquiera le importa que estos sean compromisos, realmente. Git solo necesita un tree de lado izquierdo o fuente, y un tree de lado derecho o de destino. Estos dos treees pueden provenir de una confirmación, porque una confirmación le asigna un nombre a un tree: el tree de cualquier confirmación es la instantánea de origen tomada cuando se realizó dicha confirmación. Pueden provenir de una twig, porque un nombre de twig nombra una confirmación, que nombra un tree. Uno de los treees puede provenir del "índice" de Git (también conocido como "área de preparación" o "caching"), ya que el índice es básicamente un tree aplanado. 2 Uno de los treees puede ser tu tree de trabajo. Uno o ambos treees pueden estar fuera del control de Git (de ahí el indicador --no-index ).

Por supuesto, Git puede simplemente diferir dos files

Si ejecuta git diff --no-index /path/to/file1 /path/to/file2 , Git simplemente diferirá los dos files, es decir, los tratará como un par. Esto omite por completo todo el código de emparejamiento y detección de cambio de nombre. Si no hay una gran cantidad de toquecitos con --no-renames --find-renames , --find-renames , --rename-threshold , etc., las opciones hacen el truco, puedes diferenciar explícitamente las routes de files, en lugar de las routes de directory (tree). Para un gran set de files, esto, por supuesto, será doloroso.


1 Puede haber más compromisos más allá de ese punto, pero sigue siendo la punta de su cadena . Además, varios nombres pueden apuntar a una única confirmación. Dibujo esta situación como:

 ...--o--o <-- tip1 \ o--o <-- tip2, tip3 

Tenga en count que los commits que están "detrás" de más de un nombre de twig son, de hecho, en todas esas twigs. Por lo tanto, ambas confirmaciones de la fila inferior están en las twigs tip2 y tip3 , mientras que las confirmaciones de la fila superior están en las tres twigs. No obstante, cada nombre de sucursal se resuelve en uno y solo uno.

2 De hecho, para hacer un nuevo commit, Git simplemente convierte el índice, tal como está ahora, en un tree usando git write-tree , y luego hace un commit que nombra ese tree (y que usa el commit actual como su padre, y tiene un autor y confirmador, y un post de confirmación). El hecho de que Git use el índice existente es la razón por la cual debe git add los files actualizados del tree de trabajo al índice antes de cometerlos.

Hay algunos accesos directos que le permiten indicar a git commit que agregue files al índice, por ejemplo, git commit -a o git commit <path> . Estos pueden ser un poco complicados ya que no siempre producen el índice que podría esperar. Ver las opciones --include vs --only para git commit <path> , por ejemplo. También funcionan al copyr el índice principal a un nuevo índice temporal; y esto puede tener resultados sorprendentes, porque si la confirmación tiene éxito, el índice temporal se copy nuevamente sobre el índice regular.