Можно ли перемещать/переименовывать файлы в 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:

ограничение пары основных правил, когда применяется определение переименования каталогов:

  1. Если данный каталог все еще существует по обе стороны от слияния, мы не считаем его переименованным.
  2. Если подмножество файлов, которые должны быть переименованы, имеют файл или каталог в пути (или будут мешать друг другу), "отключите" переименование каталога для этих конкретных подпутей и сообщите о конфликте пользователю,
  3. Если другая сторона истории сделала переименование каталога по пути, который переименована вашей стороной истории, то игнорируйте это конкретное переименование с другой стороны истории для любых неявных переименований каталога (но предупреждайте пользователя).

Вы можете увидеть множество тестов в 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

Я не знаю, почему, но это работает для меня.