Начало использования 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
¦ L-- ...
+-- info
¦ L-- exclude
+-- objects
¦ +-- info
¦ L-- pack
L-- refs
+-- heads
L-- tags
Вот содержимое типовой папки .git перед вашим первым коммитом:
- HEAD — это мы рассмотрим позже.
- config — этот файл содержит настройки для вашего репозитория, здесь, например, хранится url вашего репозитория в хранилище, ваше имя, email и так далее. Каждый раз когда вы делаете git config, вы обращаетесь к этому файлу.
- description — используется gitweb для отображения описания репозитория.
- hooks — эта папка содержит скрипты, которые могут выполняться на различных этапах выполнения Git. Эти скрипты, называемые хуками, могут запускаться до/после commit/rebase/pull… Имя скрипта определяет время его выполнения. Примером хука может служить скрипт проверки стиля перед выполнением команды push в репозиторий.
- info — exclude — здесь описываются файлы, которые вы не хотите включать в репозиторий. Функционал этого файла такой же, как у файла .gitignore, за исключением того, что он не передается в репозиторий. На практике обычно для всех задач хватает .gitignore.
Что внутри коммита?
Каждый раз, когда вы создаете файл и коммитите изменения, Git архивирует файл и сохраняет его в своей структуре данных. Архивированный объект создается с уникальным именем и хранится в папке объектов.
Перед изучением папки объектов уточним, что же такое коммит. Коммит — это слепок текущего состояния файлов в рабочей папке, но не только это.
По факту, когда вы коммитите изменения, Git производит всего два действия:
- Если файл в рабочей папке не изменялся, он просто добавляет имя сжатого файла (хеш) в снимок.
- Если файл в рабочей папке изменялся, он сжимает его, помещает в папку объектов и добавляет имя сжатого файла (хеш) в снимок.
Конечно, тут все описано несколько упрощенно, однако, этого достаточно для понимания происходящих процессов.
Как только снимок сделан, он также архивируется и именуется при помощи хеша, затем помещается в папку объектов.
+-- 4c
¦ L-- f44f1e3fe4fb7f8aa42138c324f63f5ac85828 // hash
+-- 86
¦ L-- 550c31847e518e1927f95991c949fc14efc711 // hash
+-- e6
¦ L-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 // hash
+-- info // let's ignore that
L-- pack // let's ignore that too
Вот как выглядит папка объектов после того, как я создал файл file_1.txt и закоммитил его. Пожалуйста, учтите, что если хеш вашего файла начинается на «4cf44f1e…», то Git сохранит его с именем «f44f1e…» в подпапке с именем «4c». Таким образом, файлы будут разложены по 256 подпапкам и в каждой не будет слишком много файлов.
У нас, как вы видите, три хеша. Один для файла file_1.txt, второй для снимка, сделанного при коммите. Для чего же третий? Третий хеш создается потому, что коммит — тоже объект, он также архивируется и помещается в папку объектов.
Вам нужно запомнить, что коммит состоит из четырех вещей:
- Имя (хеш) снимка рабочей директории.
- Комментарий.
- Информация о том, кто выполнил коммит.
- Хеш родительского коммита.
Посмотрите сами, что произойдет, если распаковать файл коммита:
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
Вы видите, как и ожидалось, хеш снимка, автора и комментарий коммита. Тут важны две вещи:
- Хеш снимка «86550...» также является объектом и его можно увидеть в папке объектов.
- Так как это первый коммит, у него нет родительского коммита.
Что же в снимке в действительности?
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.
- Простые способы кэширования в GitLab CI: руководство в картинках.
- Как технический долг и лже-Agile убивает ваши проекты.
- Мой второй год в качестве независимого разработчика.
MechanicZelenyy
Возможно я что-то не так понял в статье, но я считал что git ( в отличии от например svn) хранит не файлы, а изменения сделанные в файле. А если следовать написанному в статье, то получается что он хранит копию каждого состояния файла.
RomanenkoDenys Автор
Насколько я могу судить, все таки архивированные объекты.
githowto.com/ru/git_internals_git_directory
Цитата — Смотрим в один из каталогов с именем из 2 букв. Вы увидите файлы с именами из 38 символов. Это файлы, содержащие объекты, хранящиеся в git. Они сжаты и закодированы, поэтому просмотр их содержимого нам мало чем поможет.
MechanicZelenyy
Ну я подозреваю что в данном случае под объектом подразумевается набор изменений в файле.
RomanenkoDenys Автор
Нет это именно файлы
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
Там подробно с примерами расписано как хранятся файлы в дереве
MechanicZelenyy
Интересно. Я думал он как и mercurial хранит изменения.
Теперь возникает вопрос: почему git победил svn?
TyVik
Потому что популярнее. Мне кажется, это единственное что решило. Хотя hg более продуманный как по внутреннему устройству, так и по интерфейсу работы. Вот забавная статья про коаны git.
ivan386
На мой взгляд потому что нет зависимости от сервера. Недавно узнал что можно клонировать и пушить не только на сервер но и в дирректорию на диске.
AVI-crak
Потому что позволяет редактировать данные задним числом, как в 1С.
Alexei_987
В гите есть несколько уровней оптимизации. С точки зрения прикладного пользователя вы можете считать что он хранит полные копии каждого файла на момент его коммита. Но на самом деле гит периодически выполняет команду упаковки и в процессе ее выполнения анализирует обьекты в хранилище и вычисляет дельты между ними, в дальнейшем он заменяет обьекты на дельты за счет чего экономится место. При этом там есть еще куча оптимизаций вроде того что самые свежие версии файла хранятся целиком для быстрого доступа, а старые версии хранятся в виде отрицательных дельт для экономии места.
ИМХО git победил svn за счет гениальной организации и отличной и продуманной работы с ветками (до которой далеко тому же hg). Данная статья не описывает даже десятой части того насколько красиво внутри все устроено
playermet
Alexei_987
Что в вашем понимании безболезненно закрывать?
git merge позволяет смерджить ветку куда либо и не удаляет ее… это равно закрыть ветку?
Если вы хотите избавится от возможности создавать в ветке новые комиты — создайте тег с тем же именем и удалите ветку. останется зафиксированный тег. ИМХО у вас какой то странный запрос.
playermet
Я в курсе про такой способ, им и перебиваюсь, но будем честны это просто костыль. Большинство разработчиков используют теги для версионирования, а мелькающие среди версий огрызки веток выглядят ужасно и кустарно.
Если вы не следуете gitflow или похожему подходу, когда одна задача = одна ветка, то скорее всего у вас просто не возникнет такой проблемы.
Настолько странный, что о нем писали сотни раз до меня. В hg это дефолтный способ работы с ветками.
Alexei_987
Механизм тегов решает именно ту задачу о которой вы пишете:
1) не отображаются в основном списке веток
2) нельзя комитить в него без явного переоткрытия (создания новой ветки на основе тега)
Ничего не мешает создать некий префикс для имен таких тегов и фильтровать по нему. Даже с использованием тегов только для версионирования очень быстро их становятся сотни а значит глазами их никто не читает. Обычный вариант как я пользуюсь командой:
playermet
Почему нельзя? Чекаут по имени тега, внес изменения, закоммитил. Фактически все работает, просто наш тег выполняющий роль головы ветки не двигается на новый коммит, как раз потому что он тег, а не голова ветки. Т.е. мы просто поломали функционал ветки, а не реализовали ее закрытие.
И как это сделать скажем в гитхабе/битбакките, чтобы посетитель репозитория зайдя в раздел релизов не видел там дохлых веток?
Alexei_987
Чем мусорная закрытая ветка отличается от мусорного тега?
Ну это уже вопрос собственно к гитхабу и битбакету, если они не реализовали подобный функционал значит спрос на него небольшой. Это не проблемы гита как системы контроля версий как там гитхаб их отображает
playermet
А совсем не потому что гит на это не рассчитан, и теги нужны именно для тегов, да. Наверное оно со всеми фичами так, если еще нет — значит и не надо.
Alexei_987
Тег это просто метка и постоянный указатель на коммит. За годы развития гита было множество вариантов их использовать. Наиболее популярный из тех что есть сейчас — это использовать теги для версионирования. Но при этом в самом гите на этот счет ничего не сказано и вы имеете все возможности использовать их так как вам удобно. Например в одном из наших workflow мы ставим несколько тегов на один коммит — один тег помечает комит как версию, а еще 2 тега на тот же коммит подписаны людьми ответственными за выпуск релиза и соответственно удостоверяют подписью их согласие на выпуск. Соответственно деплой prod окружения происходит при наличии 3-х тегов а не одного. Соответственно мы используем этот механизм так как это удобно нам. Поэтому я не понимаю почему вы считает что для хранения легаси веток их нельзя использовать только потому что Github не дает вам удобной UI для их просмотра
bingo347
удалено
McDuk
Потомучто github ;)
Сравнимого по функционалу бесплатного хостинга для чего угодно другого нет, КМК.
MechanicZelenyy
Это не объясняет почему он github, а не svnhub или даже hghub.
McDuk
Что первично, курица или яйцо ;)
Так то hg значительно проще в использовании, чем git. И многим его с головой бы хватило. И при наличии бесплатного (для личного пользования) hghub с вебмордой — многи предпочли бы его.
RomanenkoDenys Автор
я думаю, что просто «исторически так сложилось». Как показывает практика, далеко не всегда выживает лучший продукт. Достаточно вспомнить историю развития операционных систем )
playermet
McDuk
В 2016 году, судя по ссылкам из википедии, в Bitbucket пользователей (hg + git) было в 4 раза меньше, чем у github (git only).
playermet
И тем не менее, сама возможность бесплатного юза была, причем с большинством важных фич которые были у гитхаба. Я даже большу скажу, в битбакките можно было бесплатно иметь приватные репозитории, а в гитхабе они тогда были платными. Ну и «четверть от овердофига» это все еще «дофига».
ALexhha
DistortNeo
Даже более того, в случае git можно синхронизироваться вообще без сетевого соединения, через git bundle.
AlexMcArrow
Лично я понял как — деревья используются для хранения данные об файлах их иерархическом положении в древе абстрактной файловой системы для корректного воссоздания структуры.
А по факту при коммите формируется, и храниться дельта изменений.
В противном случаи каждый коммит в файл исходного кода, размером в 1 Мб, должен будет порождать его копию (пусть даже и архивную). Чисто эмперическим путем можно увидеть:
AlexMcArrow
"Ох как часто мы принимаем науку за магию" — я сам.
Был не прав в своем понимании.
Действительно хранятся "слепки" файлов. а небольшие изменения в размерах вызваны просто их "архивацией".
gluck59
В этой статье содержится еще меньше информации, чем в сотне других.
edo1h
можно хотя бы 2-3, в которых описываются "кишки"?
maquefel
Pro Git — не ?
https://git-scm.com/book/en/v2
gluck59
Автор не увидит ваш комментарий здесь.
От меня: https://git-scm.com/doc
DistortNeo
Там ещё упакованный формат хранения в директории objects/pack есть.
da-nie
Было бы интересно, если бы кто-нибудь написал статью про алгоритм совместной работы нескольких человек над одним проектом в git. Это всем кажется очевидным, но я так толком и не понял, как это делается правильно, чтобы не получился конфликт написанного разными людьми. Им договариваться не трогать модули друг-друга или как? А то Вася впишет в модуль своё, Юра поменяет его идеологию и что тогда получится? Вот как?
Nokse
Тогда при попытке слияния коммитов Васи и Юры возникнет конфликт слияния (мерж конфликт) и гит попросит решить, что из двух вариантов изменений файла нужно оставить, а что удалить (или же оставить всё).
Разрешение конфликтов слияния — очень частая операция при работе даже двоих людей над одним и тем же кодом.
Когда ситуация разрастается до десятков и больше человек — как вариант решения, назначается ответственный за прием кода в мастер-ветку, он же и решает мерж-конфликты. Хотя это не единственный возможный сценарий решения этой проблемы.
da-nie
Правильно. И получится нерабочий код. Юра трогал не только общий модуль. Вася поступил так же. И арбитр замучается выяснять, что можно слить, а чего нельзя. Это-то и беспокоит.
mayorovp
В большинстве случаев слияние тривиально.
В меньшинстве случаев либо Вася, либо Юра берут обновлёный код и делают те же самые изменения, которые делали ранее.
Ну и проверку получившегося кода никто не отменял.
Nokse
Еще один вариант — перед пушем кода в общую ветку сначала стягиваются к себе все изменения из общей ветки и возникшие мерж-конфликты решаются разработчиком самостоятельно. В результате при пуше конфликтов нет.
Стоит добавить, что при таком варианте возможно попадание некачественного кода в основную ветку.
Это решается обязыванием проганять тесты перед пушем в мастер, и обязятельным ревью кода, без которого пуш в мастер невозможен.
Это снимает необходимость в супер-девелопере, который должен принимать изменения в основную ветку, и снижает нагрузку.
Еще добавлю что редко всё же бывают ситуации когда отдельные запросы на внесение кода в основную ветку проходят тесты успешно, но в конце дня суммарно может что-то поломаться. Ну и человек, занимающийся внесением проверенных тестами и просмотренных другими людьми изменений в основную ветку всё же нужен в данном сценарии.
gavk
https://m.habr.com/ru/post/75990/ — как-то так. Информацию по настройке можете пропустить.
da-nie
О, спасибо! То, что нужно.
tim4dev
> договариваться не трогать модули друг-друга
в точку.
Stas911
Это же как раз одна из фич git — возможность нормальной работы нескольких человек над одним файлом. Насколько помню, в svn и SourceSafe при чекауте просто лочился файл и привет — жди, пока отпустят.
mayorovp
Это в SourceSafe было. В svn изменения в текстовых файлах тоже сливаются.
amarao
С таким переводом xkcd — спасибо, не надо. Почему "запомни", а не "запомнил"?
dyadyaSerezha
Кол-во файлов в 255 раз меньше? Как так?
RomanenkoDenys Автор
они же по папкам раскладываются от 00 до ff
dyadyaSerezha
От разкладывания по 255 папкам кол-во файлов уменьшается в 255 раз? Серьёзно?
DistortNeo
В среднем в одной директории становится в 256 раз меньше файлов. Кстати, если файлов будет слишком много, то возможно дальнейшее распихивание по директориям:
objects/aa/bb/...
и т.д.Цель такого мероприятия: уменьшить количество файлов в одной директории из-за проблем с масштабируемостью файловых систем.
RomanenkoDenys Автор
да все верно, отличный комментарий!
dyadyaSerezha
Я не спрашивал про цель, она очевидна. Я усомнился в конкретном утверждении: «Этот маленький трюк в 255 раз уменьшает количество файлов в папке /objects». Вы увидели тут слова «в среднем в одной директории», а я почему-то увидел «в папке /objects». Так может, лучше просто признать косяк?
RomanenkoDenys Автор
Я не вижу здесь косяка, но я поменял текст чтобы было меньше разночтений.
dyadyaSerezha
Это очевидный косяк, потому что в папке /objects кол-во файлов не изменилось вообще, а лишь добавились промежуточные директории. И разночтений тут быть не может.
dyadyaSerezha
От разкладывания по 255 папкам кол-во файлов уменьшается в 255 раз? Серьёзно?
RomanenkoDenys Автор
файлы раскладываются по папкам — в каждой папке меньше файлов чем было бы если бы не раскладывали. Серьезно.
dyadyaSerezha
«Этот маленький трюк в 255 раз уменьшает количество файлов в папке /objects» — вы считаете, что «папка /objects» это недостаточно чёткое определение?
geher
Вопрос трактовки.
"количество файлов в папке" может означать количество файлов в папке как с учетом файлов во вложенных папках, так и без учета.
Регулярно встречаю как первое, так и второе в интерфейсе программ, всяких руководствах и книгах.
Если имеется ввиду первое, то оспариваемое утверждение неверно, если второе, то верно.
dyadyaSerezha
Если второе, то в папке objects нет ни одного файла и от 1 до 255 других папок 00-ff. То есть, оспариваемое утверждение неверно никогда.
geher
"Этот маленький трюк", о последствиях которого спор, уменьшает количество файлов в папке /objects до количества вложенных папок (каждая из которых тоже файл). Если бы не трюк, то все файлы из подпапок валялись бы непосредственно в папке /objects.
Кстати, уменьшение количества файлов начинается не сразу. На начальном этапе в каждой подпапке получается по одному файлу, а потому никакого выигрыша нет.
dyadyaSerezha
Вот именно, что до количества вложенных папок, а не в 255 раз. То есть, в первоначальном тексте был явный косяк, о чем я и написал. И был прав.
sborisov
Вот ссылка не тред, где сами авторы git и hg «меряются»