Обработка переименований файлов в git

Я читал, что при переименовании файлов в 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?

14 ответов

Решение

За 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 на самом деле даже не заботится обо всем "обнаружении переименования", и любые коммиты, которые вы сделали с переименованиями, полностью независимы от эвристики, которую мы затем используем для отображения переименований.

dry-run использует реальные механизмы переименования, в то время какgit status наверное нет.

Вы должны добавить два измененных файла в индекс, прежде чем git распознает его как ход.

Единственная разница между mv old new а также git mv old new является то, что git mv также добавляет файлы в индекс.

mv old new затем git add -A работал бы тоже.

Обратите внимание, что вы не можете просто использовать git add . потому что это не добавляет удаления в индекс.

См. Разница между "git add -A" и "git add".

Для git 1.7.x у меня работали следующие команды:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

В git add не было необходимости, так как исходный файл (т.е. css/mobile.css) уже был в подтвержденных файлах ранее.

Лучше всего попробовать это для себя.

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 status и 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 status, неверно.

Мораль истории: если вы не уверены, был ли ваш файл переименован, введите "git commit --dry-run -a". Если он показывает, что файл переименован, вы можете идти.

Шаг 1: переименуйте файл из старого файла в новый файл

git mv #oldfile #newfile

Шаг 2: сделать коммит и добавить комментарии

git commit -m "rename oldfile to newfile"

Шаг 3: перенесите это изменение на удаленный сервер

git push origin #localbranch:#remotebranch

Ты должен git add css/mobile.css новый файл и git rm css/iphone.cssтак что мерзавец знает об этом. тогда он покажет тот же результат в 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.

Имейте в виду, что 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

С точки зрения мерзавца,

  • iphone.css нет (он удаляется - об этом предупреждает git).
  • появился новый файл mobile.css.
  • Эти файлы абсолютно не связаны.

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

На данный момент ни "удаление iphone.css", ни 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.

Git распознает файл по содержимому, а не видит его как новый неотслеживаемый файл

Вот где ты ошибся.

Только после добавления файла g it распознает его по содержимому.

Вы не ставили результаты своего поиска. Я полагаю, что если вы сделали переход через Finder, а затем сделали git add css/mobile.css ; git rm css/iphone.cssgit вычислит хеш нового файла и только потом поймет, что хеши файлов совпадают (и, следовательно, это переименование).

Для пользователей 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.

Я только что столкнулся с этой проблемой - если вы обновили кучу файлов и не хотите делать git mv все они, это тоже работает:

  1. Переименуйте родительский каталог с на /whatever/RenamedFile.js.
  2. инсценировать это изменение
  3. Переименуйте родительский каталог обратно в /dir/RenamedFile.js.
  4. git add -A снова будет перезагружен по сравнению с этим изменением, заставляя Git подбирать изменение имени файла.

В случаях, когда вам действительно нужно переименовать файлы вручную, например. используя скрипт для пакетного переименования группы файлов, затем с помощью git add -A . работал на меня.

Протестировано на git 2.33 в Windows 10.

  1. Переименуйте папки и файлы, которые вам нужны в Проводнике.
  2. git add .
  3. git commit -m "commit message"
  4. git push

Git обнаружит переименования. Вы можете проверить, запустив git status(после совершения)

Когда я использовал заглавные буквы в папках и файлах, я использовал файловую систему Node.js для решения этой проблемы.

Не забудьте сделать коммит перед тем, как начать. Не уверен, насколько он стабилен :)

  1. Создайте файл сценария в корне проекта.
      // File rename.js

const fs = require('fs').promises;
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const args = process.argv.slice(2);
const path = args[0];

const isCapitalized = (s) => s.charAt(0) === s.charAt(0).toUpperCase();

const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);

async function rename(p) {
  const files = await fs.readdir(p, { withFileTypes: true });
  files.forEach(async (file) => {
    const { name } = file;
    const newName = capitalize(name);
    const oldPath = `${p}/${name}`;
    const dumbPath = `${p}/dumb`;
    const newPath = `${p}/${newName}`;
    if (!isCapitalized(name) && name !== 'i18n' && name !== 'README.md') {
      // 'git mv' won't work if we only changed size of letters.
      // That's why we need to rename to dumb name first, then back to current.
      await exec(`git mv ${oldPath} ${dumbPath}`);
      await exec(`git mv ${dumbPath} ${newPath}`);
    }
    if (file.isDirectory()) {
      rename(newPath);
    }
  });
}

rename(path);

Переименовать все файлы в каталоге (или в одном файле) скрипт проще:

      // rename.js

const fs = require('fs').promises;
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const args = process.argv.slice(2);
const path = args[0];

async function rename(p) {
  const files = await fs.readdir(p, { withFileTypes: true });
  files.forEach(async (file) => {
    const { name } = file;
    const currentPath = `${p}/${name}`;
    const dumbPapth = `${p}/dumb`;
    await exec(`git mv ${currentPath} ${dumbPath}`);
    await exec(`git mv ${dumbPath} ${currentPath}`);
    if (file.isDirectory()) {
      rename(newPath);
    }
  });
}

rename(path);
  1. Запустить скрипт с аргументами
      node rename.js ./frontend/apollo/queries

Как запустить файл или команду сценария оболочки с помощью Node.js

Другие вопросы по тегам