Переключение Git-репозитория с ISO-8859-1 на кодировку UTF-8 для файлов исходного кода

В эти выходные я собираюсь преобразовать большой проект Mercurial в Git с помощью быстрого экспорта. Я проверял это несколько раз, и результаты хорошие.

Мы также хотели бы изменить нашу кодировку исходного кода (много немецких комментариев / строковых литералов с Umlauts) с ISO-8859-1 на UTF-8 (все другие не-Java-файлы в репо должны оставаться как есть), и миграция Git дает нам шанс сделать это сейчас, так как каждый должен все равно снова клонировать. Тем не менее, я не нахожу хороший подход к этому.

  1. Я попробовал git filter-tree --tree-filter ... Подход из этого комментария на SO. Однако, хотя это кажется идеальным, из-за размера репозитория (около 200000 коммитов, 18000 кодовых файлов) это заняло бы гораздо больше времени, чем мои выходные. Я попытался запустить его (в сильно оптимизированной версии, где список файлов разделен на части, а подсписки преобразованы параллельно (с использованием параллельной GNU)) прямо с тома 64 ГБ tmpfs на виртуальной машине Linux с 72 ядрами, и все же это будет занять несколько дней...
  2. В качестве альтернативы я попробовал простой подход, при котором я выполняю преобразование просто для каждой активной ветви по отдельности и фиксирую изменения. Тем не менее, результат неудовлетворительный, потому что тогда я почти всегда получаю конфликты при слияниях или фиксации перед преобразованием.
  3. Сейчас я снова запускаю подход 1, но не пытаюсь переписать полную историю всех веток (--all как <rev-list>) но только все коммиты, достижимые из текущих активных ветвей и не достижимые некоторым прошлым коммитом, который (надеюсь) является предшественником всех текущих веток (branch-a branch-b branch-c --not old-tag-before-branch-a-b-c-forked-off как <rev-list>). Это все еще работает, но я боюсь, что я не могу действительно доверять результатам, поскольку это кажется очень плохой идеей.
  4. Мы могли бы просто переключить кодировку в основной ветке с помощью обычного коммита, как в подходе 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-что-то вроде этого:

  1. Вызов git ls-files --cached --stage и итерации по каждой записи.

    Рассмотрим только те, которые имеют 100644файловый режим, то есть обычные файлы.

  2. Для каждой записи запустите что-то вроде

    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
    
  3. Промыть, повторить.

Я понимаю, что альтернативный подход заключается в том, чтобы атаковать проблему под другим углом: формат потоков, создаваемых 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, но с некоторыми дополнительными улучшениями.

  1. Использовать git diff --name-only --staged для обнаружения содержимого промежуточной области
  2. Просмотрите этот список и отфильтруйте:
    1. git ls-files $filename, т.е. это не удаленный файл
    2. результат git show ":0:$filename" | file - --brief --mime-encoding не binary, т.е. это текстовый файл, и он еще не закодирован в UTF-8.
  3. Использовать обнаруженную кодировку mime для каждого файла
  4. Используйте iconv для преобразования файлов
  5. Определите файловый режим с помощью 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
}
Другие вопросы по тегам