Переключение Git-репозитория с ISO-8859-1 на кодировку UTF-8 для файлов исходного кода
В эти выходные я собираюсь преобразовать большой проект Mercurial в Git с помощью быстрого экспорта. Я проверял это несколько раз, и результаты хорошие.
Мы также хотели бы изменить нашу кодировку исходного кода (много немецких комментариев / строковых литералов с Umlauts) с ISO-8859-1 на UTF-8 (все другие не-Java-файлы в репо должны оставаться как есть), и миграция Git дает нам шанс сделать это сейчас, так как каждый должен все равно снова клонировать. Тем не менее, я не нахожу хороший подход к этому.
- Я попробовал
git filter-tree --tree-filter ...
Подход из этого комментария на SO. Однако, хотя это кажется идеальным, из-за размера репозитория (около 200000 коммитов, 18000 кодовых файлов) это заняло бы гораздо больше времени, чем мои выходные. Я попытался запустить его (в сильно оптимизированной версии, где список файлов разделен на части, а подсписки преобразованы параллельно (с использованием параллельной GNU)) прямо с тома 64 ГБ tmpfs на виртуальной машине Linux с 72 ядрами, и все же это будет занять несколько дней... - В качестве альтернативы я попробовал простой подход, при котором я выполняю преобразование просто для каждой активной ветви по отдельности и фиксирую изменения. Тем не менее, результат неудовлетворительный, потому что тогда я почти всегда получаю конфликты при слияниях или фиксации перед преобразованием.
- Сейчас я снова запускаю подход 1, но не пытаюсь переписать полную историю всех веток (
--all
как<rev-list>
) но только все коммиты, достижимые из текущих активных ветвей и не достижимые некоторым прошлым коммитом, который (надеюсь) является предшественником всех текущих веток (branch-a branch-b branch-c --not old-tag-before-branch-a-b-c-forked-off
как<rev-list>
). Это все еще работает, но я боюсь, что я не могу действительно доверять результатам, поскольку это кажется очень плохой идеей. - Мы могли бы просто переключить кодировку в основной ветке с помощью обычного коммита, как в подходе 2, но снова это сделало бы исправления выбора вишни из /, чтобы справиться с катастрофой. И это привело бы к большим проблемам кодирования, потому что разработчики наверняка забудут изменить свои настройки IDE при переключении между главной и неконвертированной ветвями.
Так что сейчас я чувствую, что лучшее решение - это придерживаться ISO-8859-1.
У кого-нибудь есть идея? Кто-то упомянул, что, возможно, reposurgeon может сделать в основном подход 1, используя его transcode
работа с производительностью намного лучше, чем git filter-tree --tree-filter ...
но я понятия не имею, как это работает.
2 ответа
Фильтр дерева в git filter-branch
по сути медленный Он работает, извлекая каждый коммит в полноценное дерево во временном каталоге, позволяя вам изменять каждый файл, а затем выясняя, что вы изменили, и делая новый коммит из каждого файла, который вы оставили.
Если вы экспортируете и импортируете через fast-export / fast-import, это будет время для преобразования данных: у вас есть расширенные данные файла в памяти, но не в виде файловой системы, прежде чем записать их в экспортно-импортный трубопровод. Более того, git fast-import
сам по себе является сценарием оболочки, поэтому вставлять туда фильтрацию тривиально, и hg-fast-export
это программа на Python, поэтому вставлять туда фильтрацию тоже просто. Очевидное место было бы здесь: просто перекодировать d
,
Вы могли бы рассмотреть возможность использования git filter-branch --index-filter
- в отличие от --tree-filter
(который используется по умолчанию). Идея в том, что с --index-filter
, нет этапа проверки (т. е. рабочее дерево не заполняется (повторно) на каждой итерации).
Таким образом, вы можете написать фильтр для git filter-branch --index-filter
который бы использовал git ls-files
-что-то вроде этого:
Вызов
git ls-files --cached --stage
и итерации по каждой записи.Рассмотрим только те, которые имеют
100644
файловый режим, то есть обычные файлы.Для каждой записи запустите что-то вроде
sha1=`git show ":0:$filename" \ | iconv -f io8859-1 -t utf-8 \ | git hash-object -t blob -w --stdin` git update-index --cacheinfo "10644,$sha1,$filename" --info-only
Промыть, повторить.
Я понимаю, что альтернативный подход заключается в том, чтобы атаковать проблему под другим углом: формат потоков, создаваемых git fast-export
и потребляется git fast-import
простой текст¹ (просто направьте вывод вашего экспортера less
или другой пейджер и убедитесь сами).
Вы можете написать фильтр, используя ваш любимый PL, который будет анализировать поток, перекодировать любой data
ломти. Поток организован таким образом, что хэши SHA-1 не используются, поэтому вы можете перекодировать по ходу дела. Единственная очевидная проблема, которую я понимаю, состоит в том, что data
чанки не несут никакой информации о том, какой файл они будут представлять в результирующем коммите (если таковые имеются), поэтому, если у вас есть нетекстовые файлы в вашей истории, вам, возможно, придется либо прибегнуть к угадыванию на основе содержимого каждого большого двоичного объекта данных, либо сделать ваш процессор сложнее, запоминая сгустки, которые он видел, и решая, какие из них перекодировать после того, как он увидел commit
запись, которая присваивает имена файлов (некоторые из) этих BLOB-объектов.
¹ задокументировано в git-fast-import(1)
-бежать git help fast-import
,
У меня была точно такая же проблема, и решение основано на ответе @kostix об использовании в качестве основы --index-filter
вариант filter-branch
, но с некоторыми дополнительными улучшениями.
- Использовать
git diff --name-only --staged
для обнаружения содержимого промежуточной области - Просмотрите этот список и отфильтруйте:
git ls-files $filename
, т.е. это не удаленный файл- результат
git show ":0:$filename" | file - --brief --mime-encoding
неbinary
, т.е. это текстовый файл, и он еще не закодирован в UTF-8.
- Использовать обнаруженную кодировку mime для каждого файла
- Используйте iconv для преобразования файлов
- Определите файловый режим с помощью
git ls-files $filename --stage | cut -c 1-6
Так выглядит моя функция bash:
changeencoding() {
for filename in `git diff --name-only --staged`; do
# Only if file is present, i.e., filter deletions
if [ `git ls-files $filename` ]; then
local encoding=`git show ":0:$filename" | file - --brief --mime-encoding`
if [ "$encoding" != "binary" -a "$encoding" != "utf-8" ]; then
local sha1=`git show ":0:$filename" \
| iconv --from-code=$encoding --to-code=utf-8 \
| git hash-object -t blob -w --stdin`
local mode=`git ls-files $filename --stage | cut -c 1-6`
git update-index --cacheinfo "$mode,$sha1,$filename" --info-only
fi
fi
done
}