Обработка переименований файлов в 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 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.css
git вычислит хеш нового файла и только потом поймет, что хеши файлов совпадают (и, следовательно, это переименование).
Для пользователей XCode: если вы переименуете свой файл в XCode, вы увидите, что значок значка изменится на добавление. Если вы делаете коммит с использованием XCode, вы фактически создаете новый файл и теряете историю.
Обойти это легко, но вы должны сделать это перед тем, как совершить использование Xcode:
- Сделайте git Status для вашей папки. Вы должны увидеть, что внесенные изменения верны:
переименован: Project/OldName.h -> Project/NewName.h переименован: Project/OldName.m -> Project/NewName.m
- сделать коммит -m 'изменение имени'
Затем вернитесь к XCode, и вы увидите, что значок изменился с A на M, и теперь он сохраняет изменения, чтобы зафиксировать изменения при использовании xcode.
Я только что столкнулся с этой проблемой - если вы обновили кучу файлов и не хотите делать
git mv
все они, это тоже работает:
- Переименуйте родительский каталог с на
/whatever/RenamedFile.js
. - инсценировать это изменение
- Переименуйте родительский каталог обратно в
/dir/RenamedFile.js
. -
git add -A
снова будет перезагружен по сравнению с этим изменением, заставляя Git подбирать изменение имени файла.
В случаях, когда вам действительно нужно переименовать файлы вручную, например. используя скрипт для пакетного переименования группы файлов, затем с помощью git add -A .
работал на меня.
Протестировано на git 2.33 в Windows 10.
- Переименуйте папки и файлы, которые вам нужны в Проводнике.
-
git add .
-
git commit -m "commit message"
-
git push
Git обнаружит переименования. Вы можете проверить, запустив
git status
(после совершения)
Когда я использовал заглавные буквы в папках и файлах, я использовал файловую систему Node.js для решения этой проблемы.
Не забудьте сделать коммит перед тем, как начать. Не уверен, насколько он стабилен :)
- Создайте файл сценария в корне проекта.
// 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);
- Запустить скрипт с аргументами
node rename.js ./frontend/apollo/queries
Как запустить файл или команду сценария оболочки с помощью Node.js