É possível mover / mudar o nome dos ficheiros no Git e manter o seu histórico?

Eu gostaria de mudar o nome/mover uma sub-árvore de projecto no Git, mudando-a de

/project/xyz

a

/components/xyz

Se eu usar um simples {[[2]}, então todo o histórico de commit para o xyz project se perde. Existe uma maneira de mover isso de tal forma que a história seja mantida?

 555
Author: Micha Wiedenmann, 2010-02-23

9 answers

O Git detecta renomeações em vez de persistir a operação com o commit, por isso não importa se você usa git mv ou mv.

O comando log leva um argumento --follow que continua a história antes de uma operação de mudança de nome, isto é, procura por conteúdo semelhante usando as heurísticas:

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

Para procurar o histórico completo, use o seguinte comando:

git log --follow ./path/to/file
 566
Author: Troels Thomsen, 2018-01-19 11:40:27

É possível mudar o nome de um ficheiro e manter o histórico intacto, embora Faça com que o ficheiro seja renomeado ao longo de toda a história do repositório. Isto é provavelmente apenas para os obsessivos amantes de git-log, e tem algumas implicações sérias, incluindo estas:

  • Você pode estar reescrevendo uma história compartilhada, que é o mais importante Não enquanto usa Git. Se alguém clonou o repositório, você vai quebrá-lo fazendo isso. Terão de voltar a clonar evitar dores de cabeça. Isto pode ser OK se o nome for importante o suficiente, mas você vai precisar considerar isso cuidadosamente -- você pode acabar perturbando uma comunidade inteira de opensource!
  • se referenciou o ficheiro usando o seu nome antigo mais cedo no histórico do repositório, está efectivamente a quebrar versões anteriores. Para remediar isto, vais ter de fazer mais saltos de arco. Não é impossível, apenas tedioso e possivelmente não vale a pena.
Agora, já que ainda estás comigo, és um ... provavelmente um programador a solo a mudar o nome de um ficheiro completamente isolado. Vamos mover um ficheiro usando filter-tree!

Assume que vais mover um ficheiro old para uma pasta dir e dá-lhe o nome new

Isto pode ser feito com git mv old dir/new && git add -u dir/new, mas isso quebra a história.

Em vez disso:

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

Will refazer {[[9]} cada commit no ramo, executando o comando nos carrapatos para cada iteração. Muitas coisas podem correr mal quando fazes isto. Eu normalmente teste para ver se o arquivo está presente (caso contrário, ainda não está lá para se mover) e, em seguida, executar os passos necessários para Engraxar a árvore ao meu gosto. Aqui você pode procurar através de arquivos para alterar as referências ao arquivo e assim por diante. Diverte-te! :)

Quando concluído, o ficheiro é movido e o registo está intacto. Sentes-te como um pirata ninja.

Também; a pasta mkdir só é necessária se mover o ficheiro para uma nova pasta, claro. O SE irá evitar a criação desta pasta mais cedo no histórico do que o seu ficheiro existir.

 76
Author: Øystein Steimler, 2015-05-31 10:08:03
Não.

A resposta curta é Não , não é possível mudar o nome de um ficheiro no Git e recordar a história. E é uma dor.

Há rumores de que git log --follow--find-copies-harder vai funcionar, mas não funciona para mim, mesmo que haja zero alterações no conteúdo do arquivo, e os movimentos foram feitos com git mv.

(inicialmente usei o Eclipse para mudar o nome e actualizar os pacotes numa única operação, o que pode ter confundido o git. Mas isso é muito comum. uma coisa a fazer. --follow parece funcionar se apenas um mv for realizado e então um commit e o mv não for muito longe.)

O Linus diz que é suposto compreenderes todo o conteúdo de um projecto de software de forma holística, não precisando de seguir ficheiros individuais. Infelizmente, o meu pequeno cérebro não pode fazer isso.

É realmente irritanteque tantas pessoas tenham repetido sem pensar a afirmação de que o git segue automaticamente os movimentos. Eles fizeram-me perder tempo. O Git não coisa. por design (!) Git não segue movimentos de todo.

A minha solução é mudar o nome dos ficheiros para os locais originais. Mude o software para caber no controle de fonte. Com o git, parece que precisas de agir correctamente da primeira vez. Infelizmente, isso quebra o Eclipse, que parece usar --follow.
git log --follow Às vezes não mostra o histórico completo de arquivos com histórias de renomeação complicadas, mesmo que git log o. (Eu não sei porquê.) [13] (Há alguns hacks muito inteligentes que voltam e recomeçam o trabalho antigo, mas eles são bastante assustadores. Ver GitHub-Gist: emiller / git-mv-with-history.)
 62
Author: Tuntable, 2016-11-25 12:25:36
git log --follow [file]
Vou mostrar-te a história através de novos nomes.
 39
Author: Erik Hesselink, 2010-02-22 22:25:05

Eu faço:

git mv {old} {new}
git add -u {new}
 16
Author: James M. Greene, 2016-02-29 10:58:14

Objectivo

  • Usar git am (inspirado Smar, emprestado de Exherbo)
  • adicionar o histórico de commit dos ficheiros copiados/movidos
  • de um directório para outro
  • ou de um repositório para outro

Limitação

  • As Marcas e os ramos não são mantidos
  • o histórico é cortado na mudança de nome do ficheiro path (mudança de nome da pasta)

Resumo

    Extracto histórico no formato de E-mail a usar
    git log --pretty=email -p --reverse --full-index --binary
  1. reorganizar a árvore de ficheiros e actualizar os nomes dos ficheiros
  2. adicionar um novo histórico usando
    cat extracted-history | git am --committer-date-is-author-date

1. Extrair o histórico no formato de E-mail

Exemplo: extracto histórico de file3, file4 e file5
my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Definir / limpar o destino

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

Extrair o histórico de cada ficheiro em formato de E-mail

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"' {} ';'

Infelizmente a opção --follow ou --find-copies-harder Não pode ser combinada com --reverse. É por isso que a história é cortada quando o ficheiro é renomeado (ou quando uma pasta-mãe é renomeada).

Histórico temporário em formato de E-mail:

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

O Dan Bonachea sugere inverter os loops do comando git log generation neste primeiro passo: em vez de executar o git log uma vez por ficheiro, executá-lo exactamente uma vez com uma lista de ficheiros na linha de comandos e gerar um único registo unificado. Desta forma, commits que modificam vários arquivos permanecem um único commit no resultado, e todos os commits novos manter o seu ordem relativa original. Note que isso também requer mudanças no segundo passo abaixo quando reescrever nomes de arquivos no (agora unificado) log.


2. Reorganizar a árvore de ficheiros e actualizar os nomes dos ficheiros

Suponha que você quer mover estes três arquivos neste outro repo (pode ser o mesmo repo).

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 isso reorganize os seus ficheiros.
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
A sua história temporária é agora:
/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Muda também os nomes dos ficheiros dentro do histórico:

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

3. Aplicar nova história

O teu outro acordo é:

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

Aplicar autorizações a partir de ficheiros históricos temporários:

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

--committer-date-is-author-date preserva os selos de tempo originais do commit ([83]}O Comentário de Dan Bonachea ).

