2015-04-17 1 views
10

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

Так что у меня это:

Changes not staged for commit: 

     deleted: old-name.txt 

Untracked files: 

     new-name.txt 

но хотят либо это:

Changes to be committed: 

     new file: new-name.txt 
     deleted: old-name.txt 

Changes not staged for commit: 

     modified: new-name.txt 

или это:

Changes to be committed: 

     renamed: old-name.txt -> new-name.txt 

Changes not staged for commit: 

     modified: new-name.txt 

(где мера подобия должно быть 100%).

Я не могу придумать простой способ сделать это.

Существует ли синтаксис для получения содержимого конкретной ревизии конкретного файла и добавления его в область хранения git по указанному пути?

Функция удаления части, с git rm, отлично:

$ git rm old-name.txt 

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

Спасибо!

+4

Пробовал 'git mv'? – vaultah

+1

'git mv' самостоятельно не будет работать, если исходный файл уже удален или существует путь назначения. Вам нужно сделать процесс сохранения/восстановления, который вы описали ... во время которого вы можете использовать 'git mv' вместо' mv + git add'. Поскольку git не отслеживал 'new-name.txt', когда вы вносили в него изменения, так как ... это не может помочь отделить эти изменения. – hinerm

ответ

12

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).

+1

Мой любимый вариант вашего списка - переименовать обратно в старый, а затем использовать 'git mv'. –

+1

Кроме того, может быть стоит отметить, что вы можете сделать git stash для возврата в исходное состояние, а затем выполнить 'git mv' для переименования. После фиксации вы можете использовать 'git stash -pop', с возможным конфликтом слияния, чтобы вернуться туда, где вы были. –

+1

@BrianJ: да, это (mv, then git mv), безусловно, самое легкое здесь. Это может быть сложнее, если вы создали новый файл со старым именем, поскольку в этом случае нет свободного места для перемещения 15-головоломок :-) – torek

4

@torek дал очень четкий и полный ответ. Там очень много полезной детали; это хорошо стоит прочитать.

Но, ради тех, кто в спешке, суть очень простое решение данной было это:

Что мы делаем сейчас, во-первых, вручную поместить файл обратно под своим старым именем , а затем использовать GIT мв снова переключиться на новое имя:

$ mv new-name.txt old-name.txt 
$ git mv old-name.txt new-name.txt 

(. Это было просто mv назад, что мне не хватает, чтобы сделать возможным git mv)

Пожалуйста, поддержите @ torek's ответ если вы считаете это полезным.