company_banner

Руководство по Git. Часть №1: все, что нужно знать про каталог .git

Автор оригинала: Pierre de Wulf
  • Перевод



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

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

Это первая часть гайда по Git из блога Pierre de Wulf в переводе команды Mail.ru Cloud Solutions

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

Наверное, единственный путь преодолеть все эти трудности — узнать чуть больше, чем просто git commit/push, понять, как именно работает Git.

Папка .git


Когда вы создаете новый репозиторий командой git init, Git создает волшебную папку, .git. В ней содержится все необходимое для работы Git. Если вы хотите убрать Git из  вашего проекта, но оставить проектные файлы на диске, просто удалите папку .git. Хотя кому такое может потребоваться?

    ├── HEAD
    ├── branches
    ├── config
    ├── description
    ├── hooks
    │ ├── pre-commit.sample
    │ ├── pre-push.sample
    │ └── ...
    ├── info
    │ └── exclude
    ├── objects
    │ ├── info
    │ └── pack
    └── refs
     ├── heads
     └── tags


Вот содержимое типовой папки .git перед вашим первым коммитом:

  1. HEAD — это мы рассмотрим позже.
  2. config — этот файл содержит настройки для вашего репозитория, здесь, например, хранится url вашего репозитория в хранилище, ваше имя, email и так далее. Каждый раз когда вы делаете git config, вы обращаетесь к этому файлу.
  3. description — используется gitweb для отображения описания репозитория.
  4. hooks — эта папка содержит скрипты, которые могут выполняться на различных этапах выполнения Git. Эти скрипты, называемые хуками, могут запускаться до/после commit/rebase/pull… Имя скрипта определяет время его выполнения. Примером хука может служить скрипт проверки стиля перед выполнением команды push в репозиторий.
  5. info — exclude — здесь описываются файлы, которые вы не хотите включать в репозиторий. Функционал этого файла такой же, как у файла .gitignore, за исключением того, что он не передается в репозиторий. На практике обычно для всех задач хватает .gitignore.

Что внутри коммита?


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

Перед изучением папки объектов уточним, что же такое коммит. Коммит — это слепок текущего состояния файлов в рабочей папке, но не только это.

По факту, когда вы коммитите изменения, Git производит всего два действия:

  1. Если файл в рабочей папке не изменялся, он просто добавляет имя сжатого файла (хеш) в снимок.
  2. Если файл в рабочей папке изменялся, он сжимает его, помещает в папку объектов и добавляет имя сжатого файла (хеш) в снимок.

Конечно, тут все описано несколько упрощенно, однако, этого достаточно для понимания происходящих процессов.

Как только снимок сделан, он также архивируется и именуется при помощи хеша, затем помещается в папку объектов.

├── 4c
│ └── f44f1e3fe4fb7f8aa42138c324f63f5ac85828 // hash
├── 86
│ └── 550c31847e518e1927f95991c949fc14efc711 // hash
├── e6
│ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 // hash
├── info // let's ignore that
└── pack // let's ignore that too


Вот как выглядит папка объектов после того, как я создал файл file_1.txt  и закоммитил его. Пожалуйста, учтите, что если хеш вашего файла начинается на «4cf44f1e…», то Git сохранит его с именем «f44f1e…» в подпапке с именем «4c». Таким образом, файлы будут разложены по 256 подпапкам и в каждой не будет слишком много файлов.

У нас, как вы видите, три хеша. Один для файла file_1.txt, второй для снимка, сделанного при коммите. Для чего же третий? Третий хеш создается потому, что коммит — тоже объект, он также архивируется и помещается в папку объектов.

Вам нужно запомнить, что коммит состоит из четырех вещей:

  1. Имя (хеш) снимка рабочей директории.
  2. Комментарий.
  3. Информация о том, кто выполнил коммит.
  4. Хеш родительского коммита.

Посмотрите сами, что произойдет, если распаковать файл коммита:

git cat-file -p 4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828

И вот, что вы увидите:

tree 86550c31847e518e1927f95991c949fc14efc711
author Pierre De Wulf 
<test[@gmail.com](mailto:pierredewulf31@gmail.com)> 1455775173 -0500
committer Pierre De Wulf 
<[test@gmail.com](mailto:pierredewulf31@gmail.com)> 1455775173 -0500
commit A

Вы видите, как и ожидалось, хеш снимка, автора и комментарий коммита. Тут важны две вещи:

  1. Хеш снимка «86550...» также является объектом и его можно увидеть в папке объектов.
  2. Так как это первый коммит, у него нет родительского коммита.

