Что такое хэш git
Я не совсем понимаю, как хэши SHA-1 рассчитываются для коммитов, деревьев и BLOB-объектов. Согласно этой статье, хеши коммитов рассчитываются на основе следующих факторов:
Те же факторы влияют и на хэши деревьев и блобов?
2 ответа
Git иногда называют «файлово-адресной файловой системой». Хэши - это адреса, и они основаны на содержимом различных объектов. Итак, чтобы узнать, на чем основан хеш, нам нужно знать только содержимое различных объектов.
blob - это просто поток октетов. Ничего более. Это сродни концепции содержимого файла в файловой системе Unix.
Таким образом, хэш BLOB-объекта основан исключительно на его содержимом, а BLOB-объект не имеет метаданных.
дерево связывает имена и разрешения с другими объектами (BLOB-объектами или деревьями). Дерево - это просто список четверок (permission, type, hash, name) . Например, дерево может выглядеть так:
Обратите внимание на третью запись, которая сама является деревом.
Дерево аналогично специальному файлу каталога в файловой системе Unix.
Опять же, хэш основан на содержимом дерева, что означает имена, разрешения, типы и хэши его листьев.
commit записывает моментальный снимок дерева вместе с некоторыми метаданными и тем, как появился этот снимок. Коммит состоит из:
Хеш коммита основан на тех.
Теги не являются объектами в вышеприведенном смысле. Они не являются частью хранилища объектов и не имеют хеша. Они являются ссылками на объекты. (Примечание: любой объект может быть помечен, а не только зафиксирован, хотя это обычный случай использования.)
аннотированный тег отличается: он является частью хранилища объектов.
Аннотированный тег хранит:
Как и для всех других объектов, хэш рассчитывается на основе всех из них и ничего более.
подписанный тег похож на аннотированный тег, но добавляет криптографическую подпись.
Заметки позволяют связать произвольный коммит с произвольным объектом Git.
Хранение заметок немного сложнее. На самом деле заметка - это просто коммит (содержащий дерево, содержащее BLOB-объекты, содержащие содержимое заметки). Git создает специальную ветку для заметок, и там происходит связь между фиксацией заметки и ее «объектом-аннотируемым». Я не знаю точно, как.
Однако, поскольку заметка - это просто коммит, а связь происходит извне, хэш заметки такой же, как и любой другой коммит.
Формат хранения содержит простой заголовок. Содержимое, которое фактически сохраняется (и хэшируется), представляет собой заголовок, за которым следует октет NULL, за которым следует содержимое объекта.
Заголовок содержит тип и длину содержимого объекта, закодированного в ASCII. Таким образом, BLOB-объект, содержащий строку Hello, World , закодированную в ASCII, будет выглядеть так:
И это - это то, что хэшируется и сохраняется.
Другие типы объектов имеют более структурированный формат, поэтому объект дерева начинается с заголовка tree <length of content in octets>\0 , за которым следует строго определенное, структурированное, сериализованное представление дерева.
То же самое для коммитов и так далее.
Большинство форматов являются текстовыми форматами, основанными на простом ASCII. Например, размер кодируется не как двоичное целое число, а как десятичное целое число, причем каждая цифра представлена в виде соответствующего символа ASCII.
После вычисления хеша поток октетов, соответствующий объекту, включающему заголовок, сжимается с использованием zlib-deflate, и результирующий поток октетов сохраняется в файле на основе хеша; по умолчанию в каталоге
Приведенный выше формат хранения называется форматом свободного объекта , поскольку каждый объект хранится индивидуально. Существует более эффективный формат хранения (который также используется в качестве формата передачи по сети), называемый packfile .
Пакетные файлы важны для оптимизации скорости и хранилища, но они довольно сложны, поэтому я не буду описывать их подробно.
В первом приближении, пакетный файл состоит из всех несжатых объектов, объединенных в один файл, и второго файла, который содержит индекс того, где в пакете находится какой объект. Затем пакетный файл в целом сжимается, что обеспечивает лучшую степень сжатия, поскольку алгоритм также может находить избыточности между объектами , а не только внутри одного объекта. (Например, если у вас есть две версии блоба, которые почти идентичны . что является нормой в SCM.)
Он не использует zlib-deflate, а использует алгоритм двоичного дельта-сжатия. Он также использует определенные эвристические методы для размещения объектов в файле пакета, чтобы объекты, которые могут иметь большое сходство, были расположены близко друг к другу. (Дельта-алгоритм на самом деле не может сразу увидеть весь пакетный файл, который бы занимал слишком много памяти, скорее он работает на скользящем окне поверх пакетного файла; эвристика пытается гарантировать, что подобные объекты попадают в один и тот же окно.) Вот некоторые из этих эвристик: посмотрите на имена, которые дерево ассоциирует с BLOB-объектами, и постарайтесь, чтобы те, у которых одинаковые имена, были близко друг к другу, постарайтесь, чтобы те, которые имеют одинаковое расширение файлов, были близко друг к другу, постарайтесь, чтобы последующие ревизии были закрыты. вместе и так далее.
Свободные (то есть не упакованные) объекты просто zlib-deflated. разверните их и просто посмотрите на них, чтобы увидеть, как они структурированы. Обратите внимание, что несжатый поток октетов является точно тем, что хэшируется; объекты хранятся в сжатом виде, но хэшируются перед сжатием.
Я думаю, что лучший способ понять содержание каждого типа объектов git - это изучить их самостоятельно.
Вы можете сделать это легко с помощью команды:
Начните с ша1 коммита. Вы получите несколько деревьев, возьмете одно и всегда применяете одну и ту же команду, чтобы пройти весь путь до конца с блобом.
Каждый раз вы будете видеть содержимое, которое хранится в объекте git в базе данных.
Единственное, что вам следует знать, это то, что контенту предшествует тип объекта, длина контента, а затем он сжимается.
Git имеет репутацию запутывающего инструмента. Пользователи натыкаются на терминологию и формулировки, которые вводят в заблуждение. Это более всего проявляется в "перезаписывающих" историю командах, таких как git cherry-pick или git rebase. По моему опыту, первопричина путаницы — интерпретация коммитов как различий, которые можно перетасовать. Однако коммиты — это не различия, а снимки! Я считаю, что Git станет понятным, если поднять занавес и посмотреть, как он хранит данные репозитория. Изучив модель хранения данных мы посмотрим, как новый взгляд помогает понять команды, такие как git cherry-pick и git rebase.
Если хочется углубиться по-настоящему, читайте главу о внутренней работе Git (Git internals) книги Pro Git. Я буду работать с репозиторием git/git версии v2.29.2. Просто повторяйте команды за мной, чтобы немного попрактиковаться.
Хеши — идентификаторы объектов
Самое важное, что нужно знать о Git-объектах, — это то, что Git ссылается на каждый из них по идентификатору объекта (OID для краткости), даёт объекту уникальное имя.
Чтобы найти OID, воспользуемся командой git rev-parse. Каждый объект, по сути, — простой текстовый файл, его содержимое можно проверить командой git cat-file -p.
Мы привыкли к тому, что OID даны в виде укороченной шестнадцатеричной строки. Строка рассчитана так, чтобы только один объект в репозитории имел совпадающий с ней OID. Если запросить объект слишком коротким OID, мы увидим список соответствующих подстроке OID.
Блобы — это содержимое файлов
На нижнем уровне объектной модели блобы — содержимое файла. Чтобы обнаружить OID файла текущей ревизии, запустите git rev-parse HEAD:<path>, а затем, чтобы вывести содержимое файла — git cat-file -p <oid>.
Если я отредактирую файл README.md на моём диске, то git status предупредит, что файл недавно изменился, и хэширует его содержимое. Когда содержимое файла не совпадает с текущим OID в HEAD:README.md, git status сообщает о файле как о "модифицированном на диске". Таким образом видно, совпадает ли содержимое файла в текущей рабочей директории с ожидаемым содержимым в HEAD.
Деревья — это списки каталогов
Обратите внимание, что блобы хранят содержание файла, но не его имя. Имена берутся из представления каталогов Git — деревьев. Дерево — это упорядоченный список путей в паре с типами объектов, режимами файлов и OID для объекта по этому пути. Подкаталоги также представлены в виде деревьев, поэтому деревья могут указывать на другие деревья!
Воспользуемся диаграммами, чтобы визуализировать связи объектов между собой. Красные квадраты — наши блобы, а треугольники — деревья.
Деревья дают названия каждому подпункту и также содержат такую информацию, как разрешения на файлы в Unix, тип объекта (blob или tree) и OID каждой записи. Мы вырезаем выходные данные из 15 верхних записей, но можем использовать grep, чтобы обнаружить, что в этом дереве есть запись README.md, которая указывает на предыдущий OID блоба.
При помощи путей деревья могут указывать на блобы и другие деревья. Имейте в виду, что эти отношения идут в паре с именами путей, но мы не всегда показываем эти имена на диаграммах.
Само дерево не знает, где внутри репозитория оно находится, то есть указывать на дерево — роль объектов. Дерево, на которое ссылается <ref>^, особое — это корневое дерево. Такое обозначение основано на специальной ссылке из вашего коммита.
Коммиты — это снапшоты
Коммит — это снимок во времени. Каждый содержит указатель на своё корневое дерево, представляющее состояние рабочего каталога на момент снимка.
В коммите есть список родительских коммитов, соответствующих предыдущим снимкам. Коммит без родителей — это корневой коммит, а коммит с несколькими родителями — это коммит слияния.
Например, коммит в v2.29.2 в Git-репозитории описывает этот релиз, также он авторизован, а его автор — член команды разработки Git.
Круги на диаграммах будут представлять коммиты:
Квадраты — это блобы. Они представляют содержимое файла.
Треугольники — это деревья. Они представляют каталоги.
Круги — это коммиты. Снапшоты во времени.
Ветви — это указатели
В Git мы перемещаемся по истории и вносим изменения, в основном не обращаясь к OID. Это связано с тем, что ветви дают указатели на интересующие нас коммиты. Ветка с именем main — на самом деле ссылка в Git, она называется refs/heads/main. Файлы ссылок буквально содержат шестнадцатеричные строки, которые ссылаются на OID коммита. В процессе работы эти ссылки изменяются, указывая на другие коммиты.
Это означает, что ветки существенно отличаются от Git-объектов. Коммиты, деревья и блобы неизменяемы (иммутабельны), это означает, что вы не можете изменить их содержимое. Изменив его, вы получите другой хэш и, таким образом, новый OID со ссылкой на новый объект!
Ветки именуются по смыслу, например, trunk [ствол] или my-special-object. Ветки используются, чтобы отслеживать работу и делиться её результатами. Специальная ссылка HEAD указывает на текущую ветку. Когда коммит добавляется в HEAD, HEAD автоматически обновляется до нового коммита ветки. Создать новую ветку и обновить HEAD можно при помощи флага git -c:
Обратите внимание: когда создавалась my-branch, также был создан файл (.git/refs/heads/my-branch) с текущим OID коммита, а файл .git/HEAD был обновлён так, чтобы указывать на эту ветку. Теперь, если мы обновим HEAD, создав новые коммиты, ветка my-branch обновится так, что станет указывать на этот новый коммит!
Общая картина
Посмотрим на всю картину. Ветви указывают на коммиты, коммиты — на другие коммиты и их корневые деревья, деревья указывают на блобы и другие деревья, а блобы не указывают ни на что. Вот диаграмма со всеми объектами сразу:
Время на диаграмме отсчитывается слева направо. Стрелки между коммитом и его родителями идут справа налево. У каждого коммита одно корневое дерево. HEAD указывает здесь на ветку main, а main указывает на самый недавний коммит.
Корневое дерево у этого коммита раскинулось полностью под ним, у остальных деревьев есть указывающие на эти объекты стрелки, потому что одни и те же объекты доступны из нескольких корневых деревьев! Эти деревья ссылаются на объекты по их OID (их содержимое), поэтому снимкам не нужно несколько копий одних и тех же данных. Таким образом, объектная модель Git образует дерево хешей.
Рассматривая объектную модель таким образом, мы видим, почему коммиты — это снимки: они непосредственно ссылаются на полное представление рабочего каталога коммита!
Вычисление различий
Чтобы сравнить два коммита, сначала рассмотрите их корневые деревья, которые почти всегда отличаются друг от друга. Затем в поддеревьях выполните поиск в глубину, следуя по парам, когда пути для текущего дерева имеют разные OID.
В примере ниже корневые деревья имеют разные значения для docs, поэтому мы рекурсивно обходим их. Эти деревья имеют разные значения для M.md, таким образом, два блоба сравниваются построчно и отображается их различие. Внутри docs N.md по-прежнему тот же самый, так что пропускаем их и возвращаемся к корневому дереву. После этого корневое дерево видит, что каталоги things имеют одинаковые OID, так же как и записи README.md.
На диаграмме выше мы заметили, что дерево things не посещается никогда, а значит, не посещается ни один из его достижимых объектов. Таким образом, стоимость вычисления различий зависит от количества путей с разным содержимым.
Теперь, когда понятно, что коммиты — это снимки, можно динамически вычислять разницу между любыми двумя коммитами. Почему тогда этот факт не общеизвестен? Почему новые пользователи натыкаются на идею о том, что коммит — это различие?
Одна из моих любимых аналогий — дуализм коммитов как дуализм частиц, при котором иногда коммиты рассматриваются как снимки, а иногда — как различия. Суть дела в другом виде данных, которые не являются Git-объектами — в патчах.
Подождите, а что такое патч?
Патч — это текстовый документ, где описывается, как изменить существующую кодовую базу. Патчи — это способ самых разрозненных команд делиться кодом без коммитов в Git. Видно, как патчи перетасовываются в списке рассылки Git.
Патч содержит описание изменения и причину ценности этого изменения, сопровождаемые выводом diff. Идея такова: некий разработчик может рассматривать рассуждение как оправдание применения патча, отличающегося от копии кода нашего разработчика.
Git может преобразовать коммит в патч командой git format-patch. Затем патч может быть применён к Git-репозиторию командой git apply. В первые дни существования открытого исходного кода такой способ обмена доминировал, но большинство проектов перешли на обмен коммитами непосредственно через пул-реквесты.
Самая большая проблема с тем, чтобы делиться исправлениями, в том, что патч теряет родительскую информацию, а новый коммит имеет родителя, который одинаков с вашим HEAD. Более того, вы получаете другой коммит, даже если работаете с тем же родителем, что и раньше, из-за времени коммита, но при этом коммиттер меняется! Вот основная причина, по которой в объекте коммита Git есть разделение на "автора", и "коммиттера".
Самая большая проблема в работе с патчами заключается в том, что патч трудно применить, когда ваш рабочий каталог не совпадает с предыдущим коммитом отправителя. Потеря истории коммитов затрудняет разрешение конфликтов.
Идея перемещения патчей с места на место перешла в несколько команд Git как "перемещение коммитов". На самом же деле различие коммитов воспроизводится, создавая новые коммиты.
Если коммиты — это не различия, что делает git cherry-pick?
Команда git cherry-pick создаёт новый коммит с идентичным отличием от <oid>, родитель которого — текущий коммит. Git в сущности выполняет такие шаги:
Вычисляет разницу между <oid> коммита и его родителя.
Применяет различие к текущему HEAD.
Создаёт новый коммит, корневое дерево которого соответствует новому рабочему каталогу, а родитель созданного коммита — HEAD.
Перемещает ссылку HEAD в этот новый коммит.
После создания нового коммита вывод git log -1 -p HEAD должен совпадать с выводом git log -1 -p <oid>.
Важно понимать, что мы не "перемещали" коммит так, чтобы он был поверх нашего текущего HEAD, мы создали новый коммит, и его вывод diff совпадает со старым коммитом.
А что делает git rebase?
Команда git rebase — это способ переместить коммиты так, чтобы получить новую историю. В простой форме это на самом деле серия команд git cherry-pick, которая воспроизводит различия поверх другого, отличного коммита.
Самое главное: git rebase <target> обнаружит список коммитов, доступных из HEAD, но недоступных из <target>. С помощью команды git log --online <target>. HEAD вы можете отобразить их самостоятельно.
Затем команда rebase просто переходит в местоположению <target> и выполняет команды git cherry-pick в этом диапазоне коммитов, начиная со старых. В конце мы получили новый набор коммитов с разными OID, но схожих с первоначальным диапазоном.
Для примера рассмотрим последовательность из трёх коммитов в текущей ветке HEAD с момента разветвления target. При запуске git rebase target, чтобы определить список коммитов A, B, и C, вычисляется общая база P. Затем поверх target они выбираются cherry-pick, чтобы создать новые коммиты A', B' и C'.
Коммиты A', B' и C' — это совершенно новые коммиты с общим доступом к большому количеству информации через A, B и C, но они представляют собой отдельные новые объекты. На самом деле старые коммиты существуют в вашем репозитории до тех пор, пока не начнётся сбор мусора.
С помощью команды git range-diff мы даже можем посмотреть на различие двух диапазонов коммитов! Я использую несколько примеров коммитов в репозитории Git, чтобы сделать rebase на тег v2.29.2, а затем слегка изменю описание коммита.
Если пройти вдоль дерева, вы увидите, что история коммитов всё ещё существует у обоих наборов коммитов. Новые коммиты имеют тег v2.29.2 — в истории это третий коммит, тогда как старые имеют тег v2.28.0 — болеее ранний, а в истории он также третий.
Если коммиты – не отличия, тогда как Git отслеживает переименования?
Внимательно посмотрев на объектную модель, вы заметите, что Git никогда не отслеживает изменения между коммитами в сохранённых объектных данных. Можно задаться вопросом: "Откуда Git знает, что произошло переименование?"
Git не отслеживает переименования. В нём нет структуры данных, которая хранила бы запись о том, что между коммитом и его родителем имело место переименование.
Вместо этого Git пытается обнаружить переименования во время динамического вычисления различий. Есть два этапа обнаружения переименований: именно переименования и редактирования.
После первого вычисления различий Git исследует внутренние различия, чтобы обнаружить, какие пути добавлены или удалены. Естественно, что перемещение файла из одного места в другое будет выглядеть как удаление из одного места и добавление в другое. Git попытается сопоставить эти действия, чтобы создать набор предполагаемых переименований.
На первом этапе этого алгоритма сопоставления рассматриваются OID добавленных и удалённых путей и проверяется их точное соответствие. Такие точные совпадения соединяются в пары.
Вторая стадия — дорогая часть вычислений: как обнаружить файлы, которые были переименованы и отредактированы? Посмотреть каждый добавленный файл и сравните этот файл с каждым удалённым, чтобы вычислить показатель схожести в процентах к общему количеству строк. По умолчанию что-либо, что превышает 50 % общих строк, засчитывается как потенциальное редактирование с переименованием. Алгоритм сравнивает эти пары до момента, пока не найдёт максимальное совпадение.
Вы заметили проблему? Этот алгоритм прогоняет A * D различий, где A — количество добавлений и D — количество удалений, то есть у него квадратичная сложность! Чтобы избежать слишком долгих вычислений по переименованию, Git пропустит часть с обнаружением редактирований с переименованием, если A + D больше внутреннего лимита. Ограничение можно изменить настройкой опции diff.renameLimit в конфигурации. Вы также можете полностью отказаться от алгоритма, просто отключив diff.renames.
Я воспользовался знаниями о процессе обнаружения переименований в своих собственных проектах. Например, форкнул VFS for Git, создал проект Scalar и хотел повторно использовать большое количество кода, но при этом существенно изменить структуру файла. Хотелось иметь возможность следить за историей версий в VFS for Git, поэтому рефакторинг состоял из двух этапов:
Эти два шага позволили мне быстро выполнить git log --follow -- <path>, чтобы посмотреть историю переименовывания.
Я сократил вывод: два этих последних коммита на самом деле не имеют пути, соответствующего Scalar/CommandLine/ScalarVerb.cs, вместо этого отслеживая предыдущий путь GVSF/GVFS/CommandLine/GVFSVerb.cs, потому что Git распознал точное переименование содержимого из коммита fb3a2a36 [RENAME] Rename all files.
Не обманывайтесь больше
Теперь вы знаете, что коммиты — это снапшоты, а не различия! Понимание этого поможет вам ориентироваться в работе с Git.
И теперь мы вооружены глубокими знаниями объектной модели Git. Не важно, какая у вас специализация, frontend, backend, или вовсе fullstack — вы можете использовать эти знания, чтобы развить свои навыки работы с командами Git'а или принять решение о рабочих процессах в вашей команде. А к нам можете приходить за более фундаментальными знаниями, чтобы иметь возможность повысить свою ценность как специалиста или вовсе сменить сферу.
Краткое содержание урока, основные инструкции для командной строки, полезные ссылки и советы.
Для информации
Урок частично повторяет содержание предыдущего. Но в отличие от прошлого историю коммитов мы рассмотрим намного подробнее.
История коммитов
Сохранение истории изменений или история коммитов - одна из самых важных частей git. В истории сохраняются все коммиты, по которым можно посмотреть автора коммита, commit message, дату коммита и его хэш. А также можно увидеть измененные файлы и изменения в каждом файле. То есть git хранит буквально все, от самого начала проекта.
Команда git log
За просмотр истории коммитов отвечает команда git log. В сочетании с различными параметрами эта команда выводит историю по-разному. Есть много различных вариантов и комбинаций параметров, посмотрим некоторые из них
git log, просмотр истории по умолчанию
Показывает все коммиты от новых к старым. Для каждого коммита выводится
git log -p, расширенный вывод истории
Выводит то же, что и git log, но еще и с изменениями в файлах
git log --oneline, короткая запись
Вывод коммитов в одну строку. Показывает только хэш коммита и commit message
git log --stat --graph, история в виде дерева
Выводит коммиты в виде дерева, в командной строке псевдографикой. Плюс выводит список измененных файлов. К дереву коммитов мы вернемся, когда будем работать с ветками.
Сортировка и фильтрация истории
Есть множество команд, которые позволяют сортировать и фильтровать историю коммитов в командной строке. В том числе в сочетании с линуксовыми командами. Рассмотрим некоторые из них
Поиск по коммитам
Команда grep - мощный инструмент, который помогает работать в том числе и с git. Например, искать по коммитам
Коммиты, затронувшие один файл
Поиск по автору
В опции --author можно указать имя или email, необязательно целиком, можно только часть.
Поиск по диапазону дат
Опции --after и --before задают начальную и конечную даты коммитов
Комбинация команд и опций
Команды и опции git можно комбинировать и дополнять их линуксовыми командами
Какие еще есть варианты
Мы рассмотрели базовые примеры, но в документации по git log есть много различных опций. Все их рассматривать нет смысла, при необходимости изучайте документацию.
Просмотр отдельного коммита, git show
Чтобы просмотреть отдельный коммит, нужно узнать его хэш. Хэш коммита выводится в любой команде git log, с параметрами или без. Например,
Смотрим второй коммит
Выводится подробная информация о коммите:
- хэш
- автор
- дата
- commit message
- список измененных файлов
- изменения в каждом файле
Короткий хэш коммита
Хэш коммита 40-символьный, но можно использовать короткую запись - первые 7 символов хэша. Команда git log --oneline выводит именно короткий хэш. Для других операций с коммитами достаточно использовать первые 4 символа. Например, 3 команды ниже покажут содержимое одного и того же коммита
История коммитов в PhpStorm
В окне Local Changes, на вкладке Log показывается вся история коммитов, в левой половине вкладки. В списке коммитов показываются их commit message, автор и дата. Клик на нужный коммит откроет в правой части вкладки список измененных файлов. Клик на нужном файле и Ctrl/Cmd+D покажет все изменения в этом файле точно так же, как и git diff.
В тексте объяснить работу с историей в PhpStorm сложно, смотрите видеоурок.
Переключение на старый коммит, зачем это нужно
Нужно это обычно в двух случаях:
1. При неудачном деплое, когда вскрылась критичная бага. Если бага сложная и пофиксить ее быстро не удается, можно откатиться на рабочий коммит, задеплоить рабочую версию и уже потом чинить багу.
2. При отладке. Когда в код закралась бага и мы постепенно продвигаемся "назад в прошлое" и ищем, в какой момент что-то сломалось
Как переключиться на коммит в терминале
Первое - узнать хэш нужного коммита. Например, имеем такую историю
Хотим переключиться на предпоследний коммит. Коммиты идут в порядке убывания, поэтому нам нужен второй сверху - 26812f9. Переключаемся на него командой
Все, вернулись в прошлое. Проверим историю, теперь коммит, на который мы переключились - последний
Уйдем еще дальше, переключимся на первый коммит. Так как коммиты упорядочиваются по убыванию даты, то первый коммит - это последний в списке - 0b90433 Initial commit
Чтобы вернуться обрано, в исходное состояние, нужно набрать
master - это ветка, в которой мы работаем по умолчанию. О ветках поговорим через пару уроков
Как переключаться между коммитами в PhpStrom
Вкладка Log, правый клик на нужном коммите и Checkout Revision. Все. История коммитов будет видна по-прежнему вся, но напротив текущего коммита будет стоять значок HEAD с символом "!"
Как вернуться обратно? В правом нижем угле PhpStorm есть пункт git: , кликаем на него, выбираем Local Branches - master - checkout. Значок "!" пропадет - мы вернулись в исходное состояние
Что могу посоветовать
- как и git diff, историю коммитов git log удобнее смотреть в PhpStorm
- в команде git log есть множество вариантов сортировки и фильтрации
- сочетание git log с простыми линуксовыми командами дает хороший эффект. Обычный grep - очень хороший помощник
- PhpStorm предоставляет удобные возможности по фильтрации коммитов. Можно искать коммиты по commit message, по автору, дате и по папкам, в которых происходили изменения
- перемещайтесь по истории осторожно, не забывайте возвращаться в исходное состояние
На этом все. В следующем уроке мы поговорим о взаимодействии с сервером и познакомимся с командами git push и git pull.
Система управления исходным кодом Git, как известно, построена на алгоритме хеширования SHA ‑ 1, который с годами становится все более слабой основой. SHA ‑ 1 теперь считается сломанным, и, несмотря на то, что он еще не настолько сломан, чтобы его можно было использовать для взлома репозиториев Git, пользователи все больше беспокоятся о его защищенности. Хорошей новостью является то, что работа по выводу Git за пределы SHA ‑ 1 уже ведется и постепенно начинает приносить плоды; есть версия кода, которую можно посмотреть сейчас.
Git часто описывается как файло-адресная файловая система, где вы можете найти объект, если знаете его контент. Это может показаться не особенно полезным, но есть несколько способов «узнать» этот контент. В частности, вы можете заменить криптографический хеш для самого контента; с этим хэшем легче работать и он обладает некоторыми другими полезными свойствами.
Git хранит несколько типов объектов, используя хеш-коды SHA ‑ 1 для их идентификации. Так, например, хэш SHA ‑ 1
. Концептуально, по крайней мере, Git будет хранить эту версию в файле, используя этот хэш в качестве имени; ранние версии Git действительно делали это. Если кто-то внес изменения в, даже просто удалив лишний пробел в конце строки, результат будет иметь совершенно другой хэш SHA ‑ 1 и будет сохранен под другим именем.
Таким образом, репозиторий Git полон объектов (часто называемых «блобами») с именами SHA ‑ 1; поскольку новый создается для каждой ревизии файла, они имеют тенденцию к увеличению. В настоящее время репозиторий ядра вашего редактора содержит 8 647 655 объектов. Но BLOB-объекты не являются единственными типами объектов, хранящихся в репозитории Git.
Отдельный файловый объект содержит определенный набор содержимого, но у него нет информации о том, где этот файл появляется в иерархии хранилища. Если
, его хеш останется прежним, поэтому его представление в базе данных объектов Git не изменится. Отслеживание того, как файлы организованы в иерархию каталогов, является задачей «древовидного» объекта. Любой данный объект дерева можно рассматривать как набор BLOB-объектов (каждый из которых, конечно, определяется своим хешем SHA ‑ 1), связанных с их расположением в дереве каталогов. Как и следовало ожидать, объект дерева имеет собственный хэш SHA ‑ 1, который используется для его хранения в хранилище.
Наконец, объект «commit» записывает состояние хранилища в определенный момент времени. В коммите содержатся некоторые метаданные (коммиттер, дата и т. Д.), А также хэш SHA ‑ 1 объекта дерева, отражающий текущее состояние репозитория. Обладая этой информацией, Git может проверить хранилище при заданном коммите, воспроизводя состояние файлов в хранилище в этот момент. Важно отметить, что коммит также содержит хэш предыдущего коммита (или несколько коммитов в случае слияния); таким образом, он записывает не только состояние хранилища, но и предыдущее состояние, что позволяет точно определить, что изменилось.
У коммитов тоже есть хэши SHA ‑ 1, и хэш предыдущего коммита (или коммитов) включается в этот расчет. Если две цепочки разработки заканчиваются одинаковым содержимым файла, полученные коммиты все равно будут иметь разные хэши. Таким образом, в отличие от некоторых других систем управления исходным кодом, Git (концептуально, по крайней мере) не записывает «дельты» от одной ревизии к другой. Таким образом, он образует своего рода блокчейн, где каждый блок содержит состояние хранилища при данном коммите.
- что-то невероятное, например, изменение, которое заменяет случайные сектора сегментами из видео Рика Эстли, скажем. Каким-то образом это изменение должно быть включено в хранилище, чтобы оно было включено в последующие операции. Но изменение в меняет свой хэш SHA ‑ 1; это, в свою очередь, изменит каждый объект дерева, содержащий злой, и каждый коммит, который включает его. Главный коммит для хранилища, безусловно, изменится, как и старые, если злоумышленник попытается сделать так, чтобы изменения произошли в далеком прошлом.
Где-то, конечно, есть какой-то разработчик, который на самом деле запоминает хэши SHA ‑ 1 и сразу заметил бы подобное изменение. Остальные из нас, вероятно, не будут, но Git будет. Распределенная природа Git означает, что существует множество копий хранилища; как только разработчик попытается вытащить поврежденный репозиторий или выдвинуть его, операция завершится ошибкой из-за несовпадения хеш-кодов между двумя репозиториями, и обнаружится повреждение.
Целостность репозитория также защищена подписанными тегами, которые включают в себя хэш для конкретного коммита и криптографическую подпись. Цепочка хэшей, ведущая к данному тегу, не может быть изменена без аннулирования самого тега. Использование подписанных тегов не является универсальным в сообществе ядра (и редко встречается во многих других проектах), но основные выпуски ядра подписываются таким образом. Когда кто-то видит подпись Линуса Торвальдса на теге, он знает, что хранилище находится в том состоянии, которое он предполагал, когда применялся тег.
К счастью, мир еще не закончился. Все еще достаточно дорого создавать какие-либо коллизии SHA ‑ 1. Создать любую новую версию floppy.c с таким же хешем будет сложно. Злоумышленник не должен был бы просто сделать это, хотя; эта новая версия должна будет содержать желаемый враждебный код, по-прежнему функционировать как работающий драйвер гибкого диска и не выглядеть как запутанная запись конкурса кода C (по крайней мере, не больше, чем она уже делает). Создание такого зверя, вероятно, все еще невозможно. Но надпись явно на стене; время, когда SHA ‑ 1 слишком слаб для Git, быстро приближается.
Переход к более сильному хешу
Еще в первые дни Git Торвальдс не беспокоился о возможности взлома SHA-1; в результате он никогда не задумывался о возможности переключения на другой хэш; SHA ‑ 1 имеет основополагающее значение для работы Git. По состоянию на 2017 год код Git был полон объявлений, таких как:
unsigned char sha1[20];
Другими словами, тип хэша был глубоко связан с кодом, и предполагалось, что хэши поместятся в 20-байтовый массив.
В то время разработчик Git Брайан М. Карлсон уже работал над отделением ядра Git от конкретного используемого хэша; действительно, он работал над этим с 2014 года. Было неясно, какой хеш может в конечном итоге заменить SHA ‑ 1, но было возможно создать абстрактный тип для хеш-объектов, которые бы скрывали эту деталь. На этом этапе эта работа завершена и объединена.
Решение о замене алгоритма хеширования было принято в 2018 году . Был рассмотрен ряд возможностей, но сообщество Git остановилось на SHA ‑ 256 в качестве хеша Git следующего поколения. Обязательство, закрепляющее этот выбор, ссылается на его относительно долгую историю, широкую поддержку и хорошие результаты. Сообщество также решило (и в основном реализовало) план перехода, который хорошо документирован ; большая часть того, что следует, беззастенчиво выписана из этого файла.
С алгоритмом хеширования, абстрагированным от основного кода Git, переход, на первый взгляд, относительно прост. Новая версия Git может быть создана с другим алгоритмом хеширования, а также с инструментом, который преобразует хранилище из старого хэша в новый. С помощью простой команды вроде:
пользователь может оставить SHA ‑ 1 (обратите внимание, что определенные параметры командной строки могут отличаться). Однако у этого плана есть только одна проблема: большинство Git-репозиториев работают не в вакууме. Такое преобразование дня флага может работать для крошечного проекта, но оно не будет работать хорошо для проекта, подобного ядру. Таким образом, Git должен иметь возможность работать с хэшами SHA ‑ 1 и SHA ‑ 256 в обозримом будущем. Это требование имеет ряд последствий, которые ощущаются во всей системе.
Для BLOB-объектов это отслеживание будет происходить через ведение набора таблиц перевода; учитывая хеш, сгенерированный одним алгоритмом, Git сможет найти соответствующий хеш из другого. Излишне говорить, что этот поиск будет успешным только для объектов, которые на самом деле находятся в хранилище. Эти таблицы перевода будут храниться в «пакетных файлах», которые содержат большинство объектов в современном Git-хранилище. Там будет отдельная таблица для «незакрепленных объектов», которые хранятся как отдельные файлы, а не как пакеты; стоимость поиска в этой таблице считается достаточно высокой, поэтому необходимо принять меры для минимизации количества незакрепленных объектов в любом данном хранилище.
Работа с другими типами объектов немного сложнее. Например, объект дерева SHA ‑ 1 должен содержать хэши SHA ‑ 1 для объектов в дереве. Поэтому, если запрашивается такой объект дерева, Git должен будет найти версию SHA ‑ 256, а затем перевести все хеши объектов, содержащиеся в нем, прежде чем возвращать его. Подобные переводы будут необходимы для коммитов. Подписанные теги будут содержать оба хэша.
С этим механизмом установки Git будут совместимы во время перехода. В конце концов, все пользователи будут обновлены до версий Git с поддержкой SHA ‑ 256, после чего владельцы репозитория смогут начать отключать возможность SHA ‑ 1 и удалять таблицы перевода. Переход к этому моменту будет завершен.
Некоторые неудобные детали
Естественно, на этом пути могут быть некоторые глюки. Одна из них - это простая проблема человеческого фактора: когда пользователь вводит хеш-значение, должно ли оно интерпретироваться как SHA ‑ 1 или SHA ‑ 256? В некоторых случаях это однозначно; Хэши SHA ‑ 1 имеют ширину 160 бит, поэтому 256-битный хэш должен быть, например, SHA ‑ 256. Но может быть и более короткий хеш, поскольку хеши могут быть (и часто) сокращенными. В переходном документе описан многоэтапный процесс, в ходе которого интерпретация значений хеш-функции будет меняться, но большинство пользователей вряд ли пройдут этот процесс.
Конечно, есть способ однозначно дать хеш-значение в новом коде Git, и они могут даже смешиваться в командной строке; этот пример взят из документа перехода:
[lCODE]git --output-format=sha1 log abac87a^..f787cac^[/ICODE]
Для пользовательского интерфейса Git это относительно просто и лаконично, но все же можно представить, что пользователи могут уставать от него относительно быстро. Очевидное решение для такого рода усталости скобок состоит в том, чтобы полностью перевести проект на SHA ‑ 256 как можно быстрее.
Однако есть еще одна проблема: в дикой природе много хэш-значений SHA ‑ 1. В настоящее время репозиторий ядра содержит более 40 000 коммитов с тегом Fixes:; каждый из них включает хэш SHA ‑ 1. Эти значения хеш-функции также можно найти в истории отслеживания ошибок, объявлениях о выпуске, раскрытии уязвимостей и многом другом. В репозитории без совместимости с SHA ‑ 1 все эти хэши станут бессмысленными. Чтобы решить эту проблему, можно представить, что разработчики Git могут в конечном итоге добавить режим, в котором переводы для старых хешей SHA ‑ 1 остаются в хранилище, но не добавляются хеши SHA ‑ 1 для новых объектов.
Текущее состояние
Большая часть работы по реализации перехода SHA ‑ 256 была проделана, но он остается в относительно нестабильном состоянии, и большая часть его даже еще не проходит активного тестирования. В середине января Карлсон опубликовал первую часть этого кода перехода, которая явно решает только часть проблемы:
Обычно считается, что ценность репозиториев только для записи относительно низкая; даже SCCS не был так ограничен. Целью Карлсона в публикации кода на данном этапе является попытка выявить какие-либо основные проблемы, которые будет сложнее изменить в ходе работы. Разработчики, которые заинтересованы в том, куда движется Git, могут захотеть поближе взглянуть на этот код; конвертировать их рабочие репозитории не рекомендуется.
Как оказалось, работа Карлсона выходит далеко за рамки того, что было выставлено на тестирование сейчас; он опубликует это, когда будет готов, но действительно любопытные люди могут увидеть это сейчас в своем репозитории GitHub . Эта работа вряд ли попадет в системы большинства пользователей Git еще какое-то время, но приятно знать, что она приближается к готовности. Разработчики Git (в частности, Карлсон) тихо работают над этим проектом в течение многих лет; мы все выиграем от этого.
Читайте также: