Можно ли перемещать/переименовывать файлы в Git и сохранять свою историю?

Я хотел бы переименовать/переместить поддерево проекта в Git-перемещение его из

/project/xyz

до

/components/xyz

если я использую обычный git mv project components, затем вся история фиксации для xyz project теряется. Есть ли способ переместить это так, чтобы история сохранялась?

9 ответов


Git обнаруживает переименования, а не сохранение операции с фиксацией, поэтому используете ли вы git mv или mv не имеет значения.

команда log принимает --follow аргумент, который продолжает историю перед операцией переименования, т. е. ищет аналогичный контент с помощью эвристики:

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

для просмотра полной истории, используйте следующую команду:

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

это возможно переименовать файл и сохранить историю нетронутой, хотя это приводит к переименованию файла на протяжении всей истории репозитория. Это, вероятно, только для одержимых любителей git-log и имеет некоторые серьезные последствия, включая следующие:

  • вы можете переписывать общую историю,что является наиболее важным при использовании Git. Если кто-то еще клонировал хранилище, вы сломаете его, делая это. Им придется повторите клонирование, чтобы избежать головных болей. Это может быть нормально, если переименование достаточно важно, но вам нужно будет тщательно рассмотреть это-вы можете в конечном итоге расстроить все сообщество opensource!
  • если вы ссылались на файл, используя его старое имя ранее в истории репозитория, вы эффективно нарушаете более ранние версии. Чтобы исправить это, вам придется сделать немного больше прыжков с обручем. Это не невозможно, просто скучно и, возможно, не стоит того.

теперь, так как вы все еще со мной, вы, вероятно, сольный разработчик, переименовывающий полностью изолированный файл. Давайте переместим файл с помощью 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

будет повтор каждая фиксация в ветке, выполнение команды в галочках для каждой итерации. Много вещей может ошибаешься, когда делаешь это. Обычно я проверяю, присутствует ли файл (в противном случае он еще не перемещен), а затем выполняю необходимые шаги, чтобы обувать дерево по своему вкусу. Здесь вы можете sed через файлы, чтобы изменить ссылки на файл и так далее. Выбить себя! :)

по завершении файл перемещается, и журнал остается нетронутым. Ты чувствуешь себя пиратом ниндзя.

также; mkdir dir необходим только при перемещении файла в новую папку, конечно. Этот если позволит избежать создания этой папки раньше в истории, чем ваш файл существует.


нет.

короткий ответ:нет, невозможно переименовать файл в Git и запомнить историю. И это боль.

ходят слухи, что git log --follow--find-copies-harder будет работать, но это не работает для меня, даже если нет изменения в содержание файла, и шаги были сделаны с git mv.

(Первоначально я использовал Eclipse для переименования и обновления пакетов в одной операции, которая может иметь толку мерзавец. Но это очень распространенная вещь. --follow кажется, работает, если только mv и затем commit и mv не слишком далеко.)

Лайнус говорит, что вы должны понимать все содержимое программного проекта целостно, без необходимости отслеживать отдельные файлы. К сожалению, мой маленький мозг не может этого сделать.

это очень раздражает что так много людей бездумно повторили утверждение, что git автоматически отслеживает движения. Они отняли у меня время. Git не делает ничего подобного. по замыслу(!) Git не отслеживает движения вообще.

мое решение-переименовать файлы обратно в исходные местоположения. Измените программное обеспечение в соответствии с системой управления версиями. С git вам просто нужно git это правильно в первый раз.

к сожалению, это нарушает Eclipse, который, кажется, использует --follow.
git log --follow Иногда не показывает полную История файлов со сложными историями переименования, хотя git log делает. (Не знаю почему.)

(есть некоторые слишком умные хаки, которые возвращаются и возобновляют старую работу, но они довольно пугающие. См. GitHub-Gist: эмиллер / git-mv-с-историей.)


git log --follow [file]

покажет вам историю через переименовывает.


Я:

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