Что же в снимке в действительности?

git cat-file -p 86550c31847e518e1927f95991c949fc14efc711
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file_1.txt

Здесь мы видим объект, который находился в нашем хранилище объектов, единственный объект в нашем снимке.

Branch, tags, HEAD — это одно и то же


Теперь вы понимаете, что все в Git может быть получено через правильный хеш. Давайте теперь посмотрим на HEAD. Итак, что же там?

cat HEAD
ref: refs/heads/master

Там нет хеша, и в этом есть смысл, так как HEAD — это указатель на верхушку ветви, с которой вы работаете. Если вы посмотрите в файл refs/heads/master, то увидите:

cat refs/heads/master
4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828
 

Выглядит знакомо? Естественно, ведь это хеш первого коммита! Это показывает, что теги (tags) и  ветви (branch) — всего лишь указатели на коммит. Понимая это, вы можете удалить все теги, какие захотите, все ветви, какие захотите, а коммит, на который они указывали, останется на месте. Единственное, к нему будет сложнее получить доступ. Если вам хочется больше узнать об этом, почитайте git book.

Последнее замечание


После прочтения для вас должно стать очевидным, что все, что делает Git — архивирует вашу рабочую папку и помещает ее в папку объектов с некоторым количеством дополнительной информации. Если вы достаточно знакомы с Git, то вы полностью контролируете, какие файлы будут включены в коммит, а какие нет.

Я считаю, что коммит — это не снимок рабочей папки, а снимок файлов, которые вы хотите коммитить. И где Git хранит список файлов, которые вы хотите коммитить? Он сохраняет этот список в индексном файле. Мы не будем сильно углубляться в этот вопрос, если вам интересно, подробнее можно посмотреть здесь.

Переведено при поддержке Mail.ru Cloud Solutions.

Что еще почитать:

  1. Простые способы кэширования в GitLab CI: руководство в картинках.
  2. Как технический долг и лже-Agile убивает ваши проекты.
  3. Мой второй год в качестве независимого разработчика.

Mail.ru Group
Строим Интернет

Похожие публикации

Комментарии 64

    +3
    Возможно я что-то не так понял в статье, но я считал что git ( в отличии от например svn) хранит не файлы, а изменения сделанные в файле. А если следовать написанному в статье, то получается что он хранит копию каждого состояния файла.
      +1
      Насколько я могу судить, все таки архивированные объекты.
      githowto.com/ru/git_internals_git_directory
      Цитата — Смотрим в один из каталогов с именем из 2 букв. Вы увидите файлы с именами из 38 символов. Это файлы, содержащие объекты, хранящиеся в git. Они сжаты и закодированы, поэтому просмотр их содержимого нам мало чем поможет.
        +1
        Ну я подозреваю что в данном случае под объектом подразумевается набор изменений в файле.
          +1
          Нет это именно файлы
          git-scm.com/book/ru/v2/Git-%D0%B8%D0%B7%D0%BD%D1%83%D1%82%D1%80%D0%B8-%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B-Git
          Там подробно с примерами расписано как хранятся файлы в дереве
            +2
            Интересно. Я думал он как и mercurial хранит изменения.

            Теперь возникает вопрос: почему git победил svn?
              +2
              Потому что популярнее. Мне кажется, это единственное что решило. Хотя hg более продуманный как по внутреннему устройству, так и по интерфейсу работы. Вот забавная статья про коаны git.
                +2

                На мой взгляд потому что нет зависимости от сервера. Недавно узнал что можно клонировать и пушить не только на сервер но и в дирректорию на диске.

                  +1
                  Потому что позволяет редактировать данные задним числом, как в 1С.
                    +7

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


                    ИМХО git победил svn за счет гениальной организации и отличной и продуманной работы с ветками (до которой далеко тому же hg). Данная статья не описывает даже десятой части того насколько красиво внутри все устроено

                      +1
                      git победил svn за счет гениальной организации и отличной и продуманной работы с ветками (до которой далеко тому же hg)
                      Вот бы еще git научился безболезненно закрывать ветки не удаляя их, как hg, вообще ништяк было бы.
                        +2

                        Что в вашем понимании безболезненно закрывать?
                        git merge позволяет смерджить ветку куда либо и не удаляет ее… это равно закрыть ветку?
                        Если вы хотите избавится от возможности создавать в ветке новые комиты — создайте тег с тем же именем и удалите ветку. останется зафиксированный тег. ИМХО у вас какой то странный запрос.

                          +1
                          Что в вашем понимании безболезненно закрывать?
                          Чтобы ветка оставалась собой, просто не отображалась в списке текущих, и без всякого шума. А в идеале еще чтобы в нее нельзя было закоммитить без переоткрытия, и чтобы в логе можно было фильтровать отображение промежуточных коммитов закрытых веток.
                          создайте тег с тем же именем и удалите ветку. останется зафиксированный тег
                          Я в курсе про такой способ, им и перебиваюсь, но будем честны это просто костыль. Большинство разработчиков используют теги для версионирования, а мелькающие среди версий огрызки веток выглядят ужасно и кустарно.

                          Если вы не следуете gitflow или похожему подходу, когда одна задача = одна ветка, то скорее всего у вас просто не возникнет такой проблемы.
                          ИМХО у вас какой то странный запрос.
                          Настолько странный, что о нем писали сотни раз до меня. В hg это дефолтный способ работы с ветками.
                            0
                            будем честны это просто костыль.

                            Механизм тегов решает именно ту задачу о которой вы пишете:
                            1) не отображаются в основном списке веток
                            2) нельзя комитить в него без явного переоткрытия (создания новой ветки на основе тега)


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

                            Ничего не мешает создать некий префикс для имен таких тегов и фильтровать по нему. Даже с использованием тегов только для версионирования очень быстро их становятся сотни а значит глазами их никто не читает. Обычный вариант как я пользуюсь командой:


                            git tag | grep v
                              0
                              Механизм тегов решает именно ту задачу о которой вы пишете
                              Он решает ее криво (ветка не просто скрыта, ее вообще нет), с побочками в виде мусорных тегов, и это просто неудобно. При этом в истории все равно образуются холмы (пример из интернета) из коммитов «закрытых» веток.
                              нельзя комитить в него без явного переоткрытия
                              Почему нельзя? Чекаут по имени тега, внес изменения, закоммитил. Фактически все работает, просто наш тег выполняющий роль головы ветки не двигается на новый коммит, как раз потому что он тег, а не голова ветки. Т.е. мы просто поломали функционал ветки, а не реализовали ее закрытие.
                              Ничего не мешает создать некий префикс для имен таких тегов и фильтровать по нему.
                              И как это сделать скажем в гитхабе/битбакките, чтобы посетитель репозитория зайдя в раздел релизов не видел там дохлых веток?
                                0
                                Он решает ее криво, с побочками в виде мусорных тегов, и это просто неудобно.

                                Чем мусорная закрытая ветка отличается от мусорного тега?


                                И как это сделать скажем в гитхабе/битбакките, чтобы посетитель репозитория зайдя в раздел релизов не видел там дохлых веток?

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

                                  0
                                  Чем мусорная закрытая ветка отличается от мусорного тега?
                                  Тем что она не мусорная. Ее не видно там, где ее не должно быть видно, но при этом видно там, где мы ожидаем ее увидеть.
                                  Ну это уже вопрос собственно к гитхабу и битбакету, если они не реализовали подобный функционал значит спрос на него небольшой
                                  А совсем не потому что гит на это не рассчитан, и теги нужны именно для тегов, да. Наверное оно со всеми фичами так, если еще нет — значит и не надо.
                                    0

                                    Тег это просто метка и постоянный указатель на коммит. За годы развития гита было множество вариантов их использовать. Наиболее популярный из тех что есть сейчас — это использовать теги для версионирования. Но при этом в самом гите на этот счет ничего не сказано и вы имеете все возможности использовать их так как вам удобно. Например в одном из наших workflow мы ставим несколько тегов на один коммит — один тег помечает комит как версию, а еще 2 тега на тот же коммит подписаны людьми ответственными за выпуск релиза и соответственно удостоверяют подписью их согласие на выпуск. Соответственно деплой prod окружения происходит при наличии 3-х тегов а не одного. Соответственно мы используем этот механизм так как это удобно нам. Поэтому я не понимаю почему вы считает что для хранения легаси веток их нельзя использовать только потому что Github не дает вам удобной UI для их просмотра

                                  0
                                  удалено
                                0
                                чтобы ветка не отображалась и нельзя было в нее коммитить, удалите её локально, но оставьте на сервере.
                          +1
                          Потомучто github ;)
                          Сравнимого по функционалу бесплатного хостинга для чего угодно другого нет, КМК.
                            +3
                            Это не объясняет почему он github, а не svnhub или даже hghub.
                              0
                              Что первично, курица или яйцо ;)
                              Так то hg значительно проще в использовании, чем git. И многим его с головой бы хватило. И при наличии бесплатного (для личного пользования) hghub с вебмордой — многи предпочли бы его.
                                +1
                                я думаю, что просто «исторически так сложилось». Как показывает практика, далеко не всегда выживает лучший продукт. Достаточно вспомнить историю развития операционных систем )
                                  0
                                  И при наличии бесплатного (для личного пользования) hghub с вебмордой — многи предпочли бы его.
                                  Был же Bitbucket, аж с 2008 года. Но в 2011 он добавил поддержку git, а в этом году выпилит поддержку самого меркуриала.
                                    +1
                                    Был же Bitbucket, аж с 2008 года


                                    В 2016 году, судя по ссылкам из википедии, в Bitbucket пользователей (hg + git) было в 4 раза меньше, чем у github (git only).
                                    Что-то пошло не так.
                                    Чем-то напоминает delphi
                                      0
                                      И тем не менее, сама возможность бесплатного юза была, причем с большинством важных фич которые были у гитхаба. Я даже большу скажу, в битбакките можно было бесплатно иметь приватные репозитории, а в гитхабе они тогда были платными. Ну и «четверть от овердофига» это все еще «дофига».
                                        0
                                        Ну и «четверть от овердофига» это все еще «дофига».

                                        Только вот из этой четверти на меркуриале было так мало пользователей, что проще оказалось прикрыть его поддержку.
                              +4
                              Теперь возникает вопрос: почему git победил svn?
                              думаю основная причина — децентрализация и не зависимость от сервера. Не знаю как сейчас, но в svn до версии 1.7 включительно, без доступа к серверу ты не мог ничего сохранить (закомитить). Т.е. едишь ты в поезде или где то на даче без инета — и все, приехали.
                                0

                                Даже более того, в случае git можно синхронизироваться вообще без сетевого соединения, через git bundle.

                              +1

                              Лично я понял как — деревья используются для хранения данные об файлах их иерархическом положении в древе абстрактной файловой системы для корректного воссоздания структуры.
                              А по факту при коммите формируется, и храниться дельта изменений.
                              В противном случаи каждый коммит в файл исходного кода, размером в 1 Мб, должен будет порождать его копию (пусть даже и архивную). Чисто эмперическим путем можно увидеть:


                              • при первичном коммите файла в 1Мб = коммит имеет большой размер (более 1 Мб), т.к. коммит содержит дельту от "ничего" до 1Мб
                              • при следующем коммите (при изменении всего одного символа) мы получаем объем хранимых данных близкий по объему изменений.
                                0

                                "Ох как часто мы принимаем науку за магию" — я сам.
                                Был не прав в своем понимании.
                                Действительно хранятся "слепки" файлов. а небольшие изменения в размерах вызваны просто их "архивацией".

                        +17

                        В этой статье содержится еще меньше информации, чем в сотне других.

                        0

                        Там ещё упакованный формат хранения в директории objects/pack есть.

                          +4
                          Было бы интересно, если бы кто-нибудь написал статью про алгоритм совместной работы нескольких человек над одним проектом в git. Это всем кажется очевидным, но я так толком и не понял, как это делается правильно, чтобы не получился конфликт написанного разными людьми. Им договариваться не трогать модули друг-друга или как? А то Вася впишет в модуль своё, Юра поменяет его идеологию и что тогда получится? Вот как?
                            +7
                            Тогда при попытке слияния коммитов Васи и Юры возникнет конфликт слияния (мерж конфликт) и гит попросит решить, что из двух вариантов изменений файла нужно оставить, а что удалить (или же оставить всё).
                            Разрешение конфликтов слияния — очень частая операция при работе даже двоих людей над одним и тем же кодом.
                            Когда ситуация разрастается до десятков и больше человек — как вариант решения, назначается ответственный за прием кода в мастер-ветку, он же и решает мерж-конфликты. Хотя это не единственный возможный сценарий решения этой проблемы.
                              0
                              Тогда при попытке слияния коммитов Васи и Юры возникнет конфликт слияния (мерж конфликт) и гит попросит решить, что из двух вариантов изменений файла нужно оставить, а что удалить (или же оставить всё).


                              Правильно. И получится нерабочий код. Юра трогал не только общий модуль. Вася поступил так же. И арбитр замучается выяснять, что можно слить, а чего нельзя. Это-то и беспокоит.
                                0

                                В большинстве случаев слияние тривиально.


                                В меньшинстве случаев либо Вася, либо Юра берут обновлёный код и делают те же самые изменения, которые делали ранее.


                                Ну и проверку получившегося кода никто не отменял.

                                  +1
                                  Еще один вариант — перед пушем кода в общую ветку сначала стягиваются к себе все изменения из общей ветки и возникшие мерж-конфликты решаются разработчиком самостоятельно. В результате при пуше конфликтов нет.
                                  Стоит добавить, что при таком варианте возможно попадание некачественного кода в основную ветку.
                                  Это решается обязыванием проганять тесты перед пушем в мастер, и обязятельным ревью кода, без которого пуш в мастер невозможен.
                                  Это снимает необходимость в супер-девелопере, который должен принимать изменения в основную ветку, и снижает нагрузку.
                                  Еще добавлю что редко всё же бывают ситуации когда отдельные запросы на внесение кода в основную ветку проходят тесты успешно, но в конце дня суммарно может что-то поломаться. Ну и человек, занимающийся внесением проверенных тестами и просмотренных другими людьми изменений в основную ветку всё же нужен в данном сценарии.
                                +1

                                https://m.habr.com/ru/post/75990/ — как-то так. Информацию по настройке можете пропустить.

                                  0
                                  О, спасибо! То, что нужно.
                                  +1
                                  > договариваться не трогать модули друг-друга
                                  в точку.
                                    0
                                    Это же как раз одна из фич git — возможность нормальной работы нескольких человек над одним файлом. Насколько помню, в svn и SourceSafe при чекауте просто лочился файл и привет — жди, пока отпустят.
                                      0

                                      Это в SourceSafe было. В svn изменения в текстовых файлах тоже сливаются.

                                    +1

                                    С таким переводом xkcd — спасибо, не надо. Почему "запомни", а не "запомнил"?

                                      0

                                      Кол-во файлов в 255 раз меньше? Как так?

                                        0
                                        они же по папкам раскладываются от 00 до ff
                                          0

                                          От разкладывания по 255 папкам кол-во файлов уменьшается в 255 раз? Серьёзно?

                                            0

                                            В среднем в одной директории становится в 256 раз меньше файлов. Кстати, если файлов будет слишком много, то возможно дальнейшее распихивание по директориям: objects/aa/bb/... и т.д.


                                            Цель такого мероприятия: уменьшить количество файлов в одной директории из-за проблем с масштабируемостью файловых систем.

                                              0
                                              да все верно, отличный комментарий!
                                                0
                                                Я не спрашивал про цель, она очевидна. Я усомнился в конкретном утверждении: «Этот маленький трюк в 255 раз уменьшает количество файлов в папке /objects». Вы увидели тут слова «в среднем в одной директории», а я почему-то увидел «в папке /objects». Так может, лучше просто признать косяк?
                                                  0
                                                  Я не вижу здесь косяка, но я поменял текст чтобы было меньше разночтений.
                                                    0

                                                    Это очевидный косяк, потому что в папке /objects кол-во файлов не изменилось вообще, а лишь добавились промежуточные директории. И разночтений тут быть не может.

                                          0

                                          От разкладывания по 255 папкам кол-во файлов уменьшается в 255 раз? Серьёзно?

                                            0
                                            файлы раскладываются по папкам — в каждой папке меньше файлов чем было бы если бы не раскладывали. Серьезно.
                                              0
                                              «Этот маленький трюк в 255 раз уменьшает количество файлов в папке /objects» — вы считаете, что «папка /objects» это недостаточно чёткое определение?
                                                0

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

                                                  0

                                                  Если второе, то в папке objects нет ни одного файла и от 1 до 255 других папок 00-ff. То есть, оспариваемое утверждение неверно никогда.

                                                    +1

                                                    "Этот маленький трюк", о последствиях которого спор, уменьшает количество файлов в папке /objects до количества вложенных папок (каждая из которых тоже файл). Если бы не трюк, то все файлы из подпапок валялись бы непосредственно в папке /objects.
                                                    Кстати, уменьшение количества файлов начинается не сразу. На начальном этапе в каждой подпапке получается по одному файлу, а потому никакого выигрыша нет.

                                                      0

                                                      Вот именно, что до количества вложенных папок, а не в 255 раз. То есть, в первоначальном тексте был явный косяк, о чем я и написал. И был прав.

                                            0
                                            Вот ссылка не тред, где сами авторы git и hg «меряются»
                                              0
                                              Ссылка вроде бы годная, но мне кажется, что за 15 лет в обоих программах много чего могло поменяться в плане производительности и алгоритмов компрессии.

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое