Я посвящаю значительную часть полной главы моей (в настоящее время довольно умирающей) книги (см. Главу 3, которая находится в лучшей форме, чем более поздние главы) проблеме кодирования символов, потому что этоисторический беспорядок.Здесь стоит упомянуть, однако, что часть предпосылки этого вопроса - то, что Git каким-то образом поддерживает UTF-7 и UTF-32 - неверна: UTF-7 - это стандарт, который никогда даже не возник и, вероятно, никогда не должен использоваться вообще (так, естественно, более старые версии Internet Explorer делают, и это приводит к проблеме безопасности, упомянутой на связанной странице Википедии).
Тем не менее, давайте сначала отделим кодировку символов из кодовые страницы .(См. Также раздел сносок ниже.) Основная проблема здесь заключается в том, что компьютеры - ну, в любом случае, современные - работают с серией 8-битных байтов , с каждым байтомпредставляет целое число в диапазоне [0..255].В старых системах было 6, 7, 8 и даже 9-битные байты, хотя я думаю, что называть что-либо менее 8 бит «байтом» неверно.("Машины C" BBN имели 10-битные байты!) В любом случае, если один байт представляет один символ-символ, это дает нам верхний предел 256 видов символов.В те плохие старые времена ASCII этого было достаточно, поскольку в ASCII было всего 128 символов, 33 из которых были непечатными символами (управляющие коды от 0x00
до 0x1f
, плюс 0x7f
, представляющие DELили удаленный удар на бумажной ленте, записывая их здесь в шестнадцатеричном формате).
Когда нам нужно более 94 печатных символов плюс пробел (0x20
), мы - мы Я имею в виду людей, использующих компьютеры по всему миру , а не конкретно me - сказал: Хорошо, посмотрите на это, у нас есть 128 неиспользуемых кодировок, от 0x80
до 0xff
, давайтеиспользуйте некоторые из них! Таким образом, французы использовали некоторые для ç и é и так далее, и пунктуацию как «и».Чехи нуждались в одном для Z-с-Caron, ž.Русским нужно было много, для кириллицы.Грекам нужно было много и так далее.В результате верхняя половина 8-битного пространства была разбита на множество несовместимых наборов, которые люди называли кодовыми страницами .
По сути, компьютер хранит около восьми-байтное значение байта, такое как 235 десятичное число (0xEB
hex), и это зависит от чего-то другого - другой компьютерной программы или, в конечном счете, человека, смотрящего на экран, чтобы интерпретировать эти 235 как, скажем, кириллический символ «, илигреческий λ или что-то еще.Кодовая страница, если мы ее используем, говорит нам, что означает «235» : какую семантику мы должны навязать этому.
Проблема здесь в том, что существует ограничение насколько кодов символов мы можем поддержать.Если мы хотим, чтобы кириллица L (л) сосуществовала с греческой буквой L (лямбда, λ), мы не можем одновременно использовать CP-1251 и CP-1253, поэтому нам нуженлучший способ кодировать символ .Очевидным способом является прекращение использования однобайтовых значений для кодирования символов: если мы используем двухбайтовые значения, мы можем кодировать 65536 значений, от 0x0000
до 0xffff
включительно;вычтите несколько для контрольных кодов, и еще есть место для многих алфавитов.Тем не менее, мы быстро преодолели даже этот предел, поэтому мы перешли к Unicode, в котором есть место для 1114112 того, что он называет кодовые точки , каждый из которых представляет своего рода символ с некоторымсвоего рода семантическое значение.В настоящее время используется более 100 000 из них, включая Emoji, такие как ? и ?.
Кодирование Unicode в байты или слова
Здесь UTF-8, UTF-16, UTF-32,Все UCS-2 и UCS-4 входят. Это все схемы для кодирования кодовых точек Unicode - одного из этих ~ 1 миллиона значений - в байтовые потоки.Я собираюсь полностью пропустить UCS и взглянуть только на кодировки UTF-8 и UTF-16, так как они являются наиболее интересными в настоящее время.(См. Также Что такое Unicode, UTF-8, UTF-16? )
Кодировка UTF-8 является строгойghtforward: любая кодовая точка, десятичное значение которой меньше 128, кодируется как байт, содержащий это значение.Это означает, что обычные текстовые символы ASCII остаются обычными текстовыми символами ASCII.Кодовые точки от 0x0080
(128 десятичных) до 0x07ff
(2047 десятичных) кодируются в два байта, оба из которых находятся в диапазоне 128-255 и, следовательно, отличаются от однобайтового кодированного значения.Кодовые точки в диапазоне от 0x0800
до 0xffff
кодируются в три байта в том же диапазоне 128-255, а оставшиеся действительные значения кодируются в четыре таких байта. Ключевым моментом в том, что касается самого Git, является то, что ни одно закодированное значение не похоже на ASCII NUL (0x00
) или косую черту (0x2f
).
Что это за кодировка UTF-8делает, чтобы Git делал вид, что , что текстовые строки - и особенно имена файлов - являются разделенными слешем именными компонентами, концы которых, или в любом случае, отмечены байтами ASCII NUL.Это кодировка, которую Git использует в древовидных объектах, поэтому кодированные в UTF-8 древовидные объекты просто подходят, без необходимости перебирать.
Кодировка UTF-16 использует два парных байта на символ.У этого есть две проблемы для Git и путей.Во-первых, байт в паре может случайно напоминать /
, и все ASCII-значимые символы обязательно кодируются как пара байтов, где один байт равен 0x00
, что напоминает ASCII NUL.Поэтому Git должен знать: это имя пути было закодировано в UTF-16 и работать с байтовыми парами.В объекте дерева нет места для этой информации, поэтому Git понадобится новый тип объекта.Во-вторых, всякий раз, когда мы разбиваем 16-битное значение на два отдельных 8-битных байта, мы делаем это в некотором порядке: сначала я даю вам более старший байт, а затем менее значимый;или сначала я даю менее значимый байт, затем более значимый.Эта вторая проблема приводит к тому, что UTF-16 имеет байтовых меток .UTF-8 не нуждается в метке порядка байтов и достаточно, так почему бы не использовать это в деревьях?Git делает.
Это хорошо для деревьев, но у нас также есть коммиты, теги и BLOB-объекты
Git по-своему интерпретирует три из этих четырех типов объектов:
- Коммиты содержат хэш-идентификаторы.
- Деревья содержат пути, режимы файлов и хэш-идентификаторы.
- Теги содержат хэш-идентификаторы.
Тот, которыйздесь не указан blob , и по большей части Git не выполняет никакой интерпретации BLOB-объектов.
Чтобы упростить понимание коммитов, деревьев и тегов, ограничения Gitвсе три должны быть в UTF-8 по большей части.Однако Git делает разрешающим сообщение журнала в коммите или текст тега в теге, чтобы быть несколько (в основном) не интерпретируемыми.Они следуют после заголовка, который интерпретирует Git, так что даже если в этот момент есть что-то особенно хитрое или уродливое, это довольно безопасно.(Здесь есть некоторые незначительные риски, так как сигнатуры PGP, которые появляются под заголовками, do интерпретируются.) В частности, для коммитов, современный Git будет включать в интерпретируемую строку заголовка encoding в интерпретируемомsection, и Git может затем попытаться декодировать тело сообщения фиксации и перекодировать его в любую кодировку, используемую любой программой, интерпретирующей байты, которые Git выводит. 1
Те же правила могут работать для аннотированных теговых объектов.Я не уверен, есть ли в Git код, который делает это для тегов (код коммита в основном можно использовать повторно, но теги гораздо чаще имеют подписи PGP, и, вероятно, разумнее просто использовать здесь UTF-8).Поскольку деревья являются внутренними объектами, их кодировка в значительной степени невидима в любом случае - вам не нужно знать об этом (за исключением проблем, которые я указал в моей книге).
Это оставляеткапли, которые являются большой гориллой.
1 Это повторяющаяся тема в компьютерном мире: все постоянно кодируется и декодируется.Подумайте, как что-то приходит через WiFi или соединение по кабельной сети: оно было закодировано в какую-то радиоволну или подобное, а затем некоторое оборудование декодирует это в поток битов, который некоторое другое оборудование перекодирует в поток байтов.Аппаратное и / или программное обеспечение снимает заголовки, каким-то образом интерпретирует оставшееся кодирование, соответствующим образом изменяет данные и перекодирует биты и байты, чтобы иметь дело с другим уровнем аппаратного и программного обеспечения.Удивительно, что что-то когда-либо делается.
Кодировка BLOB-объектов
Git любит утверждать, что он полностью не зависит от фактических данных , хранящихся в ваших файлах, как Gitсгустки.Это даже в основном верно.Или, ну, наполовину правда.Или что-то.Пока Git хранит ваши данные, это полностью верно!Git просто хранит байты.Что эти байты означают зависит от вас.
Эта история разваливается, когда вы запускаете git diff
или git merge
, потому что алгоритмы diff и, следовательно, код слияния, линия ориентированная.Строки заканчиваются символами новой строки.(Если вы работаете в системе, в которой вместо новой строки используется CRLF, то второй символ пары CRLF равен новой строкой, поэтому здесь нет проблем - и Git в порядке с неопределенной последней строкой,хотя это вызывает некоторые незначительные биты изжоги здесь и там.) Если файл закодирован в UTF-16, многие байты, как правило, выглядят как ASCII NUL, поэтому Git просто обрабатывает его как двоичный файл.
This исправимо : Git может декодировать данные UTF-16 в UTF-8, передавать эти данные через все свои существующие алгоритмы, ориентированные на строки (которые теперь будут видеть строки, оканчивающиеся на новую строку), и затем перекодироватьданные возвращаются в UTF-16.Здесь есть куча мелких технических проблем;самое большое - это решить, что какой-то файл - это UTF-16, и если да, то какой порядковый номер (UTF-16-LE или UTF-16-BE?).Если файл имеет маркер порядка байтов, который решает проблему с порядком байтов, и UTF-16-ность может быть закодирована в .gitattributes
так же, как вы можете в настоящее время объявить файлы binary
или text
, так что все это решаемо.Это просто грязно, и никто еще не сделал эту работу.
Footnote-ish: кодовые страницы можно считать (дрянной) формой кодирования
Я упоминал выше, что мы делаем с этимЮникод предназначен для кодирования 21-битного значения кодовой точки в некотором количестве восьмибитных байтов (от 1 до 4 байтов в UTF-8, 2 байта в UTF-16 - есть ужасный маленький трюк с тем, что UTF-16 называет суррогаты , чтобы сжать 21 бит значения в 16 бит контейнера, иногда используя пары 16-битных значений, здесь).Этот прием кодирования означает, что мы можем представить все допустимые значения 21-битной кодовой точки, хотя для этого может потребоваться несколько 8-битных байтов.
Когда мы используем кодовую страницу (CP- число * 1170)*) то, что мы делаем, или, по крайней мере, может рассматриваться как отображение 256 значений - тех, которые вписываются в один 8-битный байт - в этот 21-битный кодточка пространства.Мы выбираем некоторое подмножество не более 256 таких кодовых точек и говорим: Это допустимые кодовые точки. Мы кодируем первый как, скажем, 0xa0
, второй как 0xa1
, и так далее.Мы всегда оставляем место по крайней мере для нескольких управляющих кодов - обычно всех 32 в диапазоне от 0x00
до 0x1f
- и обычно оставляем все 7-битное подмножество ASCII, как это делает сам Юникод (см. https://en.wikipedia.org/wiki/List_of_Unicode_characters),именно поэтому мы обычно начинаем с 0xa0
.
Когда кто-то пишет правильные библиотеки поддержки Unicode, кодовые страницы просто становятся таблицами перевода, используя только эту форму индексации.создание точных таблиц для всех кодовых страниц, которых очень много.
The niВ кодовых страницах главное то, что символы снова по одному байту.Плохо то, что вы выбираете свой набор символов один раз, когда говорите: Я использую эту кодовую страницу. С этого момента вы заблокированы в этом небольшом подмножестве Unicode.Если вы переключаетесь на другую кодовую страницу, некоторые или все ваши восьмибитные байтовые значения представляют различных символа.