когда git core.autocrlf настроен на ввод, что у нас действительно есть - PullRequest
2 голосов
/ 27 марта 2019

У меня есть несколько сценариев оболочки в проекте.В windows каждый раз, когда я делаю это в LF и фиксирую, git client превращает локальные файлы в CRLF: я считаю, что и локальный, и git репозиторий имеют одинаковые файлы редакции CRLF.Затем я изменил core.autocrlf для ввода и повторной фиксации, что находится в локальном, а что в git-репозитории?У меня есть этот вопрос из-за того, что я наблюдал: когда core.autocrlf не настроен:

  1. изменить сценарий CRLF windows на LF, git status показывает, что я изменил эти и только эти файлы.
  2. git add ., git выдает предупреждение, и локальные файлы снова в CRLF .Но git status показывает, что локальная ветвь обновлена ​​с удаленной ветвью.

Тогда у меня core.autocrlf, настроенный для ввода:

  1. изменить сценарий CRLF windows на LF, git status показывает, что я изменил эти и только эти файлы.
  2. git add ., без предупреждения, , а локальные файлы все еще находятся в LF .Но git status показывает, что локальная ветвь обновлена ​​с удаленной ветвью.

Тогда вопрос, в обоих случаях, локальная ветвь обновлена ​​с удаленной ветвью.ЧТО ТАКОЕ УДАЛЕННЫЙ ФИЛИАЛ?LF или CRLF

core.autocrlf не настроен:

not configured

core.autocrlf = ввод:

configured to input

1 Ответ

1 голос
/ 27 марта 2019

(Вы можете сказать, что в коммитах, но не то, как вы это делаете. Вам придется смотреть непосредственно на коммиты, используя инструменты низкого уровня. В общем, но невсегда - то, что в коммитах, только для LF.)

Вы смешиваете некоторые концепции, которые вам нужно разделить.Этими понятиями являются коммиты , для которых Git на самом деле предназначен, а также рабочее дерево и index , которые позволяют вам использовать Git make фиксирует.Я собираюсь пройтись по всем этим довольно быстро, потому что мы должны иметь много общей терминологии и понимания, прежде чем мы сможем разобраться в том, как на самом деле работают окончания строк CRLF и LF-only.

Коммиты, ветви типа master и имена для удаленного отслеживания, такие как origin/master

Помните, что Git - это все о commits Каждый коммит имеет свой уникальный хэш-идентификатор.Этот хэш-идентификатор, по сути, является истинным именем коммита.Сам коммит представляет собой постоянный и неизменный 1 снимок набора файлов вместе с некоторыми метаданными, такими как имя и адрес электронной почты того, кто сделал коммит, причину, по которой он его сделал (их сообщение журнала)и необработанный хэш-идентификатор родительского коммита коммита.

Поскольку каждый коммит записывает хэш-идентификатор своего родителя, мы можем с любого коммита работать в обратном направлении от до его родителя.Мы говорим, что этот коммит указывает на его родителя.Мы можем нарисовать эту ситуацию.Если мы позволим одной заглавной букве заменить настоящий хеш-код (поскольку реальные хеш-идентификаторы слишком велики и уродливы для того, чтобы их можно было запомнить и использовать), мы можем нарисовать небольшой простой репозиторий с тремя фиксациями, например:

A <-B <-C

Здесь коммит C - это последний коммит, который мы сделали.Он записывает хэш-идентификатор родительского коммита B, поэтому C указывает на B.Это позволяет Git использовать хеш-идентификатор, чтобы найти сам фактический коммит B, а B содержит хэш-идентификатор или указывает на коммит A.Это позволяет Git извлечь A.A - это особый случай: это самый первый коммит, поэтому у него нет родителя.Это позволяет Git перестать работать в обратном направлении от коммита к коммиту.

