Як зробити так, щоб git позначив видалений та новий файл у вигляді переміщення файлу?


505

Я перемістив файл вручну, а потім змінив його. За словами Git, це новий файл та вилучений файл. Чи є якийсь спосіб змусити Git ставитися до цього як до переміщення файлу?


3
Для даного файлу old_file.txt, то git mv old_file.txt new_file.txtце еквівалентно git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl

3
Джарл: ні, це не так. Якщо в файлі також є зміни, git mvне додаватиме їх у кеш, але git addбуде. Я вважаю за краще перенести файл назад, щоб я міг використовувати git mv, а потім git add -pпереглянути свій набір змін.
безтурботний

будь ласка, замовіть
deathangel908


Git покращився за останні 8 років, якщо це лише один файл, головна відповідь stackoverflow.com/a/433142/459 нічого не зробила… але ви можете дотримуватися stackoverflow.com/a/1541072/459, щоб отримати rm / add updated до статусу mv / модифікувати.
dlamblin

Відповіді:


435

Git автоматично визначить переміщення / перейменування, якщо ваша модифікація не надто сувора. Просто git addновий файл і git rmстарий файл. git statusпотім покаже, чи виявив він перейменування.

крім того, для переміщення по каталогах вам може знадобитися:

  1. cd до початку цієї структури каталогів.
  2. Біжи git add -A .
  3. Запустіть, git statusщоб переконатися, що "новий файл" тепер перейменований файл

Якщо в статусі git все ще відображається "новий файл", а його не "перейменовано", вам потрібно дотримуватися порад Хенка Гей та зробити переміщення та зміни в двох окремих комісіях.


75
Пояснення: "не надто суворий" означає, що новий файл та старий файл> 50% "подібні" на основі деяких індексів подібності, які використовує git.
pjz

151
Щось, що мене повісило на декілька хвилин: якщо перейменовані файли та видалені файли не встановлено для здійснення, вони відображатимуться як видалення та новий файл. Після того, як ви додасте їх до індексу поетапності, він визнає його перейменовуванням.
marczych

19
Варто згадати, що, говорячи про " Git, автоматично виявить переміщення / перейменування ". Це робиться в той час, коли ви використовуєте git status, git logабо git diff, не в той час, як ви це робите git add, git mvабо git rm. Далі говорити про виявлення перейменування, що має сенс лише для поетапних файлів. Отже, git mvнаступна зміна файлу може виглядати git statusтак, ніби він вважає його як a rename, але коли ви використовуєте git stage(те саме, що git add) у файлі, стає зрозуміло, що зміна є занадто великою, щоб бути виявленою як перейменування.
Jarl

5
Справжній ключ, здається, полягає в тому, щоб додати як нові, так і старі локації в одне і те ж git add, що, як підказує @ jrhorn424, мабуть, повинно бути просто цілим репо відразу.
бріанарі

4
Це, однак, не є непогрішним, особливо якщо файл змінився і є невеликим. Чи існує метод конкретно розповісти git про переїзд?
Ребс

112

Виконайте переміщення та зміни в окремих комах.


12
Я розумію, що Git є може одночасно керувати рухами та модифікаціями. Під час програмування в Java та використання IDE перейменування класу є і модифікацією, і переміщенням. Я розумію, що Git навіть повинен мати можливість автоматично розібратися, коли є крок (з видалення та створення).
pupeno

1
Perl цього теж вимагає, і я ніколи не мав Git виявляти переміщення / перейменування.
jrockway

10
@jrockway, я мав. Відбувається легко з невеликими файлами, я думаю, вони "стають" занадто різними ", щоб означати рух".
ANeves

2
Чи є спосіб зробити це автоматизовано? У мене в файлі багато файлів, хочу спершу здійснити переміщення, а потім зміни, але це важко зробити вручну
ReDetection

5
Варто зазначити, що якщо git diffне визнає перейменування через один комітет, він не розпізнає його через два коміти. Для цього вам потрібно використовувати -Mака --find-renames. Тож якщо мотивація, яка призвела вас до цього питання, полягає в тому, щоб побачити перейменування в запиті на виклик (якщо говорити з досвіду), розділення його на два коміти не сприятиме досягненню цієї мети.
mattliu

44

Це все сприймаюча річ. Git, як правило, досить добре розпізнає рухи, тому що GIT - це трекер контенту

Все, що насправді залежить, це те, як ваш "статистик" відображає його. Єдина відмінність тут - прапор -M.

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log - стат

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

журнал довідки git

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.

76
Вибачте, якщо це здається трохи педантичним, але "Git, як правило, досить добре розпізнає рухи, тому що GIT - це трекер контенту", здається, мені не послідовник. Так, це трекер контенту, так, і, можливо, це виявляється добре для виявлення рухів, але одне твердження насправді не випливає з іншого. Просто тому, що це контент-трекер, виявлення переміщення не обов'язково добре. Насправді, контент-трекер взагалі не міг виявляти руху.
Лоранс Гонсальвес