O teu outro acordo é agora:

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

Use git status para ver a quantidade de autorizações prontas a serem empurradas: -)


Truque Extra: verificar ficheiros renomeados / movidos dentro do seu repo

Para listar os ficheiros que foram renomeados:

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

Mais personalizações:poderá completar o comando git log usando as opções --find-copies-harder ou --reverse. Você também pode remover as duas primeiras colunas usando cut -f3- e limpar o padrão completo' {.* => .*}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
 12
Author: olibre, 2017-10-20 07:17:58
Enquanto o núcleo do git, a canalização do git não controla os nomes, a história que se mostra com o registo do git "porcelain" pode detectá-los se quiser.

Para um dado git log use a opção-M:

Git log-p-M

com uma versão atual do git.

Isto funciona para outros comandos como git diff também.

Existem opções para tornar as comparações mais ou menos rigorosas. Se mudar o nome de um ficheiro sem tornar significativo mudanças no arquivo ao mesmo tempo, torna mais fácil para o git log e amigos para detectar o novo nome. Por esta razão, algumas pessoas renomeiam arquivos em um commit e alterá-los em outro.

Há um custo no uso do cpu sempre que você pedir ao git para descobrir onde os arquivos foram renomeados, então se você usá-lo ou não, e quando, depende de você.

