Какова цель git-mv?

287

Из того, что я понимаю, Git на самом деле не нужно отслеживать операции переименования / перемещения / копирования файлов , так какова реальная цель git mv ? Страница man не является специально описательной ...

Это устарело? Это внутренняя команда, не предназначенная для использования обычными пользователями?

Маурисио Шеффер
источник

Ответы:

390
git mv oldname newname

это просто сокращение для:

mv oldname newname
git add newname
git rm oldname

то есть он обновляет индекс для старых и новых путей автоматически.

CB Bailey
источник
38
Также в нем есть несколько встроенных
защит
6
Спасибо @CharlesBailey - считает ли git файлы newNameFile и oldNameFile разными? Если да, что произойдет, если мы хотим объединить их? Скажем, мы разветвляем проект ant в ветви A и создаем Branch B, а затем mavenize проекты в B. Имена файлов совпадают, но размещаются по разным путям, поскольку структура проекта изменилась. Скажем, обе ветви росли некоторое время параллельно. В какой-то момент, если мы хотим объединить проекты, как git узнает, что это тот же файл, только что переименованный в его путь? (если "git mv" == "git add + git rm")
Rose
2
@SergeyOrshanskiy Если автоопределение пойдет не так mv oldname newname; git add newname; git rm oldname, оно также пойдет не так git mv oldname newname(см. Этот ответ ).
Ajedi32
5
Обратите внимание, что git mvэто немного отличается от того mv oldname newname; git add newname; git rm oldname, что, если вы внесли изменения в файл перед git mvего использованием, эти изменения не будут размещены, пока вы не создадите git addновый файл.
Ajedi32
2
git mv делает что-то другое, так как обрабатывает изменения в регистре имен файлов (от foo.txt до Foo.txt), тогда как эти команды выполняются по отдельности (в OSX)
greg.kindel
66

С официального GitFaq :

Git имеет команду переименования git mv, но это просто удобство. Эффект неотличим от удаления файла и добавления другого с другим именем и тем же содержимым

Адам Нофсингер
источник
8
Так вы теряете историю файлов? Я предполагал, что переименование сохранит старую историю для этого каталога ...
Уилл Хэнкок
17
Ну да и нет. Прочитайте приведенную выше официальную ссылку GitFaq о переименованиях, а затем прочитайте длинное электронное письмо Линуса Торвальдса о том, почему ему не нравится идея отслеживания файлов инструментом SCM: permalink.gmane.org/gmane.comp.version-control.git/ 217
Адам Нофсингер
3
@WillHancock Теперь я использовал git немного больше и могу ответить вам более определенно: в зависимости от вашего клиента git и его параметров, вы сможете отслеживать файл после переименования, если файл изменился внутренне достаточно мало, чтобы он считал его переименовать. Если вы слишком сильно измените файл и переименуете его, git не обнаружит его - в некотором смысле он говорит: «Нет, вы можете также считать, что это совершенно другой файл!»
Адам Нофсингер
7
@AdamNofsinger эта ссылка мертва. Вот зеркало: web.archive.org/web/20150209075907/http://…
Карл Уолш
2
Существует ли официальная ссылка (то есть более надежная, чем FAQ), в которой говорится об эквивалентности git mvи ручном подходе? Это не очевидно из git help mv.
Тв
40

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

Рассмотрим этот пример. Начиная с пустого репо,

git init
echo "First" >a
echo "Second" >b
git add *
git commit -m "initial commit"
mv a c
mv b a
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)
#
#   modified:   a
#   deleted:    b
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   c
no changes added to commit (use "git add" and/or "git commit -a")

