Можно ли перемещать/переименовывать файлы в Git и поддерживать их историю?
Я хотел бы переименовать/переместить поддерево проекта в Git, перемещая его из
/project/xyz
в
/components/xyz
Если я использую простые git mv project components
, то вся история коммитов для xyz project
теряется. Есть ли способ переместить это так, чтобы история сохранялась?
Ответы
Ответ 1
Git обнаруживает переименования, а не сохраняет операцию с фиксацией, поэтому не имеет значения, используете ли вы git mv
или mv
.
Команда log
принимает аргумент --follow
который продолжает историю до операции переименования, т. --follow
похожий контент, используя эвристику:
http://git-scm.com/docs/git-log
Чтобы просмотреть полную историю, используйте следующую команду:
git log --follow ./path/to/file
Ответ 2
Можно переименовать файл и сохранить историю неповрежденной, хотя она заставляет файл переименовываться на протяжении всей истории репозитория. Это, вероятно, только для навязчивых любителей git -log-а, и имеет некоторые серьезные последствия, в том числе следующие:
- Вы можете переписать общую историю, которая является самой важной, НЕ НЕ используя Git. Если кто-то еще клонировал репозиторий, вы сломаете его, делая это. Им придется повторно клонировать, чтобы избежать головных болей. Это может быть нормально, если переименование достаточно важно, но вам нужно будет рассмотреть это внимательно - вы можете в конечном итоге нарушить целостность сообщества openource!
- Если вы ссылались на файл, используя его прежнее имя ранее в истории репозитория, вы фактически нарушаете ранние версии. Чтобы исправить это, вам нужно будет немного перепрыгнуть. Это не невозможно, просто утомительно и, возможно, не стоит того.
Теперь, поскольку вы все еще со мной, вы, вероятно, сольный разработчик, переименование полностью изолированного файла. Переместите файл с помощью filter-tree
!
Предположим, вы переместите файл old
в папку dir
и укажите ему имя new
Это можно сделать с помощью git mv old dir/new && git add -u dir/new
, но это разрушает историю.
Вместо
git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD
будет повторять каждую фиксацию в ветке, выполняя команду в тиках для каждой итерации. Когда вы это сделаете, многие вещи могут пойти не так. Обычно я проверяю, присутствует ли файл (в противном случае он еще не движется), а затем выполните необходимые шаги, чтобы обучить дерево по своему вкусу. Здесь вы можете обойти файлы, чтобы изменить ссылки на файл и так далее. Выбивайся!:)
По завершении файл перемещается, и журнал не поврежден. Вы чувствуете себя пиратом ниндзя.
Также; Маркер mkdir необходим, только если вы переместите файл в новую папку, конечно. Если это позволит избежать создания этой папки ранее в истории, чем ваш файл существует.
Ответ 3
Нет.
Короткий ответ НЕТ, невозможно переименовать файл в Git и запомнить историю. И это боль.
Ходят слухи, что git log --follow
--find-copies-harder
будет работать, но это не сработает для меня, даже если есть нулевые изменения в содержимое файла, и шаги были сделаны с помощью git mv
.
(Первоначально я использовал Eclipse для переименования и обновления пакетов за одну операцию, которая, возможно, путала git. Но это очень обычная вещь. --follow
работает, если выполняется только mv
а затем a commit
и mv
не слишком далеко.)
Линус говорит, что вы должны понимать целостное содержание программного проекта целостно, не нужно отслеживать отдельные файлы. К сожалению, мой маленький мозг не может этого сделать.
действительно раздражает, что многие люди бездумно повторили выражение о том, что Git автоматически отслеживает ходы. Они потратили мое время. Git нет такой вещи. По дизайну (!) Git не отслеживает перемещения вообще.
Мое решение - переименовать файлы в исходное местоположение. Измените программное обеспечение в соответствии с контролем источника. С Git вам просто кажется, что нужно Git правильно в первый раз.
К сожалению, это разрывает Eclipse, который, как представляется, использует --follow
.
git log --follow
Иногда не отображается полная история файлов со сложными историями переименований, хотя git log
делает. (Я не знаю, почему.)
(Есть слишком умные хаки, которые возвращаются и возобновляют старую работу, но они довольно пугающие. См. GitHub-Gist: emiller/ git - мв-с историей.)
Ответ 4
git log --follow [file]
покажет вам историю с помощью переименований.
Ответ 5
Я делаю:
git mv {old} {new}
git add -u {new}
Ответ 6
Цель
- Используйте
git am
(вдохновленный Smar, заимствованный от Exherbo)
- Добавить историю фиксации скопированных/перемещенных файлов
- От одного каталога к другому
- Или из одного репозитория в другой
Ограничение
- Теги и ветки не сохраняются
- История сокращается при переименовании файла пути (переименование каталога)
Резюме
- Извлечь историю в формате электронной почты, используя
git log --pretty=email -p --reverse --full-index --binary
- Реорганизовать дерево файлов и обновить имена файлов
- Добавить новую историю, используя
cat extracted-history | git am --committer-date-is-author-date
1. Извлечение истории в формате электронной почты
Пример. Извлеките историю file3
, file4
и file5
my_repo
├── dirA
│ ├── file1
│ └── file2
├── dirB ^
│ ├── subdir | To be moved
│ │ ├── file3 | with history
│ │ └── file4 |
│ └── file5 v
└── dirC
├── file6
└── file7
Установить/очистить пункт назначения
export historydir=/tmp/mail/dir # Absolute path
rm -rf "$historydir" # Caution when cleaning the folder
Извлечь историю каждого файла в формате электронной почты
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"' {} ';'
К сожалению, параметр --follow
или --find-copies-harder
нельзя комбинировать с --reverse
. Вот почему история вырезается, когда файл переименовывается (или когда родительский каталог переименовывается).
Временная история в формате электронной почты:
/tmp/mail/dir
├── subdir
│ ├── file3
│ └── file4
└── file5
Dan Bonachea предлагает инвертировать петли команды генерации журнала git на этом первом шаге: вместо того, чтобы запускать журнал git один раз на файл, запустить его ровно один раз со списком файлов в командной строке и сгенерировать единый унифицированный журнал. Этот способ фиксирует, что изменение нескольких файлов остается единственным фиксацией в результате, и все новые коммиты сохраняют свой первоначальный относительный порядок. Обратите внимание, что это также требует изменений на втором шаге ниже при перезаписи имен файлов в (теперь унифицированном) журнале.
2. Реорганизовать дерево файлов и обновить имена файлов
Предположим, вы хотите переместить эти три файла в этом другом репо (может быть такое же репо).
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
Поэтому реорганизуйте свои файлы:
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
Ваша временная история теперь:
/tmp/mail/dir
└── dirB
├── dirB1
│ ├── file33
│ └── file44
└── dirB2
└── file5
Измените также имена файлов в истории:
cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'
3. Применить новую историю
Другое ваше репо:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
└── dirH
└── file77
Применить фиксации из временных файлов истории:
cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date
--committer-date-is-author-date
сохраняет исходные метки времени фиксации (Dan Bonachea).
Теперь ваше другое репо:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
├── dirB
│ ├── dirB1
│ │ ├── file33
│ │ └── file44
│ └── dirB2
│ └── file5
└── dirH
└── file77
Используйте git status
, чтобы увидеть количество коммитов, готовых к нажатию: -)
Дополнительный трюк: проверьте переименованные/перемещенные файлы в своем репо
Чтобы перечислить файлы, которые были переименованы:
find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'
Дополнительные настройки: вы можете выполнить команду git log
с помощью опций --find-copies-harder
или --reverse
. Вы также можете удалить первые два столбца с помощью cut -f3-
и grepping complete pattern '{. * = > . *}'.
find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
Ответ 7
Я хотел бы переименовать/переместить поддерево проекта в Git, перемещая его из
/project/xyz
в
/Компоненты/хуг
Если я использую простые git mv project components
, то вся история коммитов для проекта xyz
теряется.
Нет (8 лет спустя, Git 2.19, Q3 2018), потому что Git обнаружит переименование каталога, и теперь это лучше документировано.
Смотрите коммит b00bf1c, коммит 1634688, коммит 0661e49, коммит 4d34dff, коммит 983f464, коммит c840e1a, коммит 9929430 (27 июня 2018 г.) и коммит d4e8062, коммит 5dacd4a (25 июня 2018 г.) Элайджей Ньюреном (newren
).
(Объединено Junio C Hamano - gitster
- в коммите 0ce5a69, 24 июля 2018 г.)
Это теперь объясняется в Documentation/technical/directory-rename-detection.txt
:
Пример:
Когда все x/a
, x/b
и x/c
переместились в z/a
, z/b
и z/c
, вполне вероятно, что добавленные за это время x/d
также захотят перейти к z/d
намекнув, что весь каталог ' x
' переместился в ' z
'.
Но они много других случаев, таких как:
одна сторона истории переименовывает x → z
, а другая переименовывает некоторый файл в x/e
, вызывая необходимость слияния для транзитивного переименования.
Чтобы упростить обнаружение переименования каталогов, эти правила применяются Git:
ограничение пары основных правил, когда применяется определение переименования каталогов:
- Если данный каталог все еще существует по обе стороны от слияния, мы не считаем его переименованным.
- Если подмножество файлов, которые должны быть переименованы, имеют файл или каталог в пути (или будут мешать друг другу), "отключите" переименование каталога для этих конкретных подпутей и сообщите о конфликте пользователю,
- Если другая сторона истории сделала переименование каталога по пути, который переименована вашей стороной истории, то игнорируйте это конкретное переименование с другой стороны истории для любых неявных переименований каталога (но предупреждайте пользователя).
Вы можете увидеть множество тестов в t/t6043-merge-rename-directories.sh
, которые также указывают, что:
- a) Если переименовывает разделить каталог на два или более других, каталог с наибольшим количеством переименований, "выигрывает".
- b) Избегайте обнаружения каталогов-переименований для пути, если этот путь является источником переименования по обе стороны от слияния.
- c) Применяйте неявные переименования каталогов только к тем каталогам, которые переименовывают другую сторону истории.
Ответ 8
В то время как ядро git, сантехника git не отслеживает переименования, история, которую вы показываете с помощью git log "фарфора", может обнаруживать их, если хотите.
Для данного git log
используйте параметр -M:
git log -p -M
С текущей версией git.
Это работает и для других команд, таких как git diff
.
Есть варианты, чтобы сделать сравнения более или менее строгими. Если вы переименуете файл без внесения существенных изменений в файл в то же время, это облегчит для журнала и друзей git обнаружение переименования. По этой причине некоторые люди переименовывают файлы в одном коммите и меняют их в другой.
Там, где стоит процессор, каждый раз, когда вы запрашиваете git, чтобы найти, где файлы были переименованы, используйте ли вы его использование или нет, и когда это зависит от вас.
Если вы хотите, чтобы ваша история сообщалась с обнаружением переименования в определенном репозитории, вы можете использовать:
git config diff.renames 1
Обнаружены файлы, перемещающиеся из одного каталога в другой. Вот пример:
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
Обратите внимание, что это работает, когда вы используете diff, а не только с git log
. Например:
$ 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
В качестве пробной версии я сделал небольшое изменение в одном файле в ветки функции и зафиксировал его, а затем в главном ветки я переименовал файл, зафиксировал его, а затем сделал небольшое изменение в другой части файла и совершил это. Когда я пошел в ветку функций и слился с мастером, слияние переименовало файл и объединило изменения. Здесь вывод из слияния:
$ 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%)
В результате появился рабочий каталог с переименованным файлом и внесенными изменениями текста. Таким образом, для git можно сделать правильную вещь, несмотря на то, что она явно не отслеживает переименования.
Это поздний ответ на старый вопрос, поэтому другие ответы, возможно, были правильными для версии git в то время.
Ответ 9
Я делаю перемещение файлов, а затем выполняю
git add -A
который помещает в сателлинг зону все удаленные/новые файлы. Здесь git понимает, что файл перемещен.
git commit -m "my message"
git push
Я не знаю, почему, но это работает для меня.