Обратите внимание, однако, что нам нужно где-то сохранить фактический хэш-идентификатор C.Нам не нужно сохранять хэш-идентификатор B, потому что C сохраняет его для нас, но мы должны найти C.Фактические хэш-идентификаторы кажутся случайными (хотя они и не являются), поэтому мы должны где-то записать хэш-идентификатор C.Мы могли бы записать это на бумаге или на доске, но это глупо: почему бы не Git сохранить это для нас?Так что это именно то, что мы делаем.Вот как называется ветка: это место для сохранения одного (1) хеш-идентификатора.

Когда мы сохраняем хэш-идентификатор C в имени master, мы говоримmaster указывает на C:

A <-B <-C   <-- master (HEAD)

Мы можем поделиться этими коммитами с другим Git.Наш Git и его Git всегда будут использовать одинаковые хеш-идентификаторы (см. Сноску 1), поэтому они имеют одинаковые три коммита.Но у них есть собственные названия веток. Их master равно их .На данный момент они также указывают на (общий) коммит C:

A--B--C   <-- master (HEAD) [in their Git]

Наш Git вызывает свой Git и ведет разговор.Наш Git и их Git понимают, что у нас обоих одинаковые три коммита.Затем наш Git читает их имя master и сохраняет его в нашем собственном хранилище Git , но меняет так, чтобы оно не мешало наш master:

A--B--C   <-- master (HEAD), origin/master

Теперь давайте сделаем новый коммит в нашем собственном репозитории.Новый коммит получает большой уродливый хэш-идентификатор, который уникален для нашего нового коммита;мы назовем это D.Особенность имен branch заключается в том, что когда мы делаем новый коммит в какой-то ветке, Git записывает хэш-идентификатор new commit в имя ветки, так что имя ветки автоматически указывает на новый коммит:

A--B--C   <-- origin/master
       \
        D   <-- master (HEAD)

(Tего HEAD, который я рисую, - это то, как Git знает , какое имя ветви нужно обновить Пока у нас есть только одна ветвь, она нам на самом деле не нужна, но как только у нас будет более одной ветки, она нам понадобится.)

Теперь предположим, что кто-то контролирует другую Git-репозиторий добавляет новый коммит к их master.Этот новый коммит будет иметь хеш-идентификатор, отличный от любого другого коммита, поэтому мы назовем его E. Их master теперь будут указывать на их E:

A--B--C--E   <-- master (HEAD) [in their Git]

Теперь мы заставим нашего Git вызвать их Git и получить любые коммиты, которые у них есть, которых у нас нет- что в данном случае просто коммит E - и обновление нашего origin/master, которое наш Git использует для запоминания своих master, чтобы указать E:

A--B--C--E   <-- origin/master
       \
        D   <-- master (HEAD)

Давайте сделаем еще два коммита в нашем собственном репозитории и назовем их F и G:

A--B--C--E   <-- origin/master
       \
        D--F--G   <-- master (HEAD)

Когда git status говорит вам, что ваша ветвь ahead 3, это то, чтоэто означает: у нас есть три коммита на нашем master, которых у них нет на их master (что мы помним как наш origin/master).Когда git status говорит вам, что ваша ветвь behind 1, это также означает, что у них есть один коммит на их master (наш origin/master), которого у нас нет на наш master.

Это все , что git status означает ahead или behind: что у нас есть коммиты, которых нет, илинаоборот, или оба.


В некоторых случаях коммиты могут быть забытыми , и в конечном итоге они исчезнут, и этот хэш-идентификатор больше не будет иметь никакого значения.Но пока они не исчезнут, коммит будет эффективно постоянным.Он полностью неизменный по той простой причине, что хеш-идентификатор является криптографической контрольной суммой содержимого этого коммита.Если вы попытаетесь что-то изменить - даже один бит - то получите новый, другой коммит с другим хеш-идентификатором.Первоначальный коммит остается неизменным.Таким образом, все коммиты буквально неизменны.


Индекс и дерево операций

Коммиты являются неизменяемыми.Они навсегда заморожены: снимки внутри каждого коммита никогда не могут быть изменены, ни одного бита.Они также хранятся в специальной сжатой форме Git-only, как бы сублимированной, чтобы занимать меньше места.Это хорошо для архивирования - оно позволяет вам вернуться и посмотреть, что у вас было вчера, или на прошлой неделе, или когда-либо - но это совершенно бесполезно при выполнении любой новой работы.Если вы не можете изменить какие-либо файлы, что хорошего в Git?Более того, если они все только для Git, как вы будете их использовать?

Конечно, Git позволяет вам делать новые коммиты, но чтобы делать новые коммиты, вам все равно нужно изменить некоторые файлы .Ну, это, или удалить некоторые, или добавить несколько новых, или любую их комбинацию.Так что у Git есть , чтобы иметь возможность позволить вам взять существующий коммит и повторно его гидрировать, вывести все его файлы в полезную форму, где вы сможете их увидеть и поработать с ними.

Местогде вы можете просматривать свои файлы и работать с ними - рабочее дерево .Когда вы запускаете git checkout master, вы говорите Git: Получить все файлы из коммита, на которые указывает имя master. (Это также присоединяет HEAD к имени master,чтобы Git знал, какое имя обновлять при создании новых коммитов.) Извлеченные файлы попадают в ваше рабочее дерево, где вы можете их видеть, использовать, изменять и т. д.

Git coulОстановимся здесь, и другие системы до остановятся здесь. Текущий коммит и дерево работы - это все, что вам действительно нужно. Но Git не совсем останавливается здесь. Вместо этого, между текущим commit , который доступен только для чтения и содержит файлы Git-only, предназначенные только для чтения, и рабочим деревом, Git вставляет своего рода промежуточную точку, которую Git вызывает по-разному, index , или область подготовки , или cache . Все три имени означают одно и то же. Какое имя будет использоваться, зависит от того, кто или какая часть Git выполняет вызов.

В индексе есть, по крайней мере, на начальном этапе, все файлы из коммита . Таким образом, Git эффективно копирует замороженные файлы из коммита, в индекса, прежде чем копировать их в ваше рабочее дерево. Затем он повторно обрабатывает файлы, копируя их из индекса в рабочее дерево.

Если вы изменили копию файла рабочего дерева, вы должны скопировать ее обратно в индекс, чтобы зафиксировать результат. Вы делаете это с git add, который обезвоживает (сжимает и Git-ifies) этот файл и перезаписывает предыдущую индексную копию. Когда вы позже запустите git commit, Git берет того, что находится в индексе в то время , и помещает это в новый коммит.

Опять же, это все очень важно: Git извлекает любой существующий коммит в индекс , а создает новый коммит из индекса . Git не не создает коммит из того, что находится в вашем рабочем дереве: рабочее дерево предназначено для вас , а не для Git. Переданные копии файлов находятся в специальном формате Git-only: как бы сублимированным. index копии файлов также в этом специальном формате Git-only. (Это то, что делает git commit настолько быстрым: ему не нужно лиофилизировать каждый файл; каждый файл уже лиофилизирован, готов к работе!) Рабочее дерево 1243 * копии ... ну, вот где заканчиваются строки CRLF и LF-only!

Наконец-то мы поговорим об окончаниях строк

Поскольку внутренние (зафиксированные и индексированные) файлы находятся в другом формате, Git имеет возможность вносить специальные изменения. Всякий раз, когда Git копирует файл из индекса в рабочее дерево, Git может заменить окончания строк только для LF, которые предпочитает Linux, на окончания строк CRLF, которые предпочитает Windows. Всякий раз, когда Git копирует файл из рабочего дерева в индекс, он может делать обратное. Именно так все и работает. Ничего не происходит с любым подтвержденным файлом. Ничего не может произойти с таким файлом, потому что коммиты неизменны. Но, изменив настройки преобразования, вы можете сделать то, что входит в индекс, или то, что выходит из индекса, быть или выглядеть иначе из того, что вы видите и работаете в своем рабочем дереве.

Говоря Git: Файл A.txt должен иметь окончания CRLF в рабочем дереве говорит Git изменить LF-only на CRLF на пути из индекса и CRLF в LF-only по пути из рабочего дерева в индекс. Поэтому, когда git checkout копирует файл в рабочее дерево (из индекса), LF становится CRLF, а когда git add копирует файл из рабочее дерево (в индекс), CRLF становится LF.

Вы можете сказать Git: Не изменять A.txt при копировании из индекса в рабочее дерево, но при копировании из рабочего дерева в индекс, делать заменять CRLF только LF-only . Этот режим называется input. Когда git checkout выполняет преобразование индекса -> рабочего дерева, он ничего особенного не делает, но когда git add выполняет преобразование рабочего дерева -> индекса, он заменяет CRLF только на LF.

Есть загвоздка

Есть одна большая проблема с этой техникой. Это работает, и это действительно то, как Git работает. Но Git изначально был создан для Linux, где вы никогда не хотите ничего подобного. Ваши файлы - это просто данные; Git не имеет никакого дела, меняя их; и Git был разработан, чтобы работать таким образом. Часть git status, которая говорит вам:

Changes not staged for commit

работает , сравнивая что находится в индексе и что находится в рабочем дереве. Если у вас Git суетится с окончаниями строк, эти копии не будут совпадать. Git должен притвориться , что они do совпадают, до тех пор, пока Git играет на конце строки, и это единственное действительное отличие.

Следовательно, git status сознательно ложь . Если Git из-за настроек окончания строки отличает индекс и рабочее дерево, git status попытается сказать, что индекс и рабочее дерево совпадают. Эта автоматическая ложь не работает в каждом случае. В частности, если вы измените настройки преобразования, Git может заметить, а может и не заметить. 2 Если вы измените другие вещи, в том числе некоторые данные системного времени файлов - Git будет думать, что файлы изменены.

В этом случае вы видите последний эффект. Вы каким-то образом коснулись файлов, так что Git не просто лжет и говорит, что они одинаковые. Затем вы запускаете:

git add .

Git аккуратно копирует файлы рабочего дерева обратно в индекс, выполняя преобразование CRLF-to-LF-only, если требуется. В результате получается лиофилизированная индексная копия, которая соответствует копии HEAD . Git теперь обновляет кэшированные системные данные (stat данные, как в сноске 2) в индексе, так что git status знает, как напечатать правильную ложь, или - если копии рабочего дерева действительно только LF-только сейчас - правда : что копия HEAD, индексная копия и копия файла рабочего дерева совпадают.


2 Детали зависят от внутренних деталей индекса в его аспекте cache : он сохраняет данные stat из файла в index, и если данные stat не изменились с момента последнего обновления индекса, Git предполагает, что файл не изменился по сравнению с тем, как Git его настроил.


Как вы можете увидеть, что на самом деле 1336 * в коммите?

Есть несколько способов увидеть исходные данные без изменений при любом преобразовании LF-to-CRLF. Наиболее прямым является использование git cat-file -p, которое будет pretty-print внутренней формой хранения файла (или файла с индексной фиксацией). Например:

git cat-file -p HEAD:A.txt

извлекает то, что действительно в A.txt в текущем коммите.

Обратите внимание, однако, что даже программы вашего собственного компьютера, которые транскрибируют эти данные в окно, чтобы вы могли их видеть, могут изменять данные . (Аналогичным образом в системе Linux использование vim для файла с окончаниями строк CRLF скрывает тот факт, что у него есть окончания CRLF от пользователя Unix Linux. Вы их не увидите, но они все равно будут там. когда ты снова пишешь файл!)

Вам может понадобиться специальная программа просмотра, которая намеренно не делает данные "более удобными для пользователя", но вместо этого делает их программистскими -дружественными. Например, Linux имеет hexdump -C:

$ echo foo | hexdump -C
00000000  66 6f 6f 0a                                       |foo.|
00000004

Запуск вывода git cat-file -p на внутреннем Git blob (blob - это то, как файлы Git замораживают-сушат файлы) через hexdump -C может быть полезно здесь. Какой Windows-эквивалент hexdump -C может быть, я понятия не имею.

...