Ответ 1
Git действительно не переименовывает. Все они вычисляются по принципу "после факта": git сравнивает одно коммит с другим и в момент сравнения решает, было ли переименование. Это означает, что git считает, что "переименование" изменяется динамически. Я знаю, что вы спрашиваете о том, что вы еще не сделали, но несите меня, это действительно все связано (но ответ будет долгим).
Когда вы спрашиваете git (через git show
или git log -p
или git diff HEAD^ HEAD
) "что произошло в последнем коммите", он выполняет разницу с предыдущим фиксацией (HEAD^
или HEAD~1
или фактический необработанный SHA-1 для предыдущего фиксации - любой из них сделает для его идентификации) и текущий фиксатор (HEAD
). При создании этого diff он может обнаружить, что раньше был old.txt
и больше не существует; и не было new.txt
, но теперь есть.
Эти имена файлов, которые раньше были там, но не были, а файлы, которые есть сейчас, которые не были помещены в кучу, помечены как "кандидаты на переименование". Затем для каждого имени в куче git сравнивается "старое содержимое" и "новое содержимое". Сравнение для точного соответствия супер-легко из-за способа git сводит содержимое к SHA-1; если сбой точного совпадения, git переключается на необязательный параметр "- это содержимое, по крайней мере, похожее" diff для проверки переименований. С git diff
этот необязательный шаг управляется флагом -M
. С другими командами он либо устанавливается вашими значениями git config
, либо жестко закодирован в команду.
Теперь вернемся к промежуточной области и git status
: что git хранит в области index/staging в основном "прототип следующего коммита". Когда вы git add
что-то, git сохраняет содержимое файла прямо в этой точке, вычисляя SHA-1 в этом процессе и затем сохраняя SHA-1 в индексе. Когда вы git rm
что-то, git хранит примечание в индексе, говорящее, что "это имя пути намеренно удаляется при следующем коммите".
Затем команда git status
просто выполняет diff-or really, two diffs: HEAD
vs index, для того, что будет сделано; и index vs work-tree, для того, что может быть (но еще не завершено).
В этом первом diff, git использует тот же механизм, что и всегда, для обнаружения переименований. Если в тесте HEAD
, который ушел в индекс, есть путь, а путь в индексе, который новый, а не в HEAD
, совершает, он является кандидатом на переименование. Команда git status
hardwires переименовывает обнаружение на "on" (и ограничение количества файлов до 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
? Сначала сначала вернемся к состоянию "изменено в дереве дерева, соответствует индексу соответствия HEAD":
$ 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, заменяется на git специальные все нули SHA-1), а второй покидает режим и только SHA-1 при замене имени.
Когда индекс обновления заканчивается, индекс записывает удаление старого пути и добавление нового пути (с тем же режимом и SHA-1, как и в предыдущем пути).
Обратите внимание, что это может сильно потерпеть неудачу, если индекс имел несвязанные записи для old-name.txt
, поскольку для файла могут быть другие этапы (от 1 до 3).