У меня есть каталог с файлами как это
a.JPG
b.JPG
c.JPG
Я хотел бы выполнить что-то вроде этого
git mv a.JPG a.jpg
Я пытался использовать xargs и другие инструменты, но ничто, кажется, не работает.
Суть решения заключается в использовании инструмента/метода, который автоматизирует массовое переименование. Вы можете использовать mv в сочетании с git add или просто git mv. В любом случае вам придется предпринять дополнительные шаги, если вы используете файловую систему, не чувствительную к регистру. Поэтому, прежде чем мы займемся массовым переименованием, будет полезно немного обсудить, как обрабатывается регистр.
Некоторые системы (или комбинации система+файловая система, например, стандартный вариант файловой системы HFS+ в Mac OS X*) сохраняют регистр, но нечувствительны к регистру. В таких системах может потребоваться осторожность при переименовании, которое включает только изменение регистра имени. Обычным обходным решением является использование временного имени, отличающегося не только регистром, в качестве "моста" между двумя именами, отличающимися только регистром (например, mv foo.JPG tmp && mv tmp foo.jpg
).
* В Mac OS X можно использовать файловые системы, чувствительные к регистру (включая чувствительный к регистру вариант HFS+).
С этого момента я буду считать, что файловая система не чувствительна к регистру.
Команда mv в Mac OS X может обрабатывать переименования только с учетом регистра за один шаг. Она выдаст запрос "перезаписать?", если будет запущена с опцией -i
, и пропустит переименование, если будет задана опция -n
. Это удается только благодаря тому, что многие части Unix-подобных систем работают по умолчанию по принципу "достаточно веревки, чтобы повеситься".
Команда git mv немного более параноидально относится к ситуации. Она отказывается выполнять операцию (ошибка "место назначения существует"), если не указать опцию -f
/--force
.
# this will succeed, though it may fail/prompt if mv is aliased to use -n/-i
mv foo.JPG foo.jpg
# this will succeed
mv -f bar.JPG bar.jpg
# this will succeed but give a warning
git mv -f quux.JPG quux.jpg
Желаемая операция достаточно проста, чтобы выполнить ее с помощью небольшого сценария оболочки, но вы можете воспользоваться утилитой Perl rename (той, о которой упоминает Джордан Льюис), если вам нужно сделать что-то гораздо более сложное. Вы можете попробовать rename из пакета perl Debian, или, если вы готовы использовать CPAN, вы можете установить File::Rename, который включает в себя программу rename.
Используемый ниже -ef
не совместим с POSIX. Аналогично, хотя -e
определен в POSIX, он не совместим с чистым-Bourne. Тем не менее, оба они широко поддерживаются.
for f in *.JPG; do
ff="${f%.JPG}.jpg"
test -e "$f" || continue # possible when not using nullglob
test "$f" != "$ff" || continue # possible when using nocaseglob
if test -e "$ff" &&
! test "$f" -ef "$ff"; then # possible on a case sensitive filesystem
echo "skipping <$f>: destination <$ff> exists and is distinct" 1>&2
continue
fi
# "mv" with "git rm" and "git add"
mv -f "$f" "$ff" &&
git rm --cached "$f" &&
git add "$ff"
done
Последний раздел (mv, git rm, git add) можно заменить просто git mv:
# "git mv"
git mv -f "$f" "$ff"
Если вы очень обеспокоены тем, что переименование может не сработать в системах, нечувствительных к регистру, то можно использовать временное имя:
# temp-based "mv" with "git rm" and "git add"
t="$ff.tmp"; while test -e "$t"; do t="$t.tmp"; done
mv -n "$f" "$t" &&
mv -n "$t" "$ff" &&
git rm --cached "$f" &&
git add "$ff"
Или с помощью git mv:
# temp-based "git mv"
t="$ff.tmp"; while test -e "$t"; do t="$t.tmp"; done
git mv "$f" "$t" &&
git mv "$t" "$ff"
Здесь нужно -f
как для zmv, так и для git mv.
zsh -c 'autoload zmv && $0 $@' zmv -fp git -o 'mv -f' '(*).JPG' '$1 x.jpg'
Теперь, когда все они переименованы и обновлены в индексе Git'а, вы можете зафиксировать их.
Но смогут ли другие пользователи Git, использующие файловые системы, чувствительные к регистру, проверить их?
Если есть другие пользователи вашей истории, у них, вероятно, всё ещё будут файлы JPG
, и когда они в конце концов проверят (потомка) ваш коммит с файлами jpg
. Что произойдёт для них?
Независимо от того, что произойдёт, нет необходимости в "rename to temp, commit, rename to final, commit". git checkout не применяет коммиты последовательно при переходе между коммитами. Он действительно работает путём "слияния" индекса и рабочего дерева из HEAD с новым коммитом. Это фактически означает, что он "перепрыгивает" непосредственно на новый коммит, перетаскивая при этом не противоречащие друг другу изменения, найденные между HEAD и индексом/рабочим деревом.
Внутри Git рассматривает переименования как удаление и добавление. Я не нашёл никакой документации, описывающей поведение git checkout в отношении порядка удаления и добавления, поэтому я обратился к исходному коду. git checkout обрабатывает все удаления перед любыми обновлениями/добавлениями (cmd_checkout -> switch_branches -> merge_working_tree (-> reset_tree) -> unpack_trees -> check_updates).
Вы можете проверить это сразу после фиксации переименования:
git checkout HEAD~ # note: detached HEAD
# verify that the original names are back in place
git checkout - # back to your branch
# verify that the new names are in place again
git blame на файле, похоже, указывает на вероятную фиксацию: Make unpack-tree update removed files before any updated files, который был впервые выпущен в Git 1.5.6-rc0 (2008-06-18). Таким образом, хотя это и недокументировано(?), это поведение было реализовано специально для поддержки файловых систем, нечувствительных к регистру.
Спасибо, Линус!
Используйте стандартную утилиту Linux rename(1). После того как вы переименовали файлы, добавьте их в git.
Можно ли просто изменить регистр файла, зависит от вашей файловой системы. Даже если это работает в вашей файловой системе, вы можете создать проблемы для других людей, обновляющих файлы. Лучше всего будет переименовать их, зафиксировать, а затем переименовать обратно. Измените все на *.tmp с помощью следующего сценария bash:
for i in *.JPG; do mv $i ${i%.JPG}.tmp; done
Затем переместите их все в git. Вы можете использовать аналогичную команду, но я бы рекомендовал ознакомиться с guess-renames, который поможет с перемещением.
Затем переименуйте их все обратно в *.jpg с помощью аналогичного процесса.
Возможно, переименуйте их в * .somethingelse, а затем снова переименуйте в * .jpg