Самый простой способ обновить один файл из LF в CR / LF при git pull? - PullRequest
0 голосов
/ 18 марта 2020

Есть много вопросов, касающихся преобразования EOL, но я не могу найти ответ на эту конкретную ситуацию: у меня есть readme.txt в Unix окончаниях строк. Этот текстовый файл является частью репозитория, который развертывается на компьютерах пользователей и обновляется с использованием простого git pull.

. Мы поняли, что этот файл всегда должен быть в CR / LF, и поэтому хотели бы изменить его на LF. (другие файлы в порядке, как они). Обновление .gitattributes с помощью

readme.txt eol=crlf

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

git pull
git rm --cached readme.txt
git reset --hard

, то есть то, что я не могу сделать на компьютере каждого пользователя. Есть ли выход из этого? Поможет ли здесь обновление readme.txt?

1 Ответ

1 голос
/ 18 марта 2020

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

Поможет ли здесь обновление readme.txt?

Да, так и будет. (Остальная часть этого ответа - необязательное чтение, но, вероятно, это хорошая идея.)

Почему это так

Атрибут eol=crlf сообщает Git, что когда файл копируется из индекса до рабочего дерева пользователя, Git должен найти \n -концы строк только в замороженной копии и заменить их на \r\n окончания строк в рабочем дереве пользователя.

Нельзя сказать, что то, что вы заявили, является неправильным , но то, что вы заявили, не совсем верно либо , :-) На самом деле, он неполный. Чтобы действительно понять это, необходимо понять, как взаимодействуют коммиты, индекс и дерево работы пользователя.

Commits

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

. Вы можете наивно строить такую ​​вещь из архиватор, например rar или tar или zip, или любой другой, каждый раз, когда вы хотите сделать коммит, просто создав новый полный архив. Каждый такой архив будет полностью независим от каждого предыдущего архива. Это позволяет им легко вернуться позже. Недостатки в том, что они занимают много места, и их легко потерять.

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

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

Мне нравится называть этот сжатый формат только для чтения, Git, только лиофилизированным. Это дает понять, что вы на самом деле не можете использовать эти данные, пока не восстановите их в обычном повседневном формате, не "регидрировав" его. (Мгновенный файл: просто добавьте воды!)

Индекс и ваше рабочее дерево

Все подтвержденные копии каждого файла хранятся в базе данных. 1 Когда Вы извлекаете или , переключаетесь на некоторый коммит, Git копирует файлы из базы данных. Это восстанавливает их и делает их полезными.

Git может остановиться здесь с этими двумя наборами сущностей: коммитами и рабочим деревом. Коммиты доступны только для чтения, а рабочее дерево - это место, где вы выполняете работу. Вы бы построили новые коммиты из рабочего дерева. Другие системы управления версиями делают именно это ... но Git этого не делает. Вместо этого Git вставляет между текущей (или HEAD) фиксацией и копией рабочего дерева третью копию каждого файла.

Эта третья копия - которая фактически является в середине, между двумя другими, так что, возможно, это вторая копия - в лиофилизированном формате , но в отличие от копии внутри коммита, Вы можете изменить эту копию. Точнее, вы можете заменить этим. Эта средняя копия хранится в том, что Git вызывает, по-разному, index или область подготовки (или, в настоящее время редко, кеш ). 2

Индекс имеет несколько ролей - возможно, источник его нескольких имен - но его главная может быть описана как , где вы создаете следующий коммит, который вы будете сделать . Поскольку он начинает совпадать с коммитом, который вы извлекли, у него уже есть все файлы, готовые к go в новый коммит. Но предположим, что вы каким-то образом изменили файл рабочего дерева. как не имеет значения, важно только то, что вы его изменили. Этот файл рабочего дерева еще не в индексе.

Вам нужно будет запустить git add в обновленном файле рабочего дерева. Это копирует файл обратно в индекс, сжимая его и превращая в формат для сублимационной сушки. Это загружает предыдущую копию из индекса. Теперь индекс содержит обновленный файл, и индекс снова готов к go в новый коммит.

Когда вы запускаете git commit, Git собирает соответствующие метаданные (ваше имя и адрес электронной почты, журнал сообщение, текущая фиксация имеет sh ID и т. д.) и создает окончательный фиксированный снимок версии файлов, которые есть в его индексе. Поскольку эти файлы уже находятся в замороженном формате , этот процесс очень быстрый, особенно по сравнению с другими системами контроля версий, которые не имеют надоедливого «индекса».

Когда Вы извлекаете другой коммит, переходя в другую ветку или «возвращаясь во времени» к хронологическому c коммиту, Git должен обновить индекс, чтобы он соответствовал коммиту, и обновить ваше рабочее дерево, чтобы он соответствовал индексу. Это означает, что он должен копировать каждый файл из индекса, в рабочего дерева, повторяя его по пути. Точно так же, как мы только что видели, git add должен скопировать файл из рабочего дерева, в индекс, обезвоживая / сублимируя его по пути. Это имеет несколько ключевых последствий для наших crlf окончаний строк или, в более общем смысле, для smudge и clean фильтров (которые вы также настроили с помощью .gitattributes).


1 Это Git база данных объектов . Имена файлов хранятся в том, что Git называет древовидными объектами , с содержимым в объектах BLOB-объектов , все они связаны между собой Git * коммит-объектами . Это объединяет различные части в одну большую объектно-адресуемую объектную систему, которую Git представляет вам как последовательность коммитов.

2 Технически, индекс не содержит фактической копии каждый файл, а скорее режим (+x или -x, отображаемый как 100755 или 100644), имя файла (со встроенными слешами: path/to/file.ext) и blob ha sh . BLOB-объект ha sh предназначен для замороженного, сжатого содержимого файла: лиофилизированной формы данных файла. Когда данные совпадают с данными любого файла в любом существующем коммите, блоб ha sh совпадает с блоком существующего файла в существующем коммите.

Пока вы не вдаваетесь в детали индекса с использованием git update-index или git ls-files --stage, однако, вы можете просто думать об этом как о дополнительной копии, в лиофилизированном формате. Все остальное работает так же.


Фильтрация, включая окончания строк

Что если во время процесса извлечения сублимированных данных у нас было Git Заменить окончания строки только для новой строки окончаниями строки CRLF? Это часть процесса «размазывания»: взятие чистого файла, сохраненного в коммите и теперь в индексе, и «загрязнение его», чтобы поместить его в рабочее дерево как редактируемый пользователем, пригодный для использования файл .

Что если во время сжатия обычного файла до сублимированного формата мы Git заменили окончания строки CRLF окончаниями строки только для новой строки? Это часть процесса «очистки»: возьмите грязный файл, сохраненный в рабочей области пользователя, и «очистите его», чтобы поместить его в индекс, готовый к фиксации.

Это то, что eol= настройки делают . Они не могут, и может не изменять любые существующие зафиксированные файлы. Они уже находятся в коммитах и ​​заморожены на все времена.

Здесь также возникает ваша проблема.

Оптимизация

Когда вы переключаетесь с какого-либо коммита a123456... на какой-то другой коммит b789abc..., Git может :

  1. удалить каждый файл в индексе из индекса и рабочего дерева
  2. повторно Заполните весь индекс и рабочее дерево из нового коммита

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

Из-за способа Git сохранения файлов в коммитах, тем не менее, очень просто для Git, чтобы определить, является ли какой-либо файл с именем path/to/file.ext или что-то еще, что является в индексе сейчас, потому что a1234567... должен быть отличается - или полностью удалено - из-за того, что в b789abc... для path/to/file.ext.

Если файл не должен быть другим, Git просто оставляет это в покое, как в индексе , так и в рабочем дереве. Если файл действительно должен отличаться, Git не позволит вам переключиться с текущего коммита a123456... на этот другой коммит b789abc..., если только копии индекса и рабочего дерева файл "чистый", т.е. соответствует текущему коммиту. (Здесь много хитрых угловых случаев. См. Больше в Оформить другую ветку, когда в текущей ветке есть незафиксированные изменения .)

