Обробка перейменувань файлів у 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.