Как сделать так, чтобы git пометил удаленный и новый файл как файл перемещенный?

505

Я переместил файл вручную, а затем изменил его. По словам Git, это новый файл и удаленный файл. Есть ли способ заставить Git рассматривать его как перемещение файла?

pupeno
источник
3
Для данного файла old_file.txt, то git mv old_file.txt new_file.txtэто эквивалентно git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Ярл
3
Ярл: нет, это не так. Если в файле также есть изменения, git mvони не будут добавлены в кеш, но git addбудут. Я предпочитаю переместить файл назад, чтобы я мог использовать его git mv, а затем git add -pпросмотреть свой набор изменений.
Дхарди,
пожалуйста, проверьте мой сценарий github.com/Deathangel908/python-tricks/blob/master/…
deathangel908
Git улучшился за последние 8 лет, если это всего лишь один файл, верхний ответ stackoverflow.com/a/433142/459 ничего не сделал… но вы можете следить за stackoverflow.com/a/1541072/459, чтобы получить обновление rm / add в мв / изменить статус.
Дламблин

Ответы:

436

Git автоматически обнаружит движение / переименование, если ваша модификация не слишком серьезна. Просто git addновый файл и git rmстарый файл. git statusзатем покажет, обнаружил ли он переименование.

Кроме того, для перемещения по каталогам вам может понадобиться:

  1. перейдите к вершине этой структуры каталогов.
  2. Запустить git add -A .
  3. Запустите, git statusчтобы убедиться, что «новый файл» теперь является «переименованным» файлом

Если в git status по-прежнему отображается «новый файл», а не «переименован», вам нужно следовать совету Хэнка Гея и выполнять перемещение и модификацию в двух отдельных коммитах.

Бомбы
источник
75
Пояснение: «не слишком серьезный» означает, что новый файл и старый файл> 50% «похожи» на основании некоторых индексов сходства, которые использует git.
pjz 19.10.10
151
Что-то, что повесило меня на несколько минут: если переименованные файлы и удаленные файлы не подготовлены для фиксации, они будут отображаться как удаление и новый файл. Как только вы добавите их в промежуточный индекс, он распознает его как переименование.
Марчич
19
Стоит упомянуть, что, говоря о « Git, автоматически обнаружит перемещение / переименование ». Это происходит в то время, когда вы используете git status, git logили git diff, не в то время, когда вы делаете git add, git mvили git rm. Далее, говоря об обнаружении переименования, это имеет смысл только для промежуточных файлов. Таким образом, git mvпоследующее изменение файла может выглядеть так, git statusкак будто оно рассматривает его как файл rename, но когда вы используете git stage(так же, как git add) файл, становится ясно, что изменение слишком велико, чтобы его можно было обнаружить как переименованное.
Ярл
5
Реальный ключ, кажется, заключается в добавлении как новых, так и старых локаций в одно и то же git add, что, как предполагает @ jrhorn424, должно быть всего лишь целым репо.
Брианари
5
Это не безошибочно, особенно если файл изменился и имеет небольшой размер. Есть ли способ, чтобы конкретно рассказать git о переезде?
Ребс
112

Делать движение и модифицировать в отдельных коммитах.

Хэнк Гей
источник
12
Я понимаю, что Git может обрабатывать ходы и модификации одновременно. При программировании на Java и использовании IDE переименование класса является одновременно и модификацией, и движением. Я понимаю, что Git даже должен иметь возможность автоматически выяснить это, когда есть ход (из удаления и создания).
Пупено
1
Perl тоже требует этого, и у меня никогда не было, чтобы Git обнаруживал движение / переименование.
jrockway
10
@ jrockway, я имел. Происходит легко с небольшими файлами, я думаю, они «становятся слишком разными», чтобы означать движение ».
Ноябрь 15-10
2
Есть ли способ сделать это автоматизированным? У меня много файлов в индексе, я хочу сначала зафиксировать перемещение, а затем изменение, но это трудно сделать вручную
ReDetection
5
Стоит отметить, что если git diffне распознавать переименование через один коммит, он не распознает его через два коммита. Вам нужно будет использовать -Mака, --find-renamesчтобы сделать это. Поэтому, если мотивация, которая привела вас к этому вопросу, состоит в том, чтобы увидеть переименование в запросе на извлечение (если судить по опыту), разделение его на два коммита не приведет вас к этой цели.
Mattliu
44

Это все воспринимаемые вещи. Git обычно довольно хорошо распознает ходы, потому что GIT - это трекер контента.

