Найти в Дзене
Dev Articles

Как работает Git?

Если вы похожи на меня и имеете менее двадцати лет опыта в разработке программного обеспечения, то мысль о мире без Git кажется невозможной. Когда я начал исследовать материал для этой статьи, я чуть не упал со стула, прочитав, что Git был создан в 2005 году. Кажется, что это было не так давно… либо я просто старею. Когда я начинал программировать, я задавал себе вопрос, который иногда задаю до сих пор: как работает Git? Меня часто пугают некоторые команды Git. Трудно точно знать, следует ли мне использовать rebase или, может быть, merge. Когда вы хотите что-то отменить, сложно понять, является ли git revert лучшим вариантом. Иногда я задаю себе такие вопросы, как: в каком случае уместно использовать принудительную отправку (force push)? Определённо были случаи, когда неправильная команда Git приводила к серьёзным проблемам. Поэтому я решил набраться смелости и узнать, что происходит под этой магической «капотом». В этой статье вы узнаете, как работает Git, познакомитесь с его историей
Оглавление

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

Когда я начинал программировать, я задавал себе вопрос, который иногда задаю до сих пор: как работает Git? Меня часто пугают некоторые команды Git. Трудно точно знать, следует ли мне использовать rebase или, может быть, merge. Когда вы хотите что-то отменить, сложно понять, является ли git revert лучшим вариантом.

Иногда я задаю себе такие вопросы, как: в каком случае уместно использовать принудительную отправку (force push)? Определённо были случаи, когда неправильная команда Git приводила к серьёзным проблемам. Поэтому я решил набраться смелости и узнать, что происходит под этой магической «капотом». В этой статье вы узнаете, как работает Git, познакомитесь с его историей и получите несколько практических советов.

Краткая история Git

Git — это распределённая система контроля версий, что означает, что её можно использовать в нескольких системах, включая централизованное хранилище и сервер. До появления распределённых систем, таких как Git, самым популярным способом управления изменениями в программном обеспечении был Subversion (SVN). В отличие от Git, Subversion является централизованной, а не распределённой системой. Git использует удалённый репозиторий для отслеживания изменений, и разработчики могут использовать git push для обновления удалённого репозитория изменениями сделанными на своём локальном компьютере. Разработчики используют ветвление и запросы на включение для отслеживания изменений в кодовой базе, упрощая совместную разработку программного обеспечения.

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

Хотя большинство из нас помнит Git как первую распределённую систему контроля версий, на самом деле это было не так. До Git существовал BitKeeper — проприетарная система управления исходным кодом. Созданный в 1998 году, BitKeeper был разработан для решения некоторых проблем проекта Linux. Он предлагал бесплатную лицензию для проектов с открытым исходным кодом при условии, что разработчики не будут создавать конкурирующий инструмент во время использования BitKeeper и ещё один год после этого.

С учётом этих ограничений (для разработчиков Linux!) вы, наверное, можете догадаться, что произошло. В начале–середине 2000-х годов было много жалоб на лицензии, и в 2005 году бесплатная версия BitKeeper была удалена. Это побудило Торвальдса быстро создать Git, который он назвал в честь британского сленгового слова, означающего «неприятный человек».

Проект был передан Джуниу Хамано (основному контрибьютору) после первоначального выпуска версии 0.99, и Джуниу остаётся основным сопровождающим проекта. Интересный факт: новые функции всё ещё разрабатываются для Git. Последняя версия Git была выпущена в июне 2024 года и имеет версию 2.46.0.

Если вы хотите узнать больше о BitKeeper, ознакомьтесь со страницей Википедии, которая интересна, даже несмотря на то, что разработка уже не ведётся.

Понимание Git

Хотя Git со временем превратился в полноценную систему управления версиями, изначально это не было его целью. Линус Торвальдс сказал по этому поводу следующее:

«Во многих отношениях вы можете рассматривать Git как файловую систему — она адресуется по содержимому(content-addressable) и имеет концепцию версионирования, но я действительно разрабатывал её, подходя к проблеме с точки зрения специалиста по файловым системам (в конце концов, ядра — это то, чем я занимаюсь), и на самом деле я совершенно не заинтересован в создании традиционной системы управления исходным кодом».

Примечание: если вам интересно, что Линус имеет в виду под «адресацией по содержимому», это способ хранения информации, при котором её можно извлекать на основе содержимого, а не местоположения в файловой системе. Большинство традиционных локальных и сетевых устройств хранения данных адресуются по местоположению, и Линус подчёркивает, что Git работает не так.

Под капотом Git имеет две структуры данных:

  • изменяемый индекс (то есть точка соединения между базой данных объектов и рабочим деревом);
  • неизменяемая, только для добавления база данных объектов.

В Git существует пять типов объектов:

  • blob: это содержимое файла;
  • tree: это эквивалент каталога;
  • commit: связывает объекты tree вместе, образуя историю;
  • tag: это контейнер, который содержит ссылку на другой объект, а также другие метаданные;
  • packfile: сжатая версия различных других объектов в формате zlib

Каждый объект имеет уникальное имя, которое представляет собой SHA-1 хеш его содержимого.

Чтобы лучше понять, как всё это работает вместе, давайте рассмотрим базовый рабочий процесс Git. Начните с создания простого примера каталога проекта и выполните команду git init.

Практическое применение

Откройте терминал и создайте новый каталог. Затем выполните команду git init. Вы должны увидеть примерно такой вывод:

mkdir understanding-git
cd understanding-git
git init
Initialized empty Git repository in /Users/juliekent/Documents/understanding-git/.git/
understanding-git git:(master)

Я уверен, что вы делали это много раз, начиная новые проекты, но, возможно, вас не интересовало, что на самом деле находится в только что созданном каталоге .git. Давайте посмотрим!

Если вы выполните команду ls -a через терминал, вы увидите каталог .git. По умолчанию это скрытый каталог, поэтому вам нужен флаг -a, чтобы его отобразить. Перейдите в этот скрытый каталог, выполнив команду cd .git, затем выполните ls. Вы должны увидеть примерно следующее:

ls
HEAD config description hooks info objects refs

Для этой статьи мы сосредоточимся на каталогах HEAD, objects и refs. Мы также выполним несколько команд, чтобы у нас появились файлы индекса, но это сделаем позже. Файл description используется только программой GitWeb. Файл config довольно прост, так как содержит параметры конфигурации проекта. Каталог info хранит глобальный файл исключений для игнорируемых шаблонов, которые вы не хотите отслеживать, основанный на файле .gitignore в корне проекта, с которым большинство из вас уже знакомы.

Каталог объектов Git

Давайте начнём с изучения каталога objects. Чтобы увидеть, что создаётся командой git init, выполните find .git/objects. Вы должны увидеть следующее:

find .git/objects
.git/objects
.git/objects/pack
.git/objects/info

Далее создадим файл в каталоге, выполнив:

echo 'this is me' > myfile.txt

Эта команда просто создаёт текстовый файл с именем myfile.txt с содержимым «this is me».

Теперь выполним команду, чтобы получить контрольную сумму (хеш) этого файла:

git hash-object -w myfile.txt

Ваш вывод должен быть случайным набором цифр и букв — это SHA-1 хеш-сумма. Если вы не знакомы с SHA-1, стоит изучить, что это такое и как используется.

Далее скопируйте ваш SHA-1 и выполните следующую команду:

git cat-file -p <вставьте ваш SHA здесь>

Вы должны увидеть «this is me» — содержимое текстового файла, который вы создали ранее. Здорово! Вот как работают адресуемые по содержимому объекты Git. Вы можете представить это как хранилище ключ-значение, где ключ — это SHA-1, а значение — содержимое файла.

Далее углубимся в то, как работает git, и запишем новое содержимое в наш исходный файл, выполнив:

echo 'this is not me' > myfile.txt

Затем снова выполните команду hash-object, чтобы получить хеш:

git hash-object -w myfile.txt

Теперь у вас есть два уникальных SHA-1 для обеих версий этого файла. Если хотите получить дополнительное подтверждение, выполните find .git/objects -type f, и вы должны увидеть оба через окно терминала.

Если вы хотите узнать больше о том, как работают другие объекты в Git, рекомендую ознакомиться с этим руководством по объектам Git.

Каталог ссылок Git (refs)

Давайте перейдём к refs. При выполнении команды find .git/refs вы должны увидеть следующий вывод:

understanding-git git:(master) ✗ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags

Как мы объясняли в предыдущем разделе об объектах, Git создаёт уникальные SHA-1 хеши для каждого из них. Конечно, мы могли бы выполнять все команды Git, используя хеш каждого объекта. Например, git show 123abcd, но это неразумно и потребовало бы от нас запоминать хеш каждого объекта. К счастью, ссылки Git (refs) помогают нам связывать объекты с их хешами.

Ссылка — это просто файл, хранящийся в .git/refs, содержащий хеш объекта коммита. Давайте продолжим и закоммитим наш myfile.txt, чтобы лучше понять, как работают refs. Выполните git add myfile.txt, а затем зафиксируйте изменения командой git commit -m ‘first commit’. Вы должны увидеть примерно следующее:

git add myfile.txt
git commit -m 'first commit'
[master (root-commit) 40235ba] first commit
1 file changed, 1 insertion(+)
create mode 100644 myfile.txt

Теперь перейдите в каталог .git/refs/heads, выполнив cd .git/refs/heads. Оттуда выполните cat master, чтобы вывести содержимое файла. Вы увидите SHA-1. Наконец, выполните git log -1 master, который должен вывести что-то похожее на следующее:

commit Unique SHA-1 (HEAD -> master)
Author: Julie <jkent2910@gmail.com>
Date: Mon Aug 3 15:59:59 2020 -0500

first commit

Как видно из этого примера, ветки Git — это просто ссылки. Когда мы меняем расположение ветки master, Git просто изменяет содержимое файла refs/heads/master. Когда вы создаёте новую ветку Git, вы создаёте новую ссылку. Аналогично, создание новой ветки создаёт новый файл ссылки с хешем коммита.

Если вы хотите отменить коммит, вы можете выполнить git revert с SHA-1 коммита. Команда git revert создаст новый коммит в ветке с изменениями, которые являются полной противоположностью оригинального коммита. В качестве альтернативы вы можете использовать интерактивный rebase, чтобы полностью удалить коммит с помощью следующей команды:

git rebase master -i

Полезный совет: если вы когда-нибудь захотите увидеть все ссылки, выполните git show-ref, который выведет список всех ссылок.

Что такое Git HEAD?

В Git HEAD — это символическая ссылка. Вы можете задаться вопросом: когда выполняется git branch <branch>, как Git узнаёт SHA-1 последнего коммита? Ну, файл HEAD обычно является символической ссылкой на вашу текущую ветку Git.

Возможно, вы думаете: «Вы продолжаете говорить «символическая», но что это значит?» Отличный вопрос! Символический означает, что он содержит указатель на другую ссылку. Если у вас кружится голова, я вас понимаю. Мне потребовалось немало времени на поиск информации в Google и чтение, чтобы окончательно понять, что такое HEAD. Вот отличная аналогия, взятая из статьи, объясняющей Git HEAD:

Хорошая аналогия — это проигрыватель и клавиши воспроизведения и записи на нём как HEAD. Когда начинается запись звука, лента движется вперёд, проходя мимо головки, записывая на неё. Кнопка остановки останавливает запись, всё ещё указывая на точку последней записи, и точка, где остановилась записывающая головка, — это место, где она продолжит запись снова при нажатии кнопки записи. Если мы перемещаемся, указатель головки перемещается в разные места; однако, когда снова нажимается кнопка записи, запись начинается с точки, на которую указывала головка при нажатии кнопки записи.

Выполните cat .git/HEAD, чтобы вывести содержимое файла. Вы должны увидеть что-то вроде этого:

cat .git/HEAD
ref: refs/heads/master

Это логично, потому что мы находимся на ветке master. HEAD, по сути, всегда будет ссылкой на последний коммит в текущей ветке Git.

Полезный совет: вы можете выполнить git diff HEAD, чтобы просмотреть разницу между HEAD и рабочим каталогом.

Как работает слияние в Git

Одной из наиболее часто используемых функций совместной работы в Git является ветвление. Разработчику часто приходится «отрезать» ветку от основной ветки master при работе над функцией, а затем позже слить этот код обратно в ветку master. Само по себе это звучит бессмысленно, но предоставляет мощную возможность, когда несколько разработчиков работают над кодовой базой одновременно.

Разработчики часто отправляют свой код в удалённый репозиторий с помощью git push и выполняют git merge через интерфейс для открытия запроса на включение (pull request).

Два (или более) разработчика могут работать над независимыми функциями, не мешая друг другу благодаря веткам. Когда один разработчик заканчивает работу, он может слить свою ветку в основную ветку master, добавив в неё свои коммиты. Другой разработчик затем может обновить свою ветку с последними изменениями из master (через rebase или merge) и затем слить свою ветку в master, когда будет готов.

Функционально git merge довольно прост. Когда вы запускаете git merge, Git изменяет коммит, на который указывает HEAD. До слияния HEAD указывает на последний коммит в ветке по умолчанию или master. После слияния HEAD указывает на последний коммит сливаемой ветки. Это просто под капотом, но в результате основная ветка получает все свои оригинальные коммиты плюс новые коммиты из сливаемой ветки. Если вы хотите сравнить две ветки без их слияния, вы можете использовать git diff с именем ветки для сравнения. Если у двух веток возникает конфликт слияния, разработчик должен указать Git, изменения какой ветки сохранить.

Как работает Git stash

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

Самая частая причина, по которой я использую git stash, — это когда мне нужно сделать git fetch или git pull чтобы стянуть изменения из удалённого репозитория, но у меня есть локальные изменения, не готовые к коммиту. Я не могу выполнить pull с незакоммиченными изменениями и, возможно, не готов к git push. Использование git stash помогает сохранить эти изменения, чтобы я мог выполнить git pull.

Вы можете узнать, какие изменения у вас есть, но не были зафиксированы, выполнив git status.

Если вы выполните git stash, эти изменения будут помещены в стек сохранённых изменений для репозитория. Вы можете увидеть все сохранённые изменения, выполнив:

git stash list

Когда вы хотите вернуть изменения из stash обратно в рабочий каталог, у вас есть несколько вариантов. Стек stash — это структура данных «первый вошёл — первый вышел», поэтому первым доступным изменением будет самое недавно сохранённое. Если вы выполните git stash apply, изменения будут помещены в ваш рабочий каталог. Если вы выполните git stash pop, изменения будут помещены в рабочий каталог и удалены из стека, давая вам доступ к следующему сохранённому изменению, если оно вам понадобится. В любом случае выполнение команды git status может показать вам подтверждение возврата изменений.

Выходим за рамки вопроса «как работает Git?»

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

Если вы хотите продолжить углубляться в Git, а также лучше понять, как работают некоторые распространённые команды, я настоятельно рекомендую книгу «Pro Git», которая доступна бесплатно. Мы также написали множество полезных советов и трюков по Git, если вы хотите быстро перейти к более сложным командам Git.

Оригинал статьи читайте по ссылке