Algunas veces se necesitan algunos trucos cuando se trabaja con git

Quiero compartir recetas para resolver un par de tareas que a veces surgen cuando se trabaja con git y que no son "directamente obvias".


Al principio pensé en acumular más recetas, pero todo tiene su tiempo. Creo que si hay algún beneficio, entonces es posible y poco a poco ...


Entonces ...


Combinar ramas viejas con un dolor mínimo


Preámbulo Hay una rama principal ( master ), que compromete activamente nuevas características y correcciones; hay una rama de feature paralelas, en la que los desarrolladores navegaron durante un tiempo hasta su propio nirvana, y de repente descubrieron que no habían caído durante un mes con el maestro, y la fusión "en la frente" (cabeza a cabeza) ya no era trivial.


(Sí, no se trata de un mundo ideal donde todo es correcto, el crimen está ausente, los niños siempre son obedientes e incluso cruzan el camino estrictamente de la mano con su madre, mirando cuidadosamente a su alrededor).


Propósito: frenar. Al mismo tiempo, por lo que fue una fusión "pura", sin características. Es decir de modo que en el repositorio público en el gráfico de rama, dos hilos están conectados en un solo punto con el mensaje "rama fusionada 'maestro' en función". Y todo el dolor de cabeza "este" sobre cuánto tiempo y esfuerzo tomó, cuántos conflictos se resolvieron y cuánto cabello quedó innecesariamente manchado.


La trama El hecho de que en el git puede editar la última confirmación con la clave, --amend saben. El truco es que este "último compromiso" puede ubicarse en cualquier lugar y contener cualquier cosa. Por ejemplo, puede ser no solo el "último compromiso con la rama lineal", donde se olvidaron de corregir el error tipográfico, sino también el compromiso de fusión de la fusión habitual o de "pulpo". --amend simplemente --amend sobre los cambios propuestos y "incrustará" la confirmación modificada en el árbol, como si realmente apareciera como resultado de una fusión honesta y la resolución de conflictos. En esencia, git merge y git commit --amend permite separar completamente el "lugar de replanteo" ("este commit en el árbol estará AQUÍ") y el contenido del commit en sí.


La idea básica de un compromiso de fusión complejo con un historial limpio es simple: primero "hacemos un lugar" creando un compromiso de fusión limpio (independientemente del contenido), luego lo reescribimos con --amend , haciendo que el contenido sea "correcto".


  1. "Píldorame un lugar". Esto es fácil de hacer estableciendo una estrategia de fusión que no haga preguntas innecesarias sobre la resolución de conflictos.


     git checkout feature git merge master -s ours 

  2. Oh si Antes de la fusión, era necesario crear una rama de "copia de seguridad" desde el encabezado de la característica. Después de todo, nada se fusionó realmente ... Pero que sea el segundo párrafo, y no el 0. En general, cambiamos a la función no fusionada y ahora nos fusionamos honestamente. De cualquier manera posible, a pesar de cualquier "truco sucio". Mi manera personal es mirar la rama maestra desde el momento de la última fusión y evaluar posibles confirmaciones de problemas (por ejemplo: corregir un error tipográfico en un lugar, no uno problemático. Masivamente (muchos archivos) cambiaron el nombre de una entidad, una problemática, etc.). A partir de confirmaciones de problemas, creamos nuevas ramas (lo hago de manera ingeniosa: master1, master2, master3, etc.). Y luego fusionamos rama por rama, pasando de viejos a nuevos y corrigiendo conflictos (que generalmente son evidentes con este enfoque). Sugiera otros métodos (no soy un mago; solo estoy aprendiendo; ¡me complacerá hacer comentarios constructivos!). En última instancia, al pasar (tal vez) unas horas en operaciones puramente rutinarias (que se pueden confiar al junior, porque simplemente no hay conflictos complicados con este enfoque), obtenemos el estado final del código: todas las innovaciones / correcciones del asistente se transfirieron con éxito a la rama de características, todas las pruebas relevantes pisó este código, etc. Se debe confirmar un código exitoso.


  3. Reescribiendo la "historia de éxito". Estando en el commit, donde "todo está hecho", ejecuta lo siguiente:



 git tag mp git checkout mp git reset feature git checkout feature git tag -d mp 

(Descifro: usando la etiqueta (mp - merge point), cambiamos al estado HEAD separado, y desde allí lo reiniciamos a la cabecera de nuestra rama, donde al principio se hizo un "replanteo" por un compromiso de fusión fraudulento. La etiqueta ya no es necesaria, así que la eliminamos). Ahora estamos en el commit de fusión "puro" original; Al mismo tiempo, en la copia de trabajo tenemos los archivos "correctos", donde se encuentra todo lo que necesita. Ahora debe agregar todos los archivos modificados al índice y, especialmente, mirar cuidadosamente los archivos no montados (habrá todos los archivos nuevos que aparecieron en la rama principal). Agregamos todo lo que necesitamos desde allí, también.


Finalmente, cuando todo esté listo, ingresamos nuestra confirmación correcta en el lugar reservado:


 git commit --amend 

¡Hurra! ¡Todo salió bien! Puede insertar una rama de forma casual en un repositorio público, y nadie sabrá que realmente pasó medio día de trabajo en esta fusión.


Upd: forma más concisa


Tres meses después de esta publicación, el artículo " Cómo y por qué robar árboles en git " por capslocky


En función de sus motivos, es posible lograr exactamente el mismo objetivo de una manera más corta y sin mecanismos auxiliares: no es necesario "publicar espacio", considerar los archivos sin clasificar después de restablecer y realizar modificaciones; Puede crear una confirmación de fusión directa con el contenido deseado en un solo paso.


Comenzamos de inmediato con la fusión utilizando cualquier método disponible (como en el párrafo 2 anterior). La historia intermedia y los hacks aún son irrelevantes. Y luego, en lugar de la reivindicación 3, con la sustitución del compromiso de fusión, hacemos una fusión artificial , como en el artículo:


 git tag mp git checkout feature git merge --ff $(git commit-tree mp^{tree} -m "merged branch 'master' into 'feature'" -p feature -p master) git tag -d mp 

Toda la magia aquí se realiza en un solo paso mediante el tercer comando (git commit-tree).


Seleccione parte del archivo, manteniendo el historial


Preámbulo: el archivo fue codificado-codificado, y finalmente codificado, de modo que incluso el estudio visual comenzó a disminuir la velocidad, digiriéndolo (sin mencionar JetBrains). (Sí, estamos de vuelta en el mundo "imperfecto". Como siempre).


Los cerebros inteligentes pensaron, pensaron y seleccionaron varias entidades que se pueden representar en un archivo separado. Pero! Si solo lo toma, copie y pegue una parte del archivo y péguelo en otro; este será un archivo completamente nuevo desde el punto de vista de git. En caso de algún problema, una búsqueda en el historial indicará inequívocamente solo “¿dónde está esta persona discapacitada?” Quién compartió el archivo. Y puede ser necesario encontrar la fuente original para nada "para represiones", sino puramente constructiva, para descubrir por qué se cambió esta línea; qué error solucionó (o no solucionó). ¡Quiero que el archivo sea nuevo, pero al mismo tiempo toda la historia de cambios aún permanece!


La trama Con algunos efectos de borde ligeramente molestos, esto se puede hacer. Para mayor claridad, hay un archivo file.txt , del que quiero resaltar una parte en file2.txt . (y aún mantengo la historia, sí). Ejecute este fragmento:


 f=file.txt; f1=file1.txt; f2=file2.txt cp $f $f2 git add $f2 git mv $f $f1 git commit -m"split $f step 1, converted to $f1 and $f2" 

Como resultado, obtenemos los archivos file1.txt y file2.txt . Ambos tienen exactamente la misma historia (real; como el archivo original). Sí, el file.txt original.txt tuvo que ser renombrado; Este es el efecto de borde "ligeramente molesto". Desafortunadamente, no pude encontrar una manera de guardar la historia, pero para NO renombrar el archivo fuente (si alguien puede, ¡dímelo!). Sin embargo, el git lo soportará todo; ahora nadie se molesta en cambiar el nombre del archivo en una confirmación por separado:


 git mv $f1 $f git commit -m"split finish, rename $f1 to $f" 

Ahora file2.txt dorado mostrará el mismo historial de línea que el archivo original. Lo principal es no fusionar estos dos commits juntos (de lo contrario, toda la magia desaparecerá; ¡lo intenté!). Pero al mismo tiempo, nadie se molesta en editar archivos directamente en el proceso de separación; no es necesario hacer esto más tarde con confirmaciones separadas. Y sí, ¡puedes seleccionar muchos archivos a la vez!


El punto clave de la receta: cambiar el nombre del archivo fuente a otro en el mismo commit, donde se hace (y posiblemente edita) una copia (copias). Y deje que esta confirmación viva en el futuro (nunca se equivocará con el cambio de nombre inverso).


Upd: un par de recetas de Lissov


Separe la parte del repositorio de historia


Estás en la última versión del repositorio inicial. La tarea es separar una carpeta. (Vi opciones para varias carpetas, pero es más fácil y más comprensible ya sea doblar todo primero en uno o repetir lo siguiente varias veces).


Importante! Todos los movimientos deben hacerse con el git mv , de lo contrario, el git puede perder el historial.


Realizamos:


 git filter-branch --prune-empty --subdirectory-filter "{directory}" [branch] 

{directorio} es la carpeta que se separará. Como resultado, obtenemos la carpeta junto con el historial completo de confirmaciones solo, es decir, en cada confirmación, solo se muestran los archivos de esta carpeta. Naturalmente, parte de los commits estarán vacíos, --prune-empty los elimina.
Ahora cambia el origen:


 git remote set-url origin {another_repository_url}` git checkout move_from_Repo_1 

Si el segundo repositorio está limpio, puede ir directamente al maestro. Bueno, empuja:


 git push -u move_from_Repo_1 

Fragmento completo (para copiar y pegar fácilmente):


 directory="directory_to_extract"; newurl="another_repository_url" git filter-branch --prune-empty --subdirectory-filter "$directory" git remote set-url origin "$newurl" git checkout move_from_Repo_1 git push -u move_from_Repo_1 

Fusiona dos repositorios juntos


Suponga que hizo algo 2 veces más alto y obtuvo brunches move_from_Repo_1 y move_from_Repo_2 , y en cada uno transfirió archivos usando git mv a donde deberían estar después de la fusión. Ahora queda por controlar:


 br1="move_from_Repo_1"; br2="move_from_Repo_2" git checkout master git merge origin/$br1 --allow-unrelated-histories git merge origin/$br2 --allow-unrelated-histories git push 

Todo el truco está en --permitir historias no relacionadas. Como resultado, obtenemos un repositorio con un historial completo de todos los cambios.

Source: https://habr.com/ru/post/es424045/


All Articles