Обробка перейменувань файлів у 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_htmldir, які відслідковуються в 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, але ось підказка від Лінуса :

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

@Chris: Так, звичайно, я додав, mobile.cssщо я повинен був згадати. Але це сенс моєї відповіді: сторінка man говорить про це, the index is updatedколи ви користуєтесь git-mv. Дякую за status -aроз’яснення, я використав git 1.6.4
— tanascius

4
Чудова відповідь! Я стукав головою об стіну, намагаючись з'ясувати, чому git statusне виявив перейменування. Запуск git commit -a --dry-runпісля додавання моїх "нових" файлів показав перейменування та нарешті дав мені впевненість у виконанні!
— stephen.hanson

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посилання, дуже корисне, так як я шукав такий ярлик!
— PhiLho

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 та 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, невірно.

Мораль історії: Якщо ви не впевнені, чи перейменували ваш файл, опублікуйте "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, оскільки початковий файл (тобто 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
@Greg K: 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

З точки зору git,

  • немає iphone.css (він видалений -git попереджає про це-).
  • з'явився новий файл mobile.css .
  • Ці файли абсолютно не пов'язані між собою.

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

На даний момент ні "iphone.css deletion", ні 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 фіксуйте і додайте коментарі

git commit -m "rename oldfile to newfile"

Крок 3: натисніть цю зміну до віддаленого строку

git push origin #localbranch:#remotebranch

1
Будь ласка, додайте коментар, щоб це було корисно для OP
— Devrath,

Крок 2 зайвий. Після git mvцього новий файл вже знаходиться в індексі.
— Алоїз Магдал

7

Git розпізнає файл із вмісту, а не розглядає його як новий незатребуваний файл

Ось де ви помилилися.

Це тільки після додавання файлу цей 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.