¿Es posible mover / renombrar files en git y mantener su historial?

Me gustaría cambiar el nombre / mover un subtree de proyecto en Git moviéndolo de

/project/xyz 

a

 /components/xyz 

Si utilizo un simple git mv project components entonces todo el historial de files para el xyz project se pierde.

¿Hay alguna manera de mover esto para que la historia se mantenga?

Git detecta el cambio de nombre en lugar de persistir en la operación con la confirmación, por lo que no importa si usas git mv o simplemente un mv .

El command de logging, sin embargo, toma un argumento --follow que continúa el historial antes de una operación de cambio de nombre (es decir, busca contenido similar usando la heurística):

http://git-scm.com/docs/git-log

Para search el historial completo, use el siguiente command:

 git log --follow ./path/to/file 

Es posible cambiar el nombre de un file y mantener el historial intacto, aunque hace que el file cambie de nombre a lo largo de todo el historial del repository. Esto es probablemente solo para los obsesivos amantes del logging de git, y tiene algunas implicaciones serias, que incluyen estas:

  • Podría estar reescribiendo un historial compartido, que es lo más importante NO durante el uso de Git. Si alguien más ha clonado el repository, lo romperás haciendo esto. Tendrán que volver a clonarse para evitar dolores de cabeza. Esto podría estar bien si el cambio de nombre es lo suficientemente importante, pero tendrá que considerarlo cuidadosamente: ¡podría terminar trastornando a toda una comunidad de código abierto!
  • Si ha hecho reference al file usando su nombre antiguo anteriormente en el historial del repository, está efectivamente rompiendo versiones anteriores. Para remediar esto, tendrás que saltar un poco más al aro. No es imposible, simplemente tedioso y posiblemente no valga la pena.

Ahora, como todavía estás conmigo, probablemente seas un desarrollador solist que cambia el nombre de un file completamente aislado. ¡Muevamos un file usando filter-tree !

Supongamos que va a mover un file old a un dir carpeta y darle el nombre new

Esto podría hacerse con git mv old dir/new && git add -u dir/new , pero eso rompe el historial.

En lugar:

 git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD 

rehará cada confirmación en la twig, ejecutando el command en los ticks para cada iteración. Muchas cosas pueden salir mal cuando haces esto. Normalmente pruebo para ver si el file está presente (de lo contrario, todavía no me he movido) y luego realizo los pasos necesarios para calzar el tree a mi gusto. Aquí puede search files para alterar las references al file, etc. Knock a ti mismo! 🙂

Cuando se completa, el file se mueve y el logging está intacto. Te sientes como un pirata ninja.

También; El directory mkdir solo es necesario si mueve el file a una nueva carpeta, por supuesto. El if evitará la creación de esta carpeta más temprano en la historia que su file.

No.

La respuesta corta es NO , no es posible renombrar un file en Git y recordar el historial. Y es un dolor.

Corre el rumor de que git log --follow --find-copies-harder funcionará, pero no funciona para mí, incluso si no hay cambios en el contenido del file, y los movimientos se han realizado con git mv .

(Inicialmente, utilicé Eclipse para cambiar el nombre y actualizar packages en una operación, lo que puede haber confundido a git. Pero eso es algo muy común de hacer. --follow parece funcionar si solo se realiza un mv y luego un commit y el mv no es demasiado lejos.)

Linus dice que se supone que debes comprender el contenido completo de un proyecto de software de manera integral, sin necesidad de rastrear files individuales. Bueno, lamentablemente, mi pequeño cerebro no puede hacer eso.

Es realmente molesto que tanta gente repita sin pensar la afirmación de que git sigue automáticamente los movimientos. Han perdido mi time. Git no hace tal cosa. Por layout (!) Git no rastrea movimientos en absoluto.

Mi solución es cambiar el nombre de los files a sus ubicaciones originales. Cambie el software para que se ajuste al control de origen. Con Git, parece que necesitas hacerlo bien la primera vez.

