Git: fusiona manualmente los cambios en un cambio de nombre no detectado

Esta pregunta usa Git 2.7.0.windows.1 , por lo que algunos de los commands git pueden estar desactualizados.

Si mi command git merge no detecta un file renombrado, ¿cómo puedo decirle a git que fusione manualmente los cambios en dos files que se supone que son un solo file, sin iniciar la fusión y usar un umbral de cambio de nombre más bajo?

Pasos de reproducción:

 git init echo "//Hello world!" > hw.h git add . && git commit -m "Initial commit" git checkout -b someBranch mv hw.h hw.hpp echo "//Foobar" > hw.hpp git add . && git commit -m "Change hw to HPP & change content" git checkout master echo "//Boofar" > hw.h git add . && git commit -m "Change content of hw" git merge -X rename-threshold=100% someBranch 

Obtendrá conflictos de fusión, pero no fragments en conflicto. Es decir, el único conflicto / error que debe get es:

 CONFLICT (modify/delete): hw.h deleted in branchB and modified in HEAD. Version HEAD of hw.h left in tree. Automatic merge failed; fix conflicts and then commit the result. 

Y el git status --porcelain mostrará:

 UD hw.h A hw.hpp 

Normalmente, cuando se fusiona, es ideal establecer el umbral para detectar los cambios de nombre lo suficientemente bajos como para que se puedan cambiar los nombres. Algunas personas recomiendan un 5% , por ejemplo. En mi caso, estoy haciendo una fusión masiva (> 1000 files y aproximadamente 9m LOC), y tuve que elevar el umbral de cambio de nombre lo suficientemente alto como para evitar cualquier "falso positivo"; literalmente un uno por ciento más bajo y obtuve una gran cantidad de renombrados nombres falsamente detectados (lo sé, el código duplicado apesta). En el valor que terminé usando, recibo solo un pequeño puñado de renombrados perdidos, lo cual parece ser una mejor opción.

TL; DR bajando el umbral de cambio de nombre no es una opción para mí; ¿Cómo puedo, sin comenzar la fusión, decirle a git que considere que hw.h hw.hpp son un solo file (con conflictos), en lugar de dos files como se muestra arriba?

Las herramientas para esto son un poco torpes, pero están ahí.

Debe asegurarse de que la fusión se detiene antes de comprometerse. En tu caso, esto sucede automáticamente. Para fusiones más complicadas, donde Git piensa que lo está haciendo correctamente pero no lo está, agregaría --no-commit , pero esto afecta los próximos pasos. Vamos a ignorar ese problema por ahora.

Luego, necesita get las tres versiones del file en cuestión. Desde que Git se detuvo con un conflicto, estamos en buena forma: las tres versiones son accesibles a través del índice. Recuerde que las tres versiones que nos interesan son fusionar base , --ours , y --theirs .

Si Git había detectado el cambio de nombre correctamente, las tres versiones estarían en el índice con un solo nombre. Como no fue así, no lo son: necesitamos dos nombres. (Con el caso "Git cree que funcionó correctamente", la versión base de combinación del file no está en absoluto en el índice, y tenemos que recuperarlo de otra manera.) Los dos nombres en su caso aquí son hw.h y hw.hpp , entonces ahora hacemos esto:

 $ git show :1:hw.h > hw.h.base # extract base version $ git show :2:hw.h > hw.h # extract ours $ mv hw.hpp hw.h.theirs # move theirs into place 

(El cambio de nombre no es estrictamente necesario, es solo para ayudar a mantener todo recto y bien ilustrado).

Ahora queremos fusionar el file con git merge-file :

 $ git merge-file hw.h hw.h.base hw.h.theirs 

Esto usa su merge.conflictStyle configurado para que lo que está en el file fusionado se vea como se espera, excepto que las tags en las líneas en conflicto son un poco diferentes. Tengo diff3 set de diff3 , entonces obtengo:

 $ cat hw.h <<<<<<< hw.h //Boofar 

| hw.h.base //Hello world! ======= //Foobar >>>>>>> hw.h.theirs

Ahora puede resolver esto como siempre, rm la .base adicional y .theirs files, git add el resultado final, git rm --cached hw.hpp , y git commit . ( git rm --cached hw.hpp usted el momento de git rm --cached hw.hpp : es seguro hacerlo en cualquier momento antes de la confirmación, pero una vez hecho esto ya no puede get "el suyo" del índice; ver más abajo).

Tenga en count que las versiones " git show MERGE_HEAD:path " y "theirs" también están disponibles a través de git show HEAD:path y git show MERGE_HEAD:path . Para llegar a la versión base sin el índice, tendríamos que ejecutar git merge-base HEAD MERGE_HEAD para encontrar su ID hash (y luego asumir que hay una única base de combinación también 1 ), y git show <hash>:path . Esto es lo que debemos hacer si Git cree que ha realizado la fusión correctamente.

Tenga en count también que si realmente lo desea, imagino que esto solo sería cierto si quisiera usar alguna otra herramienta que tenga, que lo requiera, puede usar git update-index para mezclar las inputs en el índice, moviendo hw.hpp en la ranura 3 de hw.h para que aparezca como "suyo", y se muestre de esa manera en git status . Para este ejemplo en particular:

  $ printf '100644 bbda177a6ecfe285153467ff8fd332de5ecfb2f8 3\thw.h' | git update-index --index-info 

El hash aquí viene de git ls-files --stage y es el hash para hw.hpp . (Necesita un segundo paso para eliminar la input del índice hw.hpp ).


1 Usa git merge-base --all para encontrar todas las bases de fusión. Si hay más de uno, puede elegir uno arbitrariamente (esto es lo que hace la -s resolve ), o tratar de fusionar todas las bases de fusión en una base de combinación virtual . Para unir dos bases de combinación, puedes encontrar su propia base de combinación y fusionar dos bases como si fueran sugerencias de twigs, usando esa base de fusión. Recurse e itere según sea necesario; esto es lo que hace Git con la estrategia -s recursive pnetworkingeterminada de los -s recursive , hasta que tenga una sola versión base de combinación del file.

    Intereting Posts