Обработка переименований файлов в git


440

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

Тем не менее, занимаясь этим вечером, я вернулся к этому git mv.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Переименуйте мою таблицу стилей в Finder из iphone.cssвmobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   css/mobile.css

Теперь git думает, что я удалил один файл CSS и добавил новый. Не то, что я хочу, давайте отменим переименование и позвольте git делать всю работу.

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

Вернуться туда, где я начал.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Давайте использовать git mvвместо.

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   index.html
#

Похоже, у нас все хорошо. Так почему же git не узнал переименование в первый раз, когда я использовал Finder?


29
Git отслеживает контент, а не файлы, поэтому не имеет значения, как вы переводите свой индекс в надлежащее состояние - add+rmили mv- он дает тот же результат. Затем Git использует обнаружение переименования / копирования, чтобы вы знали, что это было переименование. Источник, который вы указали, тоже неточный. Это действительно не имеет значения, измените ли вы + переименовать в том же коммите или нет. Когда вы выполняете различие между изменением и переименованием, обнаружение переименования будет видеть его как изменение переименования +, или, если изменение будет полностью переписано, оно будет отображаться как добавленное и удаленное - все равно не имеет значения, как вы выполнили Это.
Каскабель

6
Если это правда, почему он не обнаружил это с моим переименованием с помощью Finder?
Грег К

26
git mv old newавтоматически обновляет индекс. Когда вы переименуете за пределы Git, вам придется выполнить git add newи git rm oldвнести изменения в индекс. Как только вы это сделаете, все git statusбудет работать так, как вы ожидаете.
Крис Джонсен

4
Я просто переместил кучу файлов в public_htmlкаталог, которые отслеживаются в git. Выполнив git add .и git commit, он все еще показал кучу «удаленных» файлов в git status. Я выполнил, git commit -aи удаления были зафиксированы, но теперь у меня нет истории файлов, которые живут public_htmlсейчас. Этот рабочий процесс не так гладко, как хотелось бы.
Грег К

Ответы:


352

Для страничного руководства говоритgit mv

Индекс обновляется после успешного завершения, […]

Итак, сначала вы должны обновить индекс самостоятельно (используя git add mobile.css). Однако
git status все равно покажет два разных файла

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

Вы можете получить другой результат, запустив git commit --dry-run -a, что приведет к тому, что вы ожидаете:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

Я не могу точно сказать , почему мы видим эти различия между git statusи
git commit --dry-run -a, но вот подсказка от Linus :

Git на самом деле даже не заботится о внутреннем «обнаружении переименования», и любые коммиты, которые вы сделали с переименованиями, полностью независимы от эвристики, которую мы затем используем для отображения переименований.

A dry-runиспользует реальные механизмы переименования, а, git statusвероятно, нет.


1
Вы не упомянули этап, на котором вы сделали git add mobile.css. Без него git status -aтолько «увидели бы» удаление ранее отслеженного iphone.cssфайла, но не затронули бы новый неотслеживаемый mobile.cssфайл. Также git status -aнедействителен с Git 1.7.0 и новее. «« Git status »больше не« git commit --dry-run ».» в kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . Используйте, git commit --dry-run -aесли вы хотите эту функциональность. Как уже говорили другие, просто обновите индекс и git statusбудете работать так, как ожидает ОП.
Крис Джонсен

3
если вы сделаете обычное, git commitон не будет фиксировать переименованный файл, а рабочее дерево останется прежним. git commit -aпобеждает практически все аспекты модели рабочего процесса / мышления git - каждое изменение совершается. Что делать, если вы хотите переименовать файл, но внести изменения index.htmlв другой коммит?
knittl

@ Крис: да, конечно, я добавил, mobile.cssчто я должен был упомянуть. Но в этом суть моего ответа: страница руководства говорит, что the index is updatedкогда вы используете git-mv. Спасибо за status -aразъяснение, я использовал git 1.6.4
tanascius

4
Отличный ответ! Я бился головой о стену, пытаясь понять, почему git statusне обнаружил переименование. Запуск git commit -a --dry-runпосле добавления моих «новых» файлов показал переименования и, наконец, вселил в меня уверенность в фиксации!
Стивен Хансон,

1
В git 1.9.1 git statusтеперь ведет себя как git commit.
Жак Рене Месрин

77

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

Единственная разница между mv old newи git mv old newзаключается в том, что git mv также добавляет файлы в индекс.

mv old newтогда git add -Aбы тоже сработало.

Обратите внимание, что вы не можете просто использовать, git add .потому что это не добавляет удаления в индекс.

Смотрите разницу между "git add -A" и "git add".


3
Спасибо за git add -Aссылку, очень полезно, так как я искал такой ярлык!
Фил

5
Обратите внимание , что с мерзавцем 2, git add . это добавить абсорбцию в индекс.
Ник МакКарди

19

Лучше всего попробовать это для себя.

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

Теперь git status и git commit --dry-run -a показывают два разных результата, где git status показывает bbb.txt как новый файл / aaa.txt, а команды --dry-run показывают фактическое переименование.

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# 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:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

Теперь идите и сделайте регистрацию.

git commit -a -m "Rename"

Теперь вы можете видеть, что файл фактически переименован, а то, что показано в git status, неверно.

Мораль истории: если вы не уверены, был ли ваш файл переименован, введите команду «git commit --dry-run -a». Если он показывает, что файл переименован, вы можете идти.


3
Что важно для Git, оба верны . Последнее ближе к тому, как вы, как коммитер, вероятно, видите это. Реальная разница между переименовывать и удалять + создавать только на уровне OS / файловой системы (например , тот же индексный дескриптор # против нового инода #), что Git на самом деле не очень волнует.
Алоис Махдал

17

Для git 1.7.x у меня работали следующие команды:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

Не было необходимости в git add, так как исходный файл (т.е. css / mobile.css) уже был в подтвержденных файлах ранее.


6
Эта. Все остальные ответы нелепо и излишне сложны. Это поддерживает историю файлов между коммитами, так что слияния до / после переименования файлов не нарушаются.
Великолепно

10

у вас есть git add css/mobile.cssновый файл и git rm css/iphone.css, таким образом, git знает об этом. тогда он покажет тот же результат вgit status

Вы можете ясно увидеть это в выводе статуса (новое имя файла):

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

и (старое имя):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

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


Я не думал, что должен был, git rm css/iphone.cssпотому что я думал, что это удалит существующую историю. Может быть, я неправильно понимаю рабочий процесс в git.
Грег К

4
@ Грег К: git rmне удалит историю. Он только удаляет запись из индекса, чтобы следующая фиксация не имела записи. Однако, это все еще будет существовать в наследственных коммитах. Что вас может смущать, так это то, что (например) git log -- newвы остановитесь в том месте, где вы совершили ошибку git mv old new. Если вы хотите следовать переименованиям, используйте git log --follow -- new.
Крис Джонсен

9

Давайте подумаем о ваших файлах с точки зрения git.

Имейте в виду, что git не отслеживает метаданные о ваших файлах

Ваш репозиторий имеет (среди прочих)

$ cd repo
$ ls
...
iphone.css
...

и это под контролем git:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

Проверьте это с:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

Когда вы делаете

$ mv iphone.css mobile.css

С точки зрения мерзавца,

  • iphone.css нет (он удаляется - об этом предупреждает git).
  • появился новый файл mobile.css .
  • Эти файлы абсолютно не связаны.

Итак, git сообщает о файлах, которые он уже знает ( iphone.css ), и о новых файлах, которые он обнаруживает ( mobile.css ), но только когда файлы находятся в индексе или HEAD, git начинает проверять их содержимое.

На данный момент, ни «iphone.css удаление» , ни mobile.css являются по индексу.

Добавить удаление iphone.css в индекс

$ git rm iphone.css

Git точно скажет вам, что произошло: ( iphone.css удален. Больше ничего не произошло)

затем добавьте новый файл mobile.css

$ git add mobile.css

На этот раз и удаление, и новый файл находятся в индексе. Теперь git обнаруживает тот же контекст и выставляет его как переименованный. Фактически, если файлы похожи на 50%, он обнаружит это как переименование, что позволит вам немного изменить mobile.css , сохранив операцию как переименование.

Смотрите, это воспроизводимо на git diff. Теперь, когда ваши файлы в индексе, вы должны использовать --cached. Немного отредактируйте mobile.css , добавьте это в индекс и увидите разницу между:

$ git diff --cached 

а также

$ git diff --cached -M

-Mэто опция "обнаружить переименования" для git diff. -Mрасшифровывается как -M50%(сходство 50% и более заставит git выразить его как переименование), но вы можете уменьшить его до -M20%(20%), если много редактируете mobile.css.


8

Шаг 1: переименуйте файл из старого файла в новый файл

git mv #oldfile #newfile

Шаг 2: сделать коммит и добавить комментарии

git commit -m "rename oldfile to newfile"

Шаг 3: перенесите это изменение на удаленный сервер

git push origin #localbranch:#remotebranch

1
Пожалуйста, добавьте комментарий, чтобы он был полезен для OP
Devrath

Шаг 2 не нужен. После git mvэтого новый файл уже находится в индексе.
Алоис Махдал

7

Git распознает файл по содержимому, а не видит его как новый неотслеживаемый файл

Вот где ты ошибся.

Только после добавления файла git распознает его по содержимому.


Точно. При постановке мерзавец покажет переименование правильно.
Макс Маклеод

3

Вы не ставили результаты своего поиска. Я полагаю, что если вы сделали перемещение через Finder, а затем сделали это git add css/mobile.css ; git rm css/iphone.css, git вычислил бы хэш нового файла и только тогда понял бы, что хэши файлов совпадают (и, следовательно, это переименование).


2

В случаях, когда вам действительно нужно переименовать файлы вручную, например. используя скрипт для пакетного переименования группы файлов, затем git add -A .сработал для меня.


2

Для пользователей XCode: если вы переименуете свой файл в XCode, вы увидите, что значок значка изменится на добавление. Если вы делаете коммит с использованием XCode, вы фактически создаете новый файл и теряете историю.

Обойти это легко, но вы должны сделать это перед фиксацией с помощью Xcode:

  1. Сделайте git Status для вашей папки. Вы должны увидеть, что внесенные изменения верны:

переименован: Project / OldName.h -> Project / NewName.h переименован: Project / OldName.m -> Project / NewName.m

  1. сделать коммит -m 'изменение имени'

Затем вернитесь в XCode, и вы увидите, что значок изменился с A на M, и теперь он сохраняет изменения, чтобы зафиксировать изменения при использовании xcode.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.