Desafortunadamente, eso rompe Eclipse, que parece usar --follow .
git log --follow A veces no se muestra el historial completo de files con historiales de cambio de nombre complicados aunque git log haga. (No se por que.)

(Hay algunos hacks demasiado inteligentes que se remontan y vuelven a comprometer el trabajo anterior, pero son bastante alarmantes. Ver GitHub-Gist: emiller / git-mv-with-history .)

 git log --follow [file] 

le mostrará la historia a través de cambiar el nombre.

Hago:

 git mv {old} {new} git add -u {new} 

Objetivo

  • Usa git am (inspirado en Smar , tomado de Exherbo )
  • Agregar historial de confirmaciones de files copydos / movidos
  • De un directory a otro
  • O de un repository a otro

Limitación

  • Etiquetas y twigs no se mantienen
  • El historial se corta en el cambio de nombre de ruta de acceso (cambio de nombre de directory)

Resumen

  1. Extrae el historial en formatting de correo electrónico usando
    git log --pretty=email -p --reverse --full-index --binary
  2. Reorganizar el tree de files y actualizar los nombres de files
  3. Agregar nuevo historial usando
    cat extracted-history | git am --committer-date-is-author-date

1. Extracto del historial en formatting de correo electrónico

Ejemplo: extraer el historial de file4 , file4 y file5

 my_repo ├── dirA │  ├── file1 │  └── file2 ├── dirB ^ │ ├── subdir | To be moved │ │ ├── file3 | with history │ │ └── file4 | │  └── file5 v └── dirC ├── file6 └── file7 

Establecer / limpiar el destino

 export historydir=/tmp/mail/dir # Absolute path rm -rf "$historydir" # Caution when cleaning the folder 

Extrae el historial de cada file en formatting de correo electrónico

 cd my_repo/dirB find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';' 

Lamentablemente, la opción --follow o --find-copies-harder no se puede combinar con --reverse . Esta es la razón por la que el historial se corta cuando se cambia el nombre del file (o cuando se cambia el nombre de un directory principal).

Historial temporal en formatting de correo electrónico:

 /tmp/mail/dir ├── subdir │ ├── file3 │ └── file4 └── file5 

Dan Bonachea sugiere invertir los loops del command de generación de loggings git en este primer paso: en lugar de ejecutar git log una vez por file, ejecútelo exactamente una vez con una list de files en la línea de command y genere un único logging unificado. De esta manera se compromete a que modificar files múltiples permanezca como una única confirmación en el resultado, y todas las nuevas confirmaciones mantengan su order relativa original. Tenga en count que esto también requiere cambios en el segundo paso a continuación al volver a escribir nombres de file en el logging (ahora unificado).


2. Reorganizar el tree de files y actualizar los nombres de files

Supongamos que desea mover estos tres files en este otro repository (puede ser el mismo repository).

 my_other_repo ├── dirF │ ├── file55 │ └── file56 ├── dirB # New tree │ ├── dirB1 # from subdir │ │ ├── file33 # from file3 │ │ └── file44 # from file4 │ └── dirB2 # new dir │ └── file5 # from file5 └── dirH └── file77 

Por lo tanto, reorganice sus files:

 cd /tmp/mail/dir mkdir -p dirB/dirB1 mv subdir/file3 dirB/dirB1/file33 mv subdir/file4 dirB/dirB1/file44 mkdir -p dirB/dirB2 mv file5 dirB/dirB2 

Tu historial temporal es ahora:

 /tmp/mail/dir └── dirB ├── dirB1 │ ├── file33 │ └── file44 └── dirB2 └── file5 

Cambiar también los nombres de file dentro de la historia:

 cd "$historydir" find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';' 

3. Aplicar nueva historia

Su otro repository es:

 my_other_repo ├── dirF │ ├── file55 │ └── file56 └── dirH └── file77 