цель

  • использовать git am (навеяно от Smar, заимствованные из Exherbo)
  • добавить историю фиксации скопированных / перемещенных файлов
  • из одного каталога в другой
  • или из одного хранилища в другое

ограничение

  • теги и ветви не сохраняются
  • история вырезается при переименовании файла пути (переименование каталога)

резюме

  1. извлечение истории в формате электронной почты с помощью
    git log --pretty=email -p --reverse --full-index --binary
  2. реорганизовать дерево файлов и обновить имена файлов
  3. добавить новую историю, используя
    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 -- "" > "$historydir/"' {} ';'

к сожалению, вариант --follow или --find-copies-harder нельзя комбинировать с --reverse. Вот почему история вырезается при переименовании файла (или при переименовании родительского каталога).

временная история в формате электронной почты:

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

Дэн 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]\)/[^ ]*:/:g" -i ""' {} ';'

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 сохраняет исходную фиксацию отметки времени (Дэн 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 полный шаблон' {.* => .*}'.

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

в то время как ядро git, водопровод git не отслеживает переименования, история, которую вы отображаете с журналом git "фарфор", может обнаружить их, если хотите.

для данного git log используйте опцию-M:

git log-p-M

С текущей версией git.

это работает для других команд, как git diff как хорошо.

есть варианты, чтобы сделать сравнения более или менее строгими. Если вы переименуйте файл без внесения существенных изменений в файл, в то же время это упрощает для журнала git и друзей обнаружение переименования. По этой причине некоторые люди переименовывают файлы в одной фиксации и меняют их в другой.

существует стоимость использования процессора, когда вы просите git найти, где файлы были переименованы, поэтому используете ли вы его или нет, и когда, зависит от вас.

если вы хотите, чтобы ваша история всегда сообщалась с обнаружением переименования в конкретном репозиторий вы можете использовать:

git config diff.переименовывает 1

перемещение файлов из одного каталога в другой is обнаружены. Вот пример:

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

обратите внимание, что это работает, когда вы используете 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

в качестве пробной версии я сделал небольшое изменение в одном файле в ветви функции и зафиксировал его, а затем в главной ветви я переименовал файл, зафиксировал, а затем внес небольшое изменение в другую часть файла и сделал это. Когда я пошел в ветку объектов и слился с master, слияние переименовало файл и объединило изменения. Вот результат слияния:

 $ 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 в то время.


я хотел бы переименовать/переместить поддерево проекта в Git-перемещение его из

/project/xyz

до

/ компоненты / xyz

если я использую обычный git mv project components, затем вся история фиксации для теряется.

нет (8 лет спустя, Git 2.19, Q3 2018), потому что Git будет обнаружение переименования каталога, и теперь это лучше документировано.

посмотреть совершить b00bf1c, совершить 1634688, совершить 0661e49, совершить 4d34dff, совершить 983f464, совершить c840e1a, совершить 9929430 (27 июня 2018), и совершить d4e8062, совершить 5dacd4a (25 июня 2018) by Элайджа Newren (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 by принимая намек, что весь каталог'x' перешел в 'z'.

но они много других случаев, как:

одна сторона истории переименований x -> z, а другой переименовывает некоторый файл в x/e, вызывая необходимость слияния для транзитивного переименования.

чтобы упростить обнаружение переименования каталога, эти правила применяются Git:

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

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

вы можете ознакомиться большое тестов в t/t6043-merge-rename-directories.sh, которые также указывают, что:

  • a) если переименования разделяют каталог на два или более других, каталог с наибольшим количеством переименований, "wins".
  • b) избегайте каталога-переименования-обнаружения пути, если этот путь является источником переименования по обе стороны слияния.
  • c) применять неявные переименования каталогов только к каталогам, если обратная сторона истории делает переименование.

Я делаю перемещение файлов, а затем делаю

git add -A

которые помещают в область sataging все удаленные / новые файлы. Здесь git понимает, что файл перемещается.

git commit -m "my message"
git push

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