Не удалось автоопределение :( Или сделал?

$ git add *
$ git commit -m "change"
$ git log c

commit 0c5425be1121c20cc45df04734398dfbac689c39
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:56 2013 -0400

    change

а потом

$ git log --follow c

Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:56 2013 -0400

    change

commit 50c2a4604a27be2a1f4b95399d5e0f96c3dbf70a
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:45 2013 -0400

    initial commit

Теперь попробуйте вместо этого (не забудьте удалить .gitпапку при экспериментировании):

git init
echo "First" >a
echo "Second" >b
git add *
git commit -m "initial commit"
git mv a c
git status

Все идет нормально:

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


git mv b a
git status

Теперь никто не идеален

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a
#   deleted:    b
#   new file:   c
#

В самом деле? Но конечно...

git add *
git commit -m "change"
git log c
git log --follow c

... и результат тот же, что и выше: --followпоказывает только полную историю.


Теперь будьте осторожны с переименованием, так как любой из этих вариантов может по-прежнему вызывать странные эффекты . Пример:

git init
echo "First" >a
git add a
git commit -m "initial a"
echo "Second" >b
git add b
git commit -m "initial b"

git mv a c
git commit -m "first move"
git mv b a
git commit -m "second move"

git log --follow a

commit 81b80f5690deec1864ebff294f875980216a059d
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:35:58 2013 -0400

    second move

commit f284fba9dc8455295b1abdaae9cc6ee941b66e7f
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:34:54 2013 -0400

    initial b

Сравните это с:

git init
echo "First" >a
git add a
git commit -m "initial a"
echo "Second" >b
git add b
git commit -m "initial b"

git mv a c
git mv b a
git commit -m "both moves at the same time"

git log --follow a

Результат:

commit 84bf29b01f32ea6b746857e0d8401654c4413ecd
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:37:13 2013 -0400

    both moves at the same time

commit ec0de3c5358758ffda462913f6e6294731400455
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:36:52 2013 -0400

    initial a

Упс ... Теперь история возвращается к начальной а вместо начальной b , что неверно. Поэтому, когда мы делали два хода за раз, Git запутался и не отслеживал изменения должным образом. Кстати, в моих экспериментах то же самое происходило, когда я удалял / создавал файлы вместо использования git mv. Действовать осторожно; вы были предупреждены ...

OSA
источник
5
+1 за подробное объяснение. Я искал проблемы, которые могут возникнуть в истории журналов, если файлы будут перемещены в git, ваш ответ был действительно интересным. Спасибо! Кстати, вы знаете какие-либо другие подводные камни, которые мы должны избегать при перемещении файлов в git? (или любое упоминание, на которое вы могли бы указать .... не очень
удачно поискать
1
Ну, мои примеры пессимистичны. Когда файлы пусты, гораздо сложнее правильно интерпретировать изменения. Я полагаю, что если вы просто делаете коммит после каждого переименования, у вас все будет хорошо.
Ос
27

Как говорит @Charles, git mvэто сокращение.

Реальный вопрос здесь: «Другие системы контроля версий (например, Subversion и Perforce) обрабатывают переименования файлов специально. Почему не Git?»

Линус объясняет на http://permalink.gmane.org/gmane.comp.version-control.git/217 с характерным тактом:

Пожалуйста, прекратите это дерьмо "отслеживать файлы". Git отслеживает именно то , что имеет значение, а именно «коллекции файлов». Ничто другое не имеет отношения, и даже мысль о том, что это важно, только ограничивает ваше мировоззрение. Обратите внимание, что понятие «аннотировать» в CVS всегда неизбежно ограничивает то, как люди его используют. Я думаю, что это абсолютно бесполезный кусок дерьма, и я описал кое-что, что, на мой взгляд, в миллион раз полезнее, и все вышло именно потому, что я не ограничиваю свое мышление неправильной моделью мира.

Полковник паника
источник
9

Есть еще одно использование, которое я git mvне упомянул выше.

С момента обнаружения git add -p(режим патча git add; см. Http://git-scm.com/docs/git-add ) я люблю использовать его для просмотра изменений при добавлении их в индекс. Таким образом, мой рабочий процесс становится (1) работой над кодом, (2) просмотром и добавлением в индекс, (3) фиксацией.

Как git mvвписывается? Если файл перемещается напрямую, то с помощью git rmи git addвсе изменения добавляются в индекс, а использование git diff для просмотра изменений не так просто (перед фиксацией). Использование git mv, однако, добавляет новый путь к индексу, но не вносит изменения в файл, что позволяет git diffи git add -pработать как обычно.

dhardy
источник
5

Есть нишевый случай, который git mvостается очень полезным: когда вы хотите изменить регистр имени файла в файловой системе без учета регистра. APFS (mac) и NTFS (windows) по умолчанию нечувствительны к регистру (но сохраняют регистр).

greg.kindel упоминает об этом в комментарии к ответу CB Bailey.

Предположим, вы работаете на Mac и у вас есть файл, Mytest.txtуправляемый git. Вы хотите изменить имя файла на MyTest.txt.

Вы можете попробовать:

$ mv Mytest.txt MyTest.txt
overwrite MyTest.txt? (y/n [n]) y
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

О, Боже. Git не признает, что в файле произошли какие-либо изменения.

Вы можете обойти это, переименовав файл полностью, а затем переименовав его обратно:

$ mv Mytest.txt temp.txt
$ git rm Mytest.txt
rm 'Mytest.txt'
$ mv temp.txt MyTest.txt
$ git add MyTest.txt 
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    Mytest.txt -> MyTest.txt

Ура!

Или вы можете сэкономить на этом, используя git mv:

$ git mv Mytest.txt MyTest.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    Mytest.txt -> MyTest.txt
Duncan
источник