Aplique confirmaciones de files de historial temporal:

 cd my_other_repo find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date 

--committer-date-is-author-date conserva las --committer-date-is-author-date time de confirmación originales (comentario de Dan Bonachea ).

Su otro repository es ahora:

 my_other_repo ├── dirF │ ├── file55 │ └── file56 ├── dirB │ ├── dirB1 │ │ ├── file33 │ │ └── file44 │ └── dirB2 │ └── file5 └── dirH └── file77 

Usa el git status para ver la cantidad de commits listos para ser empujados 🙂


Truco adicional: compruebe los files renombrados / movidos dentro de su repository

Para enumerar los files que han sido renombrados:

 find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>' 

Más personalizaciones: puede completar el command git log usando las opciones --find-copies-harder o --reverse . También puede eliminar las dos primeras columnas usando cut -f3- y grepping complete pattern '{. * =>. *}'.

 find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}' 

Mientras que el núcleo de git, la plomería git no hace un seguimiento de los cambios de nombre, la historia que muestra con el logging de git "porcelana" puede detectarlos si lo desea.

Para un git log dado use la opción -M:

git log -p -M

Con una versión actual de git.

Esto funciona para otros commands como git diff también.

Hay opciones para hacer las comparaciones más o less rigurosas. Si cambia el nombre de un file sin realizar cambios significativos en el file al mismo time, es más fácil para el logging de git y sus amigos detectar el cambio de nombre. Por esta razón, algunas personas renombran files en una confirmación y los cambian en otra.

Hay un costo en el uso de la CPU cada vez que le pides a git que encuentre dónde se han cambiado los nombres de los files, por lo tanto, si lo usas o no, y cuándo, depende de ti.

Si desea que siempre se informe sobre su historial con la detección de cambio de nombre en un repository en particular, puede usar:

git config diff.renames 1

Se detectan files que se mueven de un directory a otro. Aquí hay un ejemplo:

 commit c3ee8dfb01e357eba1ab18003be1490a46325992 Author: John S. Gruber <JohnSGruber@gmail.com> Date: Wed Feb 22 22:20:19 2017 -0500 test rename again diff --git a/yyy/power.py b/zzz/power.py similarity index 100% rename from yyy/power.py rename to zzz/power.py commit ae181377154eca800832087500c258a20c95d1c3 Author: John S. Gruber <JohnSGruber@gmail.com> Date: Wed Feb 22 22:19:17 2017 -0500 rename test diff --git a/power.py b/yyy/power.py similarity index 100% rename from power.py rename to yyy/power.py 

Tenga en count que esto funciona siempre que use diff, no solo con git log . Por ejemplo:

 $ git diff HEAD c3ee8df diff --git a/power.py b/zzz/power.py similarity index 100% rename from power.py rename to zzz/power.py 

Como testing hice un pequeño cambio en un file en una twig de características y lo comprometí y luego en la twig principal cambié el nombre del file, me comprometí, y luego hice un pequeño cambio en otra parte del file y me comprometí. Cuando fui a feature branch y fusioné desde master, la fusión cambió el nombre del file y fusionó los cambios. Aquí está el resultado de la fusión:

  $ git merge -v master Auto-merging single Merge made by the 'recursive' strategy. one => single | 4 ++++ 1 file changed, 4 insertions(+) rename one => single (67%) 

El resultado fue un directory de trabajo con el file renombrado y ambos cambios de text realizados. Entonces, es posible que git haga lo correcto a pesar de que no rastrea explícitamente los nombres.

Esta es una respuesta tardía a una pregunta anterior, por lo que las otras respuestas pueden haber sido correctas para la versión de git en ese momento.

Hago mover los files y luego hago

 git add -A 

que puso en el área de sataging todos los files eliminados / nuevos. Aquí git se da count de que el file se movió.

 git commit -m "my message" git push 

No sé por qué, pero esto funciona para mí.