1
@WarrenSeine, коли ви перейменовуєте каталог, SHA1 файлів у цьому каталозі не змінюється. Все, що у вас є, це новий TREE-об’єкт з тими ж SHA1. Немає причини перейменування каталогу спричиняти суттєві зміни даних.
Кент Фредрік

1
@KentFredric Ви показали, що, використовуючи -M з "git log", ми можемо перевірити, чи перейменований файл чи ні, і я спробував його та добре працює, але коли я опублікую свій код для перегляду і побачу цей файл у gerrit ( gerritcodereview.com ) там він показує, що файл нещодавно доданий і попередній був видалений. Отже, є варіант у "git commit", використовуючи який я виконую, і Герріт показує це належним чином.
Патрік

1
Ні. Я взагалі цього не показував. Я показав, що Git здатний робити вигляд, що додавання + delete є перейменуванням, оскільки вміст однаковий. Немає способу дізнатися, що сталося, і це не байдуже. Усі запам'ятовування git "додано" та "видалено". "Перейменований" ніколи не записується.
Кент Фредрік

1
Наприклад, останнім часом я багато робив "Копіювати + редагувати" на репо. Більшість способів його перегляду бачать лише "новий файл", але якщо ви перейдете git log -M1 -C1 -B1 -D --find-copies-harder, git може "виявити", що новий файл, можливо, був скопійований першим. Іноді це робиться правильно, в іншому випадку, він знаходить абсолютно не пов'язані між собою файли, які, мабуть, мають однаковий вміст.
Кент Фредрік

36

git diff -Mабо git log -Mповинні автоматично виявляти такі зміни, як перейменування з незначними змінами , доки вони є. Якщо ваші незначні зміни не є незначними, ви можете зменшити порядок подібності, наприклад

$ git log -M20 -p --stat

щоб зменшити її з 50 до 20% за замовчуванням.


13
Чи можна визначити поріг у конфігурації?
Повторне виявлення

34

Ось швидке та брудне рішення для одного чи кількох перейменованих та модифікованих файлів, які не передаються.

Скажімо, файл був названий, fooа тепер він названий bar:

  1. Перейменувати barна тимчасове ім’я:

    mv bar side
    
  2. Оформити замовлення foo:

    git checkout HEAD foo
    
  3. Перейменуйте fooна barGit:

    git mv foo bar
    
  4. Тепер перейменуйте свій тимчасовий файл назад у bar.

    mv side bar
    

Цей останній крок - це те, що повертає змінений вміст у файл.

Хоча це може працювати, якщо переміщений файл за змістом занадто відрізняється від оригінального git, він вважатиме більш ефективним вирішити, що це новий об'єкт. Дозвольте продемонструвати:

$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md

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:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

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:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README.md
    modified:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ git mv README README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   work.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.

27
Цей процес не виконує жодної мети. git не додає жодних метаданих для перейменувань, git mvпросто зручність для a git rm/ git addpair. Якщо ви вже зробили «мв бар Foo», то все , що вам потрібно зробити , це переконатися , що ви маєте , git add fooі git rm barперш , ніж зробити Ком. Це може бути виконано як одна git add -Aкоманда, або, можливо, git add foo; git commit -aпослідовність.
CB Bailey

17
Все, що я знаю, це те, що до того, як я це зробив, Гіт не визнавав це як рух. Після того, як я це зробив, Гіт визнав це рухом.

8
Це визнає це як хід. Але вона також має зміни як нестандартні зміни. Після того, як ви addзнову будете файл, git перериває переміщення / модифікацію на видалене / додане знову.
Майкл Піефель

4
Цей процес спрацює, якщо ви git commitвиконаєте після кроку 3, інакше він невірний. Крім того, @CharlesBailey вірно, ви можете так само легко виконати норму mv blah fooна кроці 3, за якою слідує фіксація і отримати ті ж результати.
DaFlame

5
"Цей процес не виконує жодної мети." - Він служить цілі, коли git заплутався і думає, що файл видалено, а інший файл додано. Це очищає плутанину Git і позначає файл як переміщений явно без необхідності робити проміжні коміти.
Данак

20

Якщо ви говорите про git statusне відображення перейменів, спробуйте git commit --dry-run -aнатомість


11

Якщо ви використовуєте TortoiseGit, важливо відзначити, що автоматичне виявлення перейменування Git відбувається під час фіксації, але той факт, що це відбудеться, не завжди відображається програмним забезпеченням заздалегідь. Я перемістив два файли в інший каталог і провів невеликі зміни. Я використовую TortoiseGit як свій інструмент фіксації, і в списку Зроблені зміни показано, що файли видаляються та додаються, а не переміщуються. Запуск статусу git з командного рядка показав схожу ситуацію. Однак після створення файлів вони виявилися як перейменовані в журнал. Отже, відповідь на ваше запитання полягає в тому, що поки ви не зробили нічого занадто драматичного, Git повинен автоматично перейменувати назву.

Редагувати: мабуть, якщо ви додасте нові файли, а потім виконаєте статус git з командного рядка, перейменування повинно з’явитися перед введенням.

Редагування 2: Крім того, у TortoiseGit додайте нові файли у діалоговому вікні фіксації, але не виконуючи їх. Потім, якщо ви зайшли в команду Show Log і подивитеся на робочий каталог, ви побачите, чи Git виявив перейменування перед тим, як зробити.

Це ж питання було порушено тут: https://tortoisegit.org/issue/1389, і він був зареєстрований як помилка, щоб виправити тут: https://tortoisegit.org/issue/1440 Виявляється, це проблема з відображенням з прихильністю TortoiseGit Діалогове вікно, а також вид існує у статусі git, якщо ви не додали нові файли.


2
Ви маєте рацію, навіть якщо TortoiseGit показує delete + add, навіть якщо статус git показує delete + add, навіть якщо git commit - сухий запуск показує delete + add, після git počin я бачу перейменування, а не видалення + додавання.
Томаш Кубес

1
Я впевнений, що автоматичне виявлення перейменування відбувається під час пошуку історії ; у коміті завжди додавання + видалення. Це також пояснює шизофренічну поведінку, яку ви описуєте. Звідси варіанти тут: stackoverflow.com/a/434078/155892
Марк Совул

10

Або ви coud спробувати відповісти на це питання тут на Amber ! Цитувати його ще раз:

По-перше, скасуйте поетапне додавання для переміщеного вручну файла:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Потім перемістіть файл за допомогою Git:

$ git mv path/to/oldfile path/to/newfile

Звичайно, якщо ви вже здійснили ручне переміщення, можливо, ви захочете перейти до редакції перед переміщенням, а потім просто git mv звідти.


7

Використовуйте git mvкоманду для переміщення файлів замість команд переміщення ОС: https://git-scm.com/docs/git-mv

Зауважте, що git mvкоманда існує лише у версіях Git 1.8.5 та новіших версій. Тож вам, можливо, доведеться оновити свій Git, щоб використовувати цю команду.


3

У мене ця проблема була нещодавно, коли переміщували (але не змінювали) деякі файли.

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

Використання git mvрозібрало проблему, але вона працює лише на окремих файлах / каталогах, і у мене було багато файлів у корені репозиторію.

Одним із способів виправити це можна було б за допомогою магії башшю / партії.

Інший спосіб - наступний

  • Переміщення файлів і git commit. Це оновлює закінчення рядків.
  • Перемістіть файли назад у їх початкове місце, тепер вони мають нові закінчення рядка та git commit --amend
  • Перемістіть файли знову і git commit --amend. Наразі закінчення рядків не змінюється, тому Git щасливий

1

Для мене це спрацювало, щоб зберегти всі зміни перед фіксацією та випустити їх знову. Це змусило git повторно проаналізувати додані / видалені файли, і він правильно позначив їх як переміщені.


0

Існує, мабуть, кращий "командний рядок" спосіб це зробити, і я знаю, що це злом, але мені ніколи не вдалося знайти хорошого рішення.

Використання TortoiseGIT: Якщо у вас є GIT-комісія, де деякі операції з переміщення файлів відображаються як завантаження додавань / видалень, а не перейменовування, хоча файли мають лише невеликі зміни, зробіть це:

  1. Перевірте, що ви зробили на місцевому рівні
  2. Перевірте міні-зміну в одній лінії в другому комітеті
  3. Перейдіть до журналу GIT in черепаховий git
  4. Виберіть два коміти, клацніть правою кнопкою миші та виберіть "об'єднати в один комітет"

Новий комітет тепер належним чином покаже перейменування файлів ..., що допоможе підтримувати належну історію файлів.


0

Коли я редагую, перейменую і переміщую файл одночасно, жодне з цих рішень не працює. Рішення полягає в тому, щоб зробити це в два коміти (відредагувати та перейменувати / перемістити окремо), а потім виконати fixupдруге виконання через, git rebase -iщоб мати його в одному коміті.


0

Як я розумію це питання: "Як змусити git розпізнавати видалення старого файлу та створення нового файлу як переміщення файлу".

Так, у робочому каталозі, коли ви видалите старий файл і вставите старий файл, git statusбуде сказано " deleted: old_file" і " Untracked files: ... new_file"

Але в індексі / рівні інсценізації після додавання та видалення файлу за допомогою git він буде розпізнаний як переміщення файлу. Для цього, припускаючи, що ви виконали видалення та створення за допомогою операційної системи, дайте наступні команди:

git add new_file
git rm old_file

Якщо вміст файлу на 50% або більше схожий, запуск git statusкоманди повинен дати вам:

renamed: old_file -> new_file

1
git statusбуде сказати , " deleted: old_file" і " Untracked files: ... new_file": Чи не з Git 2,18: stackoverflow.com/a/50573107/6309
VonC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.