Esta resposta fornece comandos interessantes baseados git am
e apresentados usando exemplos, passo a passo.
Objetivo
- Você deseja mover alguns ou todos os arquivos de um repositório para outro.
- Você quer manter a história deles.
- Mas você não se preocupa em manter tags e ramificações.
- Você aceita histórico limitado para arquivos renomeados (e arquivos em diretórios renomeados).
Procedimento
- Extrair histórico no formato de email usando
git log --pretty=email -p --reverse --full-index --binary
- Reorganize a árvore de arquivos e atualize a alteração do nome do arquivo no histórico [opcional]
- Aplique novo histórico usando
git am
1. Extrair histórico no formato de email
Exemplo: história Extrato de file3
, file4
efile5
my_repo
├── dirA
│ ├── file1
│ └── file2
├── dirB ^
│ ├── subdir | To be moved
│ │ ├── file3 | with history
│ │ └── file4 |
│ └── file5 v
└── dirC
├── file6
└── file7
Limpe o destino do diretório temporário
export historydir=/tmp/mail/dir # Absolute path
rm -rf "$historydir" # Caution when cleaning
Limpe sua fonte de repo
git commit ... # Commit your working files
rm .gitignore # Disable gitignore
git clean -n # Simulate removal
git clean -f # Remove untracked file
git checkout .gitignore # Restore gitignore
Extrair histórico de cada arquivo no formato de email
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 opção --follow
ou --find-copies-harder
não pode ser combinada com --reverse
. É por isso que o histórico é cortado quando o arquivo é renomeado (ou quando um diretório pai é renomeado).
Depois: histórico temporário no formato de email
/tmp/mail/dir
├── subdir
│ ├── file3
│ └── file4
└── file5
2. Reorganize a árvore de arquivos e atualize a alteração do nome do arquivo no histórico [opcional]
Suponha que você queira mover esses três arquivos neste outro repositório (pode ser o mesmo repositório).
my_other_repo
├── dirF
│ ├── file55
│ └── file56
├── dirB # New tree
│ ├── dirB1 # was subdir
│ │ ├── file33 # was file3
│ │ └── file44 # was file4
│ └── dirB2 # new dir
│ └── file5 # = file5
└── dirH
└── file77
Portanto, reorganize seus arquivos:
cd /tmp/mail/dir
mkdir dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir dirB/dirB2
mv file5 dirB/dirB2
Seu histórico temporário é agora:
/tmp/mail/dir
└── dirB
├── dirB1
│ ├── file33
│ └── file44
└── dirB2
└── file5
Altere também nomes de arquivos no histórico:
cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'
Nota: Isso reescreve o histórico para refletir a alteração de caminho e nome do arquivo.
(ou seja, a alteração do novo local / nome no novo repositório)
3. Aplique novo histórico
Seu outro repo é:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
└── dirH
└── file77
Aplicar confirmações de arquivos de histórico temporários:
cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am
Seu outro repo é agora:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
├── dirB ^
│ ├── dirB1 | New files
│ │ ├── file33 | with
│ │ └── file44 | history
│ └── dirB2 | kept
│ └── file5 v
└── dirH
└── file77
Use git status
para ver a quantidade de confirmações prontas para serem enviadas :-)
Nota: Como o histórico foi reescrito para refletir a alteração do caminho e do nome do arquivo:
(por exemplo, comparado ao local / nome no repositório anterior)
- Não há necessidade de
git mv
alterar o local / nome do arquivo.
- Não há necessidade de
git log --follow
acessar o histórico completo.
Truque extra: detecte arquivos renomeados / movidos em seu repositório
Para listar os arquivos que foram renomeados:
find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'
Mais personalizações: você pode concluir 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 grepping o padrão completo '{. * =>. *}'.
find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'