Это означает, что важно, все ли три копии - HEAD коммит, индекс и рабочее дерево - совпадают или нет. Введение фильтров и преобразований в конце строки делает слово match хитрым. Git будет просматривать сохраненные данные метки времени файловой системы, кэшированные в индексе, 3 , чтобы определить, является ли файл "чистым", в некоторых случаях.

Истинное "чистое ness "файлов зависит частично от того, какой тип преобразования EOL, если таковые имеются, вы выбрали. Однако, изменение файла .gitattributes (или смазывание и очистка фильтров) не является чем-то Git на самом деле замечает , поэтому, если вы измените настройки EOL, Git может подумать, что файл "чистый" когда это не так, или наоборот.

В вашем конкретном случае вы добавили новый параметр в .gitattributes, который говорит , когда файл копируется из индекса в рабочее дерево, измените \n до \r\n; когда файл копируется из рабочего дерева в индекс, измените \r\n на \n. Так что, если Git заметит, он проверит эти вещи ... но Git не заметит.

Когда пользователь, у которого есть существующее хранилище, при коммите H1 (для некоторого ха sh), который является, скажем, master, и этот пользователь запускает git pull его Git - я предполагаю, что пользователь - мужчина - связывается с другим Git в origin и получает новые коммиты. Это приводит к коммиту, чей ха sh равен H2 (какой-то другой ха sh), что является вершиной origin 'master. Его Git затем запускает git merge на га sh ID H2 , чтобы объединить любую работу / коммиты, которые он сделал с этой другой работой.

Предполагая, что он не сделал никакой работы поскольку H1 и H2 имеют H1 в качестве родительского коммита, его Git выполняет операцию ускоренной перемотки вперед вместо слияния, что равносильно выполнению git checkout коммита H2 , который перетаскивает имя его ветви master вперед, чтобы указать коммит H2 . Так что теперь Git использует эту оптимизацию. Файл .gitattributes имеет другой blob ha sh, и его индекс и копии рабочего дерева .gitattributes должны быть заменены. Поскольку Git считает (правильно), что они чистые, они заменяются. Однако его Git индексная копия readme.txt имеет такой же blob ha sh как новый коммит H2 . Так что его Git не касается его индекса или рабочей копии дерева readme.txt.

Результат - то, что вы видите: копия рабочего дерева по-прежнему имеет любые окончания строк, которые у нее были раньше.

Если два коммита H1 и H2 иметь различное содержимое для файла readme.txt - отметим, что это означает различное очищенное содержимое - тогда операция быстрой перемотки его Git будет видеть, что его копия readme.txt, в индексе Git и его рабочем дереве do необходимо заменить. Пока его Git думает, что они "чисты", его Git заменит их. Это означает копирование подтвержденного readme.txt в индекс, а затем копирование индексной копии в его рабочее дерево: это копирование будет подчиняться новому действию eol=crlf и заменит данные только для новой строки «чистый замороженный файл» с CRLF-окончанием данные рабочего дерева.

Если пользователь впоследствии отредактирует свое рабочее дерево readme.txt, он - или, по крайней мере, его редактор - увидит эти окончания CRLF. То, что его редактор делает с ними, зависит от его редактора. (Я заставляю своего редактора показывать их мне, а затем вычеркиваю их, потому что они мне не нравятся, и мне все равно, что вы хотите, чтобы они у меня были. :-)) Если он обновит файл и запустит git add, его git add удалит эти окончания CRLF, заменив их окончаниями только для новой строки, какими должны быть файлы; это то, что будет go в индексе, и, следовательно, то, что будет в следующем коммите.


3 Отсюда и редко используемое имя cache для индекс. В современном Git термин cache в основном относится к копии индекса в памяти, которая загружается из файла индекса и затем обрабатывается любой выполняемой командой Git.

...