Все, что действительно зависит, - это то, как ваш «стат» отображает это. Единственная разница здесь - флаг -M.

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log --stat

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git help log

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.
Кент Фредрик
источник
76
Извините, если это кажется немного педантичным, но «Git, как правило, довольно хорошо распознает ходы, потому что GIT - это трекер контента», мне кажется, не является следствием. Да, это трекер контента, и, возможно, он хорошо обнаруживает ходы, но одно утверждение на самом деле не следует из другого. Только потому, что это трекер контента, обнаружение перемещения не обязательно хорошо. На самом деле, у трекера контента вообще не может быть обнаружения движения.
Лоуренс Гонсалвес
1
@WarrenSeine, когда вы переименовываете каталог, файлы SHA1 в этом каталоге не изменяются. Все, что у вас есть, это новый объект TREE с теми же SHA1. Нет причин, по которым переименование каталога может привести к значительным изменениям данных.
Кент Фредрик
1
@KentFredric Вы показали, что с помощью -M с «git log» мы можем проверить, переименован ли файл, и я попробовал его, и он работает нормально, но когда я отправляю свой код для проверки и вижу этот файл в gerrit ( gerritcodereview.com ) там он показывает, что файл был добавлен, а предыдущий был удален. Так есть ли опция в "git commit", используя которую я делаю commit и gerrit показывает это правильно.
Патрик
1
Я этого не показывал. Я показал, что Git способен притворяться, что add + delete - это переименование из-за одинакового содержимого. Там нет никакого способа узнать, что произошло, и это не волнует. Все, что git помнит, это «добавлено» и «удалено». «Переименованный» никогда не записывается.
Кент Фредрик
1
Например, в последнее время я много делал «Копировать + Редактировать» в репо. Большинство способов его просмотра видят только «новый файл», но если вы пропустите git log -M1 -C1 -B1 -D --find-copies-harder, git может «обнаружить», что новый файл мог быть скопирован первым. Иногда он делает это правильно, иногда он находит совершенно не связанные файлы, которые имеют идентичное содержимое.
Кент Фредрик
36

git diff -Mили git log -Mдолжны автоматически обнаруживать такие изменения как переименование с незначительными изменениями, если они действительно есть. Если ваши незначительные изменения не являются незначительными, вы можете уменьшить порог сходства, например

$ git log -M20 -p --stat

чтобы уменьшить его от значения по умолчанию 50% до 20%.

Саймон Ист
источник
13
Можно ли определить порог в конфиге?
Повторное обнаружение
34

Вот быстрое и грязное решение для одного или нескольких переименованных и измененных файлов, которые не были переданы.

Допустим, файл был назван fooи теперь он называется bar:

  1. Переименуйте barво временное имя:

    mv bar side
    
  2. Оформить заказ foo:

    git checkout HEAD foo
    
  3. Переименуйте fooв barGit:

    git mv foo bar
    
  4. Теперь переименуйте ваш временный файл обратно в bar.

    mv side bar
    

Этот последний шаг - то, что возвращает ваш измененный контент обратно в файл.

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

$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    README.md
    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ git mv README README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.
dlamblin
источник
27
Этот процесс не имеет смысла. мерзавец не добавляет метаданные для переименований, git mvпросто удобство для git rm/ git addпары. Если вы уже выполнили «mv bar foo», то все, что вам нужно сделать, - это убедиться, что вы git add fooи git rm barдо совершения коммита. Это может быть сделано как одна git add -Aкоманда или, возможно, git add foo; git commit -aпоследовательность.
CB Bailey
17
Все, что я знаю, это то, что до того, как я это сделал, Git не распознал это как ход. После того, как я это сделал, Git признал это ходом.
8
Это признает это как движение. Но это также имеет изменение как неустановленное изменение сейчас. Как только вы addснова загрузите файл, git разбивает перемещение / изменение на удаленное / добавленное снова.
Майкл Пифель
4
Этот процесс будет работать, если вы git commitпосле шага 3, в противном случае это неверно. Кроме того, @CharlesBailey правильно, вы можете с легкостью выполнить нормальное действие mv blah fooна шаге 3 с последующей фиксацией и получить те же результаты.
DaFlame
5
«Этот процесс не имеет смысла». - Он служит цели, когда Git запутался и думает, что файл был удален, а другой файл был добавлен. Это устраняет путаницу в Git и помечает файл как перемещенный явно, без необходимости делать промежуточные коммиты.
Данак
20

Если вы говорите git statusне показывать переименования, попробуйте git commit --dry-run -aвместо

Совет директоров
источник
11

Если вы используете TortoiseGit, важно отметить, что автоматическое обнаружение переименования в Git происходит во время коммита, но тот факт, что это произойдет, не всегда отображается программным обеспечением заранее. Я переместил два файла в другой каталог и выполнил несколько небольших изменений. Я использую TortoiseGit в качестве инструмента фиксации, и в списке «Изменения» были показаны файлы, которые были удалены и добавлены, а не перемещены. Запуск git status из командной строки показал похожую ситуацию. Однако после фиксации файлов они оказались переименованы в журнале. Таким образом, ответ на ваш вопрос таков: пока вы не сделали ничего слишком радикального, Git должен автоматически подобрать переименование.

Редактировать: очевидно, если вы добавляете новые файлы, а затем делаете состояние git из командной строки, переименование должно отображаться перед фиксацией.

Редактировать 2: Кроме того, в TortoiseGit добавьте новые файлы в диалоге фиксации, но не фиксируйте их. Затем, если вы войдете в команду Show Log и посмотрите на рабочий каталог, вы увидите, обнаружил ли Git переименование перед фиксацией.

Тот же вопрос был поднят здесь: https://tortoisegit.org/issue/1389 и был зарегистрирован как ошибка, чтобы исправить здесь: https://tortoisegit.org/issue/1440 Оказывается, это проблема отображения с коммитом TortoiseGit диалог, а также вид существует в состоянии git, если вы не добавили новые файлы.

Джайлс Робертс
источник
2
Вы правы, даже если TortoiseGit показывает delete + add, даже если git status показывает delete + add, даже если git commit --dry-run показывает delete + add, после git commit я вижу переименование, а не delete + add.
Томас Кубес
1
Я уверен, что автоматическое обнаружение переименования происходит во время поиска истории ; в коммите это всегда добавить + удалить. Это также объясняет шизофреническое поведение, которое вы описываете. Отсюда и варианты здесь: stackoverflow.com/a/434078/155892
Марк Совул
10

Или вы можете попробовать ответить на этот вопрос здесь от Амбер ! Чтобы процитировать это снова:

Сначала отмените поэтапное добавление для перемещенного вручную файла:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Затем используйте Git для перемещения файла:

$ git mv path/to/oldfile path/to/newfile

Конечно, если вы уже совершили ручное перемещение, вы можете вместо этого сбросить ревизию до перемещения, а затем просто выполнить команду git mv.

Zingam
источник
7

Используйте git mvкоманду для перемещения файлов вместо команд перемещения ОС: https://git-scm.com/docs/git-mv

Обратите внимание, что git mvкоманда существует только в Git версий 1.8.5 и выше. Поэтому вам, возможно, придется обновить ваш Git, чтобы использовать эту команду.

mostafa.elhoushi
источник
3

У меня была эта проблема недавно, при перемещении (но не изменении) некоторых файлов.

Проблема в том, что Git изменил некоторые окончания строк, когда я переместил файлы, а затем не смог сказать, что файлы были одинаковыми.

Использование git mvрешило проблему, но она работает только с отдельными файлами / каталогами, и у меня было много файлов в корне хранилища.

Один из способов исправить это - использовать магию bash / batch.

Другой способ заключается в следующем

  • Переместите файлы и git commit. Это обновляет окончания строки.
  • Переместите файлы обратно в их исходное местоположение, теперь, когда у них есть новые окончания строк, и git commit --amend
  • Переместите файлы еще раз и git commit --amend. В этот раз нет изменений в окончаниях строк, поэтому Git счастлив
cedd
источник
1

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

Алекс Илие
источник
0

Существует, вероятно, лучший способ «командной строки» сделать это, и я знаю, что это взлом, но я так и не смог найти хорошего решения.

Использование TortoiseGIT: Если у вас есть коммит GIT, в котором некоторые операции перемещения файлов отображаются в виде загрузки / удаления, а не переименования, даже если файлы имеют небольшие изменения, сделайте следующее:

  1. Проверьте, что вы сделали локально
  2. Проверить мини-однострочное изменение во 2-м коммите
  3. Перейти в GIT войти в черепаху Git
  4. Выберите два коммита, щелкните правой кнопкой мыши и выберите «объединить в один коммит»

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

Джон Реа
источник
0

Когда я редактирую, переименовываю и перемещаю файл одновременно, ни одно из этих решений не работает. Решение состоит в том, чтобы сделать это в двух коммитах (редактировать и переименовать / переместить по отдельности), а затем fixupчерез второй коммит git rebase -iсделать это в одном коммите.

Hativ
источник
0

Как я понимаю этот вопрос: «Как заставить git распознавать удаление старого файла и создание нового файла при перемещении файла».

Да, в рабочем каталоге, как только вы удалите старый файл и вставите старый файл, git statusпроизойдет " deleted: old_file" и " Untracked files: ... new_file"

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

git add new_file
git rm old_file

Если содержимое файла на 50% или более одинаковое, при выполнении git statusкоманды вы должны получить:

renamed: old_file -> new_file
harshainfo
источник
1
git statusскажет " deleted: old_file" и " Untracked files: ... new_file": не начиная с Git 2.18: stackoverflow.com/a/50573107/6309
VonC