Git действительно не переименовывает. Они все, вычисленное в «после факта» мода: git сравнивает одну фиксацию с другой и, в момент сравнения, решает, было ли переименование. Это означает, что git считает, что что-то «переименование» изменяется динамически. Я знаю, что вы спрашиваете о том, что вы еще не сделали, но несите меня, это действительно все связано (но ответ будет долгим).
Когда вы спросите мерзавец (с помощью git show
или git log -p
или git diff HEAD^ HEAD
) «что произошло в последней фиксации», он запускает дифф из предыдущей фиксации (HEAD^
или HEAD~1
или необработанного SHA-1 для предыдущего commit - любой из них сделает это, чтобы идентифицировать его) и текущий фиксатор (HEAD
). При создании этого diff он может обнаружить, что раньше был old.txt
и больше не существует; и не было new.txt
, но есть и сейчас.
Эти имена файлов, которые раньше были там, но нет, и файлы, которые есть сейчас, которые не были помещены в кучу, помечены как «кандидаты для переименования». Затем для каждого имени в куче git сравнивает «старое содержимое» и «новое содержимое». Сравнение для Точное совпадение является сверхлегким из-за того, что git уменьшает содержимое до SHA-1; если точное совпадение терпит неудачу, git переключается на опциональное «содержимое по крайней мере похожее» для проверки переименований. С git diff
этот дополнительный шаг управляется флагом -M
. С другими командами он либо задается вашими значениями git config
, либо жестко закодирован в команду.
Теперь вернемся к промежуточной области и git status
: что git хранит в области индекса/промежуточной области, в основном является «прототипом следующего коммита». Когда вы делаете git add
, git сохраняет содержимое файла прямо в этой точке, вычисляя SHA-1 в процессе и затем сохраняя SHA-1 в индексе. Когда вы git rm
что-то, git хранит примечание в индексе, говорящее, что «это имя пути намеренно удаляется при следующем коммит».
Команда git status
, то, просто делает diff-или действительно, два отличия: HEAD
vs index, для чего это будет сделано; и индекс против дерева, для чего может быть (но еще не готов).
В этом первом diff git использует тот же механизм, что и всегда, для обнаружения переименований. Если в коде HEAD
есть путь, который ушел в индекс, а путь в новом индексе, а не в HEAD
, он является кандидатом на переименование. Команда git status
hardwires переименовывает обнаружение на «включено» (и ограничение количества файлов на 200, причем только один кандидат на обнаружение переименования - это много).
Что все это значит для вашего случая? Ну, вы переименовали файл (без использования git mv
, но это не имеет большого значения, потому что git status
находит переименование или не находит его на git status
времени), и теперь у вас новая, новая версия нового файла.
Если у вас git add
новая версия, эта более новая версия входит в репо, а ее SHA-1 находится в индексе, а когда git status
делает diff, он будет сравнивать новый и старый. Если они по крайней мере «похожи на 50%» (жесткое значение для git status
), git сообщит вам, что файл переименован.
Конечно, git add
-ный измененных содержания не совсем то, что вы просили: вы хотели бы сделать промежуточный совершить где файл только переименован, т.е. совершить с деревом с новым именем , но старое содержимое.
У вас нет , чтобы сделать это из-за всего вышеперечисленного динамического определения переименования. Если вы хотите сделать это (по какой-либо причине) ... ну, git не делает все это так просто.
Самый простой способ заключается в том, что вы предлагаете: перемещайте измененное содержимое где-то в стороне, используйте git checkout -- old-name.txt
, затем git mv old-name.txt new-name.txt
, а затем зафиксируйте. git mv
переименует файл в области index/staging и переименует версию дерева.
Если git mv
был --cached
вариант как git rm
делает, вы могли бы просто git mv --cached old-name.txt new-name.txt
и затем git commit
. Первый шаг переименовал бы файл в индекс, не касаясь дерева. Но это не так: он настаивает на перезаписывании версии дерева работ и настаивает на том, чтобы старое имя должно существовать в рабочем дереве для запуска.
Одноэтапный метод для этого без прикосновения к дереву - использовать git update-index --index-info
, но это тоже немного грязно (я покажу его в любой момент). К счастью, есть одна последняя вещь, которую мы можем сделать.Я настроил ту же ситуацию, которая была ранее, переименование старого названия нового и изменению файла:
$ git status
On branch master
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: old-name.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
new-name.txt
Что мы делаем сейчас, первым, вручную поместить файл обратно под своим старым именем , а затем использовать git mv
снова переключиться на новое имя:
$ mv new-name.txt old-name.txt
$ git mv old-name.txt new-name.txt
на этот раз git mv
обновляет имя в индексе, но сохраняет оригинальное содержание как индекс SHA-1, еще ходов работы- дерево версия (новое содержание) на место в работе дерева:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: old-name.txt -> new-name.txt
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: new-name.txt
Теперь только git commit
сделать коммит с переименованием на месте, но не новое содержание.
(Обратите внимание, что это зависит от того, что не является новый файл со старым именем!)
Что об использовании git update-index
? Ну, во-первых, давайте получить вещи обратно в «изменилось в работе дерева, индекс соответствует ГОЛОВА фиксации» состояния:
$ git reset --mixed HEAD # set index=HEAD, leave work-tree alone
Теперь давайте посмотрим, что в индексе для old-name.txt
:
$ git ls-files --stage -- old-name.txt
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0 old-name.txt
Итак, что нам нужно git update-index --index-info
сделать, чтобы стереть запись для old-name.txt
но сделать иначе идентичную запись new-name.txt
:
$ (git ls-files --stage -- old-name.txt;
git ls-files --stage -- old-name.txt) |
sed -e \
'1s/^[0-9]* [0-9a-f]*/000000 0000000000000000000000000000000000000000/' \
-e '2s/old-name.txt$/new-name.txt/' |
git update-index --index-info
(примечание: Я сломал т он выше для целей публикации, это была одна строка, когда я набрал ее; в sh/bash он должен работать разбитым, как это, учитывая обратную косую черту, которую я добавил, чтобы продолжить команду sed.
Есть несколько других способов сделать это, но просто извлечение записи индекса дважды и изменение первого в удаление, а второе с новым именем показалось самым легким здесь, следовательно, команда sed
. Первая подстановка изменяет режим файла (100644, но любой режим будет преобразован во все нули) и SHA-1 (соответствует любому SHA-1, заменяется специальными всеми нулями SHA-1 git), а второй покидает режим и Только SHA-1 при замене имени.
Когда индекс обновления заканчивается, индекс записывает удаление старого пути и добавление нового пути (в том же режиме и SHA-1, как и в старом пути).
Обратите внимание, что это может сильно потерпеть неудачу, если индекс имел несвязанные записи для old-name.txt
, так как для файла могут быть другие этапы (от 1 до 3).
Пробовал 'git mv'? – vaultah
'git mv' самостоятельно не будет работать, если исходный файл уже удален или существует путь назначения. Вам нужно сделать процесс сохранения/восстановления, который вы описали ... во время которого вы можете использовать 'git mv' вместо' mv + git add'. Поскольку git не отслеживал 'new-name.txt', когда вы вносили в него изменения, так как ... это не может помочь отделить эти изменения. – hinerm