Se quiser ter sempre o seu histórico relatado com a detecção de novos nomes num repositório em particular, pode usar:

Git config comparacao.mudar o nome de 1

Os ficheiros que se deslocam de uma pasta para outra são detectados. Aqui está um exemplo:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <[email protected]>
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 <[email protected]>
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

Por favor, note que isto funciona sempre que estiver a usar diff, não apenas com git log. Por exemplo:

$ 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 um teste que eu fiz uma pequena alteração em um arquivo em um ramo de funcionalidade e compromete-lo e, em seguida, no branch master eu renomeei o arquivo, comprometida e, em seguida, fez uma pequena alteração em outra parte do arquivo e comprometida que. Quando fui para a secção de recursos e fundiu-se a partir do master o merge renomeou o arquivo e fundiu as alterações. Aqui está o resultado da junção:

 $ 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%)

O resultado foi um diretório de trabalho com o arquivo renomeado e ambas as alterações de texto feitas. Então é possível para o git fazer a coisa certa apesar do fato de que ele não rastreia explicitamente renomeações.

Esta é uma resposta tardia a uma pergunta antiga para que as outras respostas possam ter sido corretas para a versão git na época.

 2
Author: John S Gruber, 2017-02-23 04:54:04

Eu gostaria de mudar o nome/mover uma sub-árvore de projecto no Git, mudando-a de

/project/xyz

A

/ componentes / xyz

Se eu usar um plain git mv project components, Então todo o histórico de commit para o projeto xyz fica perdido.

Não (8 anos depois, Git 2.19, Q3 2018), porque o Git irá detectar a mudança de nome do directório, e isto está agora melhor documentado.

Ver comitologia b00bf1c, Comissão 1634688, Comissão 0661e49, cometer 4d34dff, cometer 983f464, cometer c840e1a, cometer 9929430 (27 de Junho de 2018), e cometer d4e8062, cometer 5dacd4a (25 de Junho de 2018) por Elias Newren (newren).
(Intercalado por Junio C Hamano -- gitster -- em cometer 0ce5a69, 24 Jul 2018)

Isso é agora explicado em Documentation/technical/directory-rename-detection.txt:

Exemplo:

Quando todos x/a, x/b e x/c mudaram-se para z/a, z/b e z/c, é provável que x/d adicionado entretanto também queira mudar para z/d por tendo em conta a sugestão de que o directório completo "x "foi transferido para" z".

Mas são muitos outros casos, como:

Um lado da história muda o nome x -> z, e o outro muda o nome de algum ficheiro para x/e, causando a necessidade da junção fazer uma mudança de nome transitiva.

Para simplificar a detecção da mudança de nome das pastas, essas regras são aplicadas por Git:

Algumas regras básicas limitam quando aplica-se a mudança de nome da pasta:

  1. se um determinado directório ainda existir em ambos os lados de uma junção, não consideramos que tenha sido renomeado.
  2. se um subconjunto de ficheiros a mudar de nome tem um ficheiro ou directório no caminho (ou estaria no caminho um do outro), "desligue" a mudança de nome do directório para esses sub-caminhos específicos e comunique o conflito ao utilizador.
  3. se o outro lado da história fez um a pasta muda o nome para uma localização que o seu lado do histórico mudou de nome, ignorando depois essa mudança de nome do outro lado do histórico para qualquer mudança de nome implícito da pasta (mas avisando o utilizador).

Você pode ver muito de testes em t/t6043-merge-rename-directories.sh, o que também indica que:

  • a) se mudar o nome de dividir uma pasta em duas ou mais outras, a pasta com o maior número de nomes, "wins".
  • b) evitar o directório-mudar o nome-detecção para um path, se esse caminho for a fonte de um novo nome em ambos os lados de uma junção.
  • c) aplicar apenas as renomeações implícitas das pastas se o outro lado da história é aquele que faz a mudança de nome.
 1
Author: VonC, 2018-07-30 19:37:40

Eu faço mover os arquivos e depois fazer

git add -A

Que colocam na área de satélite Todos os ficheiros apagados / novos. Aqui git percebe que o arquivo é movido.

git commit -m "my message"
git push
Não sei porquê, mas isto funciona comigo.
 -1
Author: Xelian, 2016-12-18 14:34:03