Научно-образовательный IT-форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Научно-образовательный IT-форум » Система управления версиями Git


Система управления версиями Git

 

СИСТЕМА УПРАВЛЕНИЯ ВЕРСИЯМИ GIT

Гибадуллин Р.Ф., КНИТУ-КАИ, landwatersun@mail.ru

 

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

 

Введение

 

Система управления версиями (СУВ) – это программное обеспечение, призванное автоматизировать работу с историей файла (или группой файлов), обеспечить мониторинг изменений, синхронизацию данных и организовать защищенное хранилище проекта. Вкратце, основная задача системы управления версиями – упростить коллективную работу с изменяющейся информацией[1].

 

Рассмотрим пример. Пусть есть проект, над которым работают несколько отделов программистов и вы являетесь координатором (или руководителем). В любой момент вы можете сделать откат данных до необходимой версии, ограничить доступ определенным участникам проекта на обновление данных, вести наблюдение за работой каждого программиста, давать рекомендации посредством создания отдельных веток, не затрагивая при этом ветки программистов, и другие действия.

 

СУВ обеспечивает надежное хранение данных благодаря наличию множества клонов. Так, например, при повреждении какого-либо файла вы своевременно можете заменить его копией. Для уменьшения объема данных проекта используется дельта-компрессия – такой вид хранения, при котором хранятся не сами версии файла, а только изменения между последовательными ревизиями.

 

Особенность Git и сферы ее применения

 

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

 

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

 

 

Рисунок 1. Показательный пример работы в Git

 

Архитектура Git

 

Каждый файл проекта в Git состоит из имени и содержания. Имя – это первые 20 байтов данных, которые наглядно записываются сорока символами в шестнадцатеричной системе счисления. Данный ключ получается хешированием содержимого файла с помощью криптографического алгоритма SHA1 (отсюда и название SHA1-хеш). Так, например, сравнив два имени, мы можем почти со стопроцентной вероятностью сказать, что они имеют одинаковое содержание. Хеш позволяет точно определить поврежденность файлов. Например, сравнив хеш содержимого с именем, мы можем вполне точно сказать, повреждены данные или нет. Стоит упомянуть о так называемых коллизиях. "Вполне точно определить поврежденность" означает, что существуют такие файлы, различные по содержанию, SHA1-хеш которых совпадает. Вероятность таких коллизий очень мала, и по предварительной оценке равна 2 в -80-й степени (~ 10 в -25-й степени).

 

Объекты Git

 

В Git существуют четыре основных типа объекта: Blob, Tree, Commit, References. Рассмотрим вкратце каждый из них.

 

Blob (Binary Large Object) включает содержимое файла и собственный SHA1-хеш.

 

Tree – тип данных, который содержит:

- собственный SHA1-хеш;

- SHA1-хеш blob’ов и/или деревьев;

- права доступа;

- символьное имя объекта.

По своей сути дерево является аналогом директории. Он задает иерархию файлов проекта.

 

Commit – тип данных, который содержит:

- собственный SHA1-хеш;

- ссылку ровно на одно дерево;

- ссылку на предыдущий commit;

- имя автора и время создания commit’а;

- произвольный кусок данных (блок можно использовать для электронной подписи или, например, для пояснения изменений commit’а).

Данный объект призван хранить версию группы файлов в определенный момент времени. Commit’ы можно объединять (merge), разветвлять (branch) или, например, установить линейную структуру, тем самым отражая иерархию версий проекта.

 

Reference – тип данных, содержащий ссылку на любой из четырех типов объекта (Blob, Tree, Commit и References). Основная его цель – указывать на объект и являться синонимом файла, на который он ссылается. Тем самым повышается понимание структуры проекта. Очень неудобно оперировать бессмысленным набором символов в названии, ссылку же, в отличие от SHA1-хеша, можно именовать так, как удобнее разработчику.

 

Из ссылок, в свою очередь, можно выделить подобъекты: Ветвь, Тег. Рассмотрим их.

 

Ветвь (Head, Branch) – символьная ссылка, которая указывает на последний в хронологии commit определенной ветви и хранит SHA1-хеш объекта.

 

Тег (Tag) – тип данных, который в отличие от ветвей неизменно ссылается на один и тот же объект типа blob, tree, commit или tag. Его, в свою очередь, можно разделить на легковесный (light tag) и тяжеловесный или аннотированный (annotated tag). Легкий тег, кроме неизменности ссылки, ничем не отличается от обычных ветвей, т.е. содержит лишь SHA1-хеш объекта, на который ссылается. Аннотированный тег состоит из двух частей:

первая часть содержит собственный SHA1-хеш;

вторая часть состоит из:

- SHA1 объекта, на который указывает аннотированный тег;

- тип указываемого объекта (blob, tree, commit или tag);

- символьное имя тега;

- дата и время создания тега;

- имя и e-mail создателя тега;

- произвольный кусок данных (данный блок можно использовать для электронной подписи или для пояснения тега).

 

Хранение данных. Репозиторий

 

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

- каждый объект Git хранится в виде архива;

- для всей иерархии файлов применяется последовательная дельта-компрессия.

 

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

 

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

 

Ситуации с изменениями состояний могут быть следующие: вы добавляете файл в ваш рабочий каталог, говорите системе, чтобы она добавила его под версионный контроль (по умолчанию ему будет присвоен статус неизмененный). Затем работаете с файлом (изменяете его), вследствие чего он переходит в состояние изменен. Чтобы получить снимок файла в определенный момент времени, предварительно необходимо перевести файл в состояние подготовленный для коммита. При этом версия файла индексируется (запоминается текущее состояние). Когда вы захотите зафиксировать файлы в хронологии, Git сохранит именно те версии файлов, которые были проиндексированы.

 

 

Рисунок 2. Состояния файла в репозитории

 

Слияние и разделение ветвей

 

Для понимания слияния и разделения обратимся к примеру. Представим себе момент разработки проекта, когда главной поставленной целью является скорость работы программы. Один из возможных тактических вариантов решения – разбить разработчиков на две группы, каждая из которых будет решать одну и ту же задачу. При этом ветвь истории проекта должна раздвоиться. Данная процедура называется ветвление (branch). Действие разветвления ветви – это простое создание её копии, которая впоследствии будет иметь свою историю.

 

Пусть мы получили два уже законченных результата одной и той же задачи, над которой работали две группы программистов. Как нам быть? Посмотреть, чей код быстрее и надежнее? Это слишком просто, но не всегда лучший выход. Хорошее решение – это, немного разобравшись в коде и файлах, разбить их на подзадачи или блоки кода. И только тогда уже выявлять сильные и слабые стороны данных кусочков. Данный процесс объединения двух целых в одно называется слияние (merge). Процесс объединения двух версий и есть ключевой момент ведения проекта. Отличительная черта Git – это максимально достоверный и довольно быстрый способ решения задачи ветвления.

 

Инструментальные средства

 

Рассмотрим некоторые инструментальные средства для работы с репозиторием и продемонстрируем приемы работы с ними.

 

Для полноценной работы нам потребуются:

- Bitbucket (или GitHub) для создания удаленного репозитория;

- SmartGit для работы с репозиториям в графическом режиме.

Саму систему Git можно скачать через источник[2].

 

Bitbucket[3] ("ведро битов") – веб-сервис для хостинга проектов и их совместной разработки, основанный на системе контроля версий Mercurial и Git. По назначению и предлагаемым функциям аналогичен GitHub, однако GitHub не предоставляет бесплатные "закрытые" репозитории в отличие от Bitbucket. Зарегистрироваться в данном сервисе можно через источник[4].

 

SmartGit/Hg[5] – кроссплатформенный визуальный клиент системы управления версиями Git, Mercurial и Subversion, который работает на Windows, Mac OS X и Linux. Интерфейс главного окна SmartGit/Hg похож на файловые менеджеры: слева в нем дерево каталогов, а справа – таблица файлов, содержащая все файлы репозитория и рабочего дерева каталогов. История открытого репозитория отображается в отдельном окне, также называемом Log window, а многие команды Git/Mercurial могут быть выполнены как через главное окно, так и через Log window, например, переключение между ветками, слияние веток, создание веток и тегов и т.д. "В коробку" также включены дополнительные инструменты:

- diff-вьювер файлов, которые показывает два файла рядом, подсвечивает различия между ними и позволяет их редактировать.

- Так называемый "решатель конфликтов", которыми обычно являются 3-х сторонние слияния. Используется для разрешения конфликтов слияния.

- Встроенный SSH-клиент.

Скачать и установить SmartGit можно через источник[6].

 

Описав основные инструменты, перейдем к основным шагам работы.

 

1. Создадим репозиторий в Bitbucket.

 

 

Рисунок 3. Диалоговое окно создания репозитория в Bitbucket

 

2. Возьмем ссылку на наш репозиторий.

 

 

Рисунок 4. Диалоговое окно с указанием ссылки на репозиторий

 

3. Запустим SmartGit, выберем Repository|Clone и заполним необходимые поля.

 

 

Рисунок 5. Диалоговое окно клонирования в SmartGit (Repository)

 

В следующем окне доступны две опции клонирования «Include Submodules» и «Fetch all Heads and Tags». C выбранной опцией «Include Submodules» все субмодули автоматически проинициализируются и обновятся. Без этой опции можно проинициализировать субмодули позже вручную через Remote|Submodule|Initialize. Однако инициализация оставит каталог субмодулей пустым. Для полной загрузки субмодулей потребуется подтянуть их посредством команды pull. Если отметить опцию «Fetch all Heads and Tags», то SmartGit после создания папки проекта скачает все ветки и теги для репозитория.

 

 

Рисунок 6. Диалоговое окно клонирования в SmartGit (Selection)

 

4. Выберем директорию, куда клонировать репозиторий.

 

 

Рисунок 7. Диалоговое окно клонирования в SmartGit (Local Direcrory)

 

После клонирования пустого репозитория можно увидеть следующее сообщение.

 

 

Рисунок 8. Диалоговое окно с указанием сообщения о склонированном пустом репозитории

 

Далее создадим Commit (зафиксируем изменения). Каждый Commit «запоминает», что именно вы изменили, и позволяет вернуть прежнее состояние файлов в любой момент времени. Чтобы создать Commit, нужно что-то изменить в проекте, например, добавить парочку файлов в папку с проектом.

 

Выберем оба файла и нажмем сначала «Stage», а потом на «Commit». Кнопка «Stage» индексирует выбранные файлы. Если вы хотите создать Commit для двух файлов, а изменили, предположим, целых 5, достаточно выбрать эти два файла, нажать «Stage», а после – на «Commit». Таким образом, только выбранные два файла попадут в Commit.

 

 

Рисунок 9. Индексирование выбранных файлов

 

После этого появится окошко, где нужно будет ввести комментарий Commit’а. Обычно туда пишут то, что было изменено, добавлено, удалено и так далее.

 

 

Рисунок 10. Диалоговое окно «Commit»

 

После чего следует нажать на кнопку «Commit» (или на «Commit & Push»). Кнопка «Commit & Push» делает тоже самое, но еще и проталкивает (заливает) изменения в удаленный репозиторий. Внизу в списке веток появится основная ветка «master».

 

А сейчас сделаем что-нибудь с нашим проектом и откатим эти изменения. Для этого:

- удалим «Файл 1.txt», отредактируем файл «Файл 2.txt» и добавим новый файл «Файл 3.txt»;

- сделаем еще один Commit;

- Выберем Commit, к которому хотим откатиться и нажмем на «Reset».

 

 

Рисунок 11. Диалоговое окно «Log»

 

В следующем окне предлагается выбрать, какой именно «Reset» мы хотим выполнить.

 

 

Рисунок 12. Диалоговое окно «Reset»

 

Soft reset сбрасывает только Commit’ы. Индекс и физические изменения в файлах остаются. Mixed reset работает так же, как и софт, но еще удаляет индекс файлов. Hard reset удаляет Commit’ы, индекс и физические изменения в файлах. Рекомендуется применять hard reset аккуратно, чтобы не удалить лишнего.

 

Ниже представлено исходное состояние проекта после выполнения команды hard reset.

 

 

Рисунок 13. Диалоговое окно репозитория после выполнения команды hard reset

 

Как видите, все изменения в файлах пропали, а точнее, все вернулось к состоянию первого Commit’а.

 

Теперь немного о создании веток. Зачем они вообще нужны? Ветка позволяет сохранить текущее состояние кода и экспериментировать. Например, вы пишите новый модуль. Логично делать это в отдельной ветке. Звонит начальство и говорит, что в проекте ошибка и срочно нужно исправить, а у вас модуль не дописан. Как же заливать нерабочие файлы? Просто переключитесь на рабочую ветку без модуля, исправьте ошибку и заливайте файлы на сервер. А когда «опасность» миновала – продолжайте работу над модулем. И это один из многих примеров пользы веток.

 

Попробуем создать свою ветку. У нас уже есть одна, это «master». Она создается автоматически (если отсутствует), когда вы делаете первый Commit. Создадим еще одну ветку и назовем ее «new_feature_1». Нажмите F7 или правым кликом внизу во вкладке «Branches» на надпись «Local Branches» и в выпадающем списке выберите «Add branch».

 

 

Рисунок 14. Диалоговое окно «Add Branch»

 

Нажмите «Add branch & Switch», чтобы сразу переключиться на созданную ветку. Теперь вы можете создавать новые Commit’ы, изменять файлы и не беспокоиться, так как у вас всегда есть ветка «master», в которую можно вернуться. Когда вы переключаете ветку, Git меняет локальные файлы на те, которые есть в этой ветке. То есть, если вы создадите новую ветку, поменяете что-то в файле, а потом переключитесь на ветку «master», то все проделанные изменения будут удалены. Если же переключиться обратно в созданную ветку – изменения вернутся.

 

Не обойдем без внимания такие операции, как merge и rebase, так как они наиболее распространенные.

 

Начнем с merge. Добавим пару Commit’ов в ветку «new_feature_1». При этом в первом Commit’e зафиксируем редакцию первого файла, а во втором Commit’e – второго файла. Если открыть диалоговое окно «Log», то можно наблюдать линейную структуру дерева из-за отсутствия изменений в ветке «master». Далее переключимся в ветку «master», так в нее мы будем сливать проделанные изменения. После чего нажимаем правым кликом на ветку, с которой хотим слить изменения и выбираем «Merge».

 

 

Рисунок 15. Выполнение слияния

 

Далее SmartGit спросит, каким именно образом нужно слить ветки. Выбрать следует «Create Merge-Commit».

 

 

Рисунок 16. Диалоговое окно «Merge»

 

В этом случае Git делает слияние, используя при этом два снимка состояния репозитория, на которые указывают вершины веток, и общий для этих двух веток снимок-прародитель. Git создаёт новый снимок состояния, который является результатом слияния, и автоматически создаёт новый Commit, который указывает на этот новый снимок состояния. Такой Commit называют Commit-слияние, так как он является особенным из-за того, что имеет больше одного предка. Дерево на нижнем рисунке демонстрирует результат такого слияния.

 

 

Рисунок 17. Дерево Commit’ов веток после слияния

 

Красные символы «1>», расположенные справа от «master», указывают на то, что в ней имеется один Commit, который можно протолкнуть на удаленный репозиторий. Сделать это можно нажатием правым кликом на ветку «master» и выбором «Push».

 

 

Рисунок 18. Проталкивание на удаленный репозиторий Commit’а ветки «master»

 

После этого на удаленном репозитории в Bitbucket можно видеть следующую картину изменений.

 

 

Рисунок 19. Дерево изменений в веб-сервисе Bitbucket после слияния

 

В отличие от merge операция rebase позволяет придать дереву изменений простоту, вытягивая Commit’ы веток в одну линию. Иногда споры, что же лучше merge или rebase доходят до холивара. В конечном счете выбор за вами, однако этот выбор не может быть продиктован уровнем владения тем или иным инструментом. Обоснованный выбор можно сделать, только когда для вас не составит труда работать и в том и в другом стиле. Прежде чем показать, как проделать данную операцию в SmartGit, обратимся к теории[7].

 

Пусть у вас есть две ветки – «master» и «feature», «feature» была создана от «master» в состоянии A и содержит в себе Commit’ы (коммиты) C, D и E. В ветку «master» после отделения от нее ветки «feature» был сделан 1 коммит B.

 

 

Рисунок 20. Дерево изменений до rebase

 

После применения операции rebase «master» в ветке «feature», дерево коммитов будет иметь вид.

 

 

Рисунок 21. Дерево изменений после rebase

 

Обратите внимание, что коммиты C', D' и E' не равны C, D и E, они имеют другие хеши, но изменения (дельты), которые они в себе несут, в идеале точно такие же. Отличие в коммитах обусловлено тем, что они имеют другую базу (в первом случае – A, во втором – B), отличия в дельтах, если они есть, обусловлены разрешением конфликтных ситуаций, возникших при rebase. Об этом чуть подробнее далее.

 

Давайте разберемся с механикой этого процесса, как именно дерево 1 превратилось в дерево 2?

 

Перед выполнением rebase мы находимся в ветке «feature», то есть наш заголовок (HEAD) смотрит на указатель «feature», который в свою очередь смотрит на коммит E. Идентификатор ветки «master» мы передаем в команду как аргумент: git rebase master.

 

Для начала git находит базовый коммит – общий родитель этих двух состояний. В данном случае это коммит A. Далее двигаясь в направлении вашего текущего HEAD git вычисляет разницу для каждой пары коммитов, на первом шаге между A и С, назовем ее ΔAC. Эта дельта применяется к текущему состоянию ветки «master». Если при этом не возникает конфликтное состояние, создается коммит C', таким образом, C' = B + ΔAC. Ветки «master» и «feature» при этом не смещаются, однако, HEAD перемещается на новый коммит (C'), приводя репозиторий состояние «отделеной головы» (detached HEAD).

 

 

Рисунок 22. C' = B + ΔAC

 

Успешно создав коммит C', git переходит к переносу следующих изменений – ΔCD. Предположим, что при наложении этих изменений на коммит C' конфликта не возникает. В итоге после применения изменений ΔDE будет создан последний коммит E', указатель ветки «feature» будет установлен на коммит E', а HEAD станет показывать на ветку «feature». Rebase окончен, старые коммиты C, D и E больше не нужны.

 

 

Рисунок 23. Окончание операции Rebase

 

Для закрепления теории давайте перейдем в SmartGit, попробуем проделать операцию rebase и убедимся в описанном.

 

В ветке «master» создадим коммит, зафиксировав редакцию первого файла. Затем переключимся в ветку «new_feature_1» и создадим в ней пару коммитов, зафиксировав редакцию первого и второго файлов. Дерево изменений после этого будет выглядеть следующим образом.

 

 

Рисунок 24. Дерево изменений перед выполнением rebase

 

Далее нажмем правым кликом на ветку «master» и выберем «Rebase HEAD to …».

 

 

Рисунок 25. Действия для выполнения rebase

 

В диалоговом окне нажмите на «Rebase HEAD to». После этого мы неизбежно получаем информацию о возникшем конфликте в первом файле, так как он подвергался редакции в обеих ветках.

 

 

Рисунок 26. Возникший конфликт при выполнении rebase

 

Для разрешения конфликта кликаем правой клавишей мыши на причинный файл и выбираем «Conflict Solver» (см. рис. 27). После разрешения конфликта индексируем файл и продолжаем приостановленный rebase нажатием на кнопку «Continue», как показано на рисунке 28.

  

 

Рисунок 27. Разрешение конфликта при выполнении rebase

 

Рисунок 28. Продолжение приостановленного rebase

 

Дерево изменений после выполнения операции rebase будет выглядеть следующим образом.

 

 

Рисунок 29. Дерево изменений по выполнения rebase

 

Обратите внимание, что ветка «new_feature_1» до этого не отравлялась на удаленный репозиторий, поэтому мы не наблюдаем красного символа « > » справа от названия ветки. Исправить данную ситуацию и освежить локальными изменениями удаленный репозиторий можно, используя команду push.

 

 

Рисунок 30. Передача ветки «new_feature_1» на удаленный репозиторий

 

Заключение

 

Итак, мы рассмотрели систему управления версиями Git, разобрали основные понятия и практические аспекты использования Git. И скажем честно, в статье не ставилась цель – глубоко проработать все команды Git и рассмотреть все возможности SmartGit, а скорее сделать краткое руководство для тех, кто вступает на путь изучения и практического использования Git. Для более подробного изучения советую обратиться к руководству[8], а также по ссылкам к источникам, которые встречались в данной статье.

 



[1] Распределенная система управления версиями Git [Электронный ресурс] / Под ред. Р. З. Хамитова. – Электрон. дан. – IBM developerWorks, 2010. – Режим доступа: https://www.ibm.com/developerworks/ru/library/l-git_1/, свободный. – Загл. с экрана.

[2] Git --distributed-even-if-your-workflow-isnt [Электронный ресурс] / – Электрон. дан. – GitHub, Inc., 2017. – Режим доступа: https://git-scm.com, свободный. – Загл. с экрана.

[3] Bitbucket [Электронный ресурс] / Материал из Википедии – свободной энциклопедии. – Электрон. дан. – Wikimedia Foundation, Inc., 2017. – Режим доступа: https://ru.wikipedia.org/wiki/Bitbucket, свободный. – Загл. с экрана.

[4] Bitbucket [Электронный ресурс] / – Электрон. дан. – Atlassian, Inc., 2017. – Режим доступа: https://bitbucket.org, свободный. – Загл. с экрана.

[5] SmartGit/Hg [Электронный ресурс] / Материал из Википедии – свободной энциклопедии. – Электрон. дан. – Wikimedia Foundation, Inc., 2017. – Режим доступа: https://ru.wikipedia.org/wiki/SmartGit/Hg, свободный. – Загл. с экрана.

[6] Git Client SmartGit [Электронный ресурс] / – Электрон. дан. – Syntevo, 2017. – Режим доступа: http://www.syntevo.com/smartgit, свободный. – Загл. с экрана.

[7] Git Rebase: руководство по использованию [Электронный ресурс] / Электрон. дан. – TechMedia, 2017. – Режим доступа: https://habrahabr.ru/post/161009, свободный. – Загл. с экрана.

[8] Pro Git book [Электронный ресурс] / Электрон. дан. – GitHub, Inc., 2017. – Режим доступа: https://git-scm.com/book/ru/v1, свободный. – Загл. с экрана.


Вы здесь » Научно-образовательный IT-форум » Система управления версиями Git