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

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

/project/xyz 

в

 /components/xyz 

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

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

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

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

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

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

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

    • Вы можете переписывать общую историю, которая является самой важной, НЕ НЕ ПРИНИМАЯ ГИТ. Если кто-то еще клонировал repository, вы сломаете его, делая это. Им придется повторно клонировать, чтобы избежать головных болей. Это может быть ОК, если переименование достаточно важно, но вам нужно будет внимательно это рассмотреть – вы можете в конечном итоге нарушить целостность сообщества с открытым исходным кодом!
    • Если вы ссылались на файл, используя его прежнее имя ранее в истории хранилища, вы фактически нарушаете ранние версии. Чтобы исправить это, вам нужно будет немного перепрыгнуть. Это не невозможно, просто утомительно и, возможно, не стоит.

    Теперь, поскольку вы все еще со мной, вы, вероятно, сольный разработчик, переименование полностью изолированного файла. Давайте переместим файл с помощью 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 необходим, только если вы переместите файл в новую папку, конечно. Если это позволит избежать создания этой папки ранее в истории, чем ваш файл существует.

    Нет.

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

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

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

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

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

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

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

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

     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. Извлечение истории в формате электронной почты

    Пример: Извлечение истории file4 , 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 log один раз в файл, запустите его ровно один раз со списком файлов в командной строке и создайте единый унифицированный журнал. Этот способ фиксирует, что изменение нескольких файлов остается единственным фиксацией в результате, и все новые коммиты сохраняют свой первоначальный относительный порядок. Обратите внимание, что это также требует изменений на втором шаге ниже при перезаписи имен файлов в (теперь унифицированном) журнале.


    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 хранит исходные метки времени фиксации (комментарий Дан Боначея ).

    Теперь ваше другое репо:

     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 '{.* => .*}' 

    В то время как kernel ​​git, git plumbing не отслеживает переименования, история, которую вы показываете с помощью 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  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  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 в то время.

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

     /project/xyz 

    в

    / Компоненты / хуг

    Если я использую простые git mv project components , тогда вся история фиксации для проекта xyz теряется.

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

    См. Commit b00bf1c , commit 1634688 , совершить 0661e49 , совершить 4d34dff , совершить 983f464 , совершить c840e1a , совершить 9929430 (27 июня 2018 года) и совершить d4e8062 , совершить 5dacd4a (25 июня 2018 года) Илья Ньюрен ( newren ) .
    (Слияние с Юнио С Хамано – 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) применять неявные переименования каталогов в каталоги, если другая сторона истории – это переименование.

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

     git add -A 

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

     git commit -m "my message" git push 

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

    Interesting Posts
    Давайте будем гением компьютера.