Что такое коммит в git
Раскладываем Git по полочкам: терминология
Что это за зверь?
Git — распределённая система управления версиями. Git поддерживает быстрое разделение и слияние версий, включает инструменты для визуализации и навигации по нелинейной истории разработки.
Ничего не понятно, давай еще раз
Какие такие версии?
Версия — это состояние файла (или нескольких файлов) в какой-то конкретный момент времени. Например, пустой файл (1), тот же файл с каким-то текстом (2) и этот же файл, в котором была исправлена опечатка (3) — три разные версии одного файла, которые были получены последовательной модификацией (изменением) файла.
Системы чего.
Система управления версиями — программа, позволяющая сохранять состояние файлов (те самые версии), возвращаться к ранее сохраненному состоянию, сохранять последовательность изменений внесенных в файлы, отменять или заново применять эти изменения, отслеживать авторство изменений.
Что там с этими версиями делают?
Разделение версий — независимые изменения одного файла.
Версионность на примере текстового файла
Например, у нас есть файл с каким-то текстом (версия этого файла). Файл отправляется на проверку, там обнаруживается и исправляется опечатка (получаем новую версию файла). Независимо от этого в старый (неисправленный) файл дописывается еще что-то (получаем еще одну версию этого файла). Т.е., на данный момент у нас есть два разных файла (две версии одного файла), которые были независимо друг от друга созданы на основе одной общей версии.
Слияние версий — объединение двух и более независимых версий. Для примера выше, слиянием будет объединение двух существующих версий нашего файла в одну — файл, в котором будет и новый текст, и исправленная опечатка.
А при чем тут история?
История разработки — совокупность всех версий файлов, над которыми ведется работа. Историей разработки в данном случае будет список изменений:
добавление изначального текста
добавление нового текста
объединение двух версий файла (при выполнении слияния)
Нелинейная история — история, в которой изменения вносятся не одно за другим последовательно, а может быть внесено несколько независимых изменений на основе одной версии файла (исправление опечатки и добавление нового текста). Т.е. мы создаем две параллельные истории изменений файла.
Немного терминологии
Репозиторий (repository) — совокупность файлов, состояние которых отслеживается, и история их изменений. По факту, репозиторий — это проект, над которым ведется работа, и все изменения в этом проекте. Для отслеживания состояния файла его необходимо добавить в репозиторий.
Коммит (commit) — сохраненное состояние (версия) файлов репозитория.
Ветка (branch) — последовательность коммитов (история изменения состояния репозитория). Каждый коммит в ветке имеет «родителя» (parent commit) — коммит, на основе которого был получен текущий. В репозитории может быть несколько веток (в случаях, когда к одной версии репозитория применяется несколько независимых изменений).
HEAD — указатель на текущий коммит (указатель на состояние, в котором репозиторий находится на данный момент).
Мастер (master, main) — основная ветка репозитория, создается автоматически при создании репозитория.
Мердж (слияние, merge) — объединение двух или более веток. В процессе мерджа изменения с указанной ветки переносятся (копируются) в текущую.
Целевая ветка мерджа — ветка, изменения с которой объединяются с текущей веткой.
База слияния (merge base) — последний общий коммит двух веток.
Мердж коммит (merge commit) — коммит, который создается автоматически по завершению процесса слияния веток. Мердж коммит содержит в себе все изменения целевой ветки мерджа, которые отсутствуют в текущей (все коммиты целевой ветки, которые начиная с базы слияния, но не включая её).
Слияние перемоткой (fast-forward merge) — слияние веток, при котором в текущей ветке отсутствуют новые коммиты (последний коммит текущей ветки является базой слияния). При таком мердже текущая ветка просто переходит в состояние целевой ветки (указатель HEAD переносится на последний коммит целевой ветки). Мердж коммит при этом не создается.
Слияние без перемотки (non fast-forward merge) — слияние, при котором новые коммиты (относительно базы слияния) присутствуют как в текущей, так и в целевой ветках.
В пустом репозитории, в основной ветке, создаем пустой файл, добавляем в репозиторий (теперь git будет отслеживать состояние файла) и коммитим (коммит А).
Добавляем в файл текст, коммитим еще раз (коммит B).
Создаем новую ветку (как бы копируя состояние репозитория), вносим изменения в файл и снова коммитим (коммит С).
Возвращаемся на предыдущую ветку (теперь репозиторий находится в состоянии, которое сохранилось при коммите B, без изменений, которые вносились в новой ветке).
Вносим новые изменения, создаем новый коммит (D).
История изменений при создании новой ветки
После этого новая ветка останется в состоянии С (со своими собственными изменениями), а в основной ветке будут изменения из обеих веток (изменения, внесенные коммитами С и D). Эта ветка перейдет в состояние Е.
Возможна и обратная ситуация, когда изменения из основной ветки вливаются в новую (мердж производится из новой ветки).
При этом мы можем вернуться в новую ветку и продолжить работу в ней (внести новые изменения и создать коммит F).
История изменений после слияния и нового коммита
Мердж конфликт (merge conflict) — ситуация, когда при слиянии веток в один или несколько файлов вносились независимые изменения. В некоторых случаях (например, если изменялись разные, не пересекающиеся части одного файла) git способен самостоятельно решить, как выполнять слияние таких файлов. Если автоматически это сделать не удалось — возникает конфликт. В таком случае необходимо самостоятельно указать, как выполнять слияние конфликтующих версий (решить конфликт, resolve merge conflict). Изменения, внесенные в процессе решения конфликта автоматически попадают в мердж коммит.
Чекаут (checkout) — переход на другое (существующее) состояние репозитория (на другой коммит или ветку). При этом все файлы в репозитории возвращаются в состояние, в котором они находились на момент указанного коммита. Если перед переходом в репозиторий были внесены изменения, которые были добавлены в репозиторий, но не попали в коммит — они будут перенесены «поверх» состояния после перехода. Как и при мердже, git попробует применить эти изменения к новому состоянию автоматически, при неудаче — возникает конфликт и изменения необходимо применить вручную.
Дифф (diff) — разница двух состояний (коммитов, веток, подготовленных или модифицированных файлов).
Трехсторонний дифф (three-way diff) — дифф, возникающий при мердже и решении конфликтов. Является разницей трех состояний: состояния репозитория в текущей ветке, состояния в целевой ветке слияния и общего состояния между этими ветками (состояния в базе слияния).
Черри-пик (cherry-pick) — процесс добавления в текущую ветку одного (или нескольких) коммитов из другой ветки, без необходимости выполнять слияние веток.
Реверт (revert) — отмена внесенных изменений (коммита или группы коммитов). В процессе реверта создается дополнительный коммит, который так же можно отменить при необходимости (вернув репозиторий в изначальное состояние). Реверт мердж коммита позволяет отменить выполненное ранее слияние веток.
Ребейз (rebase) — перенос изменений текущей ветки «поверх» другой ветки. При этом все коммиты текущей ветки, которых нет в целевой, удаляются из текущей и заново создаются в целевой ветке (последовательно применяются к состоянию в целевой ветке). Поскольку ребейз пересоздает коммиты заново и меняет существующую историю, его использование не рекомендуется при командной разработке. Ребейз в ветке, над которой работает несколько человек, может привести к потере чужих изменений и/или невозможности корректно выполнить слияние.
В данном случае коммит Е содержит изменения, которые вносились с момента последнего слияния или создания ветки (с момента коммита В). Т.е. коммит Е содержит в себе изменения из D. При выполнении ребейза git понимает, что эти изменения (E) уже присутствуют в целевой ветке (и окажутся в текущей после ребейза), потому нет необходимости копировать коммит Е, дублируя этим D.
Командная разработка
Наличие удаленного репозитория может быть полезным и при одиночной разработке: оно позволяет синхронизировать состояние проекта на разных компьютерах и просто сохранить проект на внешнем сервере.
Есть два варианта синхронизации изменений:
пулл (pull) — слияние состояния удаленного репозитория и локального (обычно — в отдельной ветке). Пулл может выполняться как для одной и той же ветки (с одинаковым именем), так и для разных. Пулл являет собою обычный мердж, но целевая ветка при этом находится не в том же репозитории, в котором выполняется пулл, а в удаленном. Как следствие, при пулле так же создается мердж коммит, пулл можно отменить (заревертить) и в его процессе может возникнуть мердж конфликт.
пуш (push) — обратный пуллу процесс. При пуше изменения из локального репозитория переносятся в удаленный. Пуш обновляет состояние текущей ветки в удаленном репозитории и не является мерджем (не создает дополнительные коммиты и не может привести к конфликтам). Если в ветке удаленного репозитория присутствуют коммиты, которых нет в локальном репозитории, сигнализируется ошибка о несовпадении истории изменений (non fast-forward merge), пуш выполнить не получится. В таком случае необходимо сначала синхронизировать состояние локального репозитория (получить недостающие коммиты с помощью пулла), и только после этого повторить процесс пуша.
Нередко возникает необходимость обновить информацию о состоянии удаленного репозитория (существующих ветках и коммитах в них) без выполнения слияния (пулла). Такой процесс называется фетчем (fetch). Таким образом, пулл является комбинаций фетча и мерджа: сперва обновляется информация о состоянии целевой ветки в удаленном репозитории, а затем ее изменения вливаются в текущую ветку в локальном репозитории.
основные понятия и термины
простые примеры терминов
применение git’a при командной разработке
некоторые проблемы, которые могут возникать при использовании git’a
Git на практике
Существует замечательная книга Pro Git, в которой подробно описаны все команды и возможности гита. Но после ее прочтения у многих остается непонимание того, как это все использовать на практике. В частности, у программистов разного уровня часто возникают вопросы о том, как работать с ветками в Git, когда их заводить и как мержить между собой. Порой мне попадались очень «оригинальные» и неоправданно усложненные схемы работы с гитом. В то время как в сообществе программистов уже сформировалась схема работы с гитом и ветками в нем. В этой статье я хочу дать краткий обзор основных моментов при работе с Git, и описать «классическую» схему работы с ветками. Многое из того что описано в этой статье будет справедливо и для других систем управления версиями.
Эта статья может быть полезна для программистов, которые только начинают осваивать Git, или какую-то другую систему управления версиями. Для опытных программистов эта статья покажется очень простой и банальной.
Для начала давайте разберемся с тем что такое ветка и коммит.
Коммит
Можно сказать, что коммит это основной объект в любой системе управления версиями. В нем содержится описание тех изменений, которые вносит пользователь в код приложения. В Git коммит состоит из нескольких так называемых объектов. Для простоты понимания можно считать, что коммиты это односвязный список, состоящий из объектов в которых содержаться измененные файлы, и ссылка на предыдущий коммит.
У коммита есть и другие свойства. Например, дата коммита, автор, комментарий к коммиту и т.п.
В качестве комментария обычно указывают те изменения, которые вносит этот коммит в код, или название задачи которую он решает.
Git это распределенная система управления версиями. Это значит, что у каждого участника проекта есть своя копия репозитория, которая находиться в папке “.git”, которая расположена в корне проекта. Именно в этой папке хранятся все коммиты и другие объекты Git. Когда вы работаете с Git, он в свою очередь работает с этой папкой.
Завести новый репозиторий очень просто, это делается командой
Таким образом у вас получиться новый пустой репозиторий. Если вы хотите присоединиться к разработке уже имеющегося проекта, то вам нужно будет скопировать этот репозиторий в свою локальную папку с удаленного репозитория. Делается это так:
Существует несколько основных областей в которых находиться код.
При этом можно использовать маски со звездочкой.
Потом вы делаете коммит в свой локальный репозиторий
git commit –m “Комментарий к коммиту”
Когда коммитов накопиться достаточно много, чтобы ими можно было поделиться, вы выполняете команду
После чего ваши коммиты уходят в удаленный репозиторий.
Если нужно получить изменения из удаленного репозитория, то нужно выполнить команду
После этого, в вашем локальном репозитории появятся те изменения, которые были отправлены другими программистами.
Код в рабочей области проекта образуется применением тех изменений, которые содержаться в коммитах. У каждого коммита есть свое имя, которое представляет собой результат хеш функции sha-1 от содержимого самого коммита.
Просмотреть коммиты можно при помощи команды
Формат ответа этой команды по дефолту не очень удобен. Вот такая команда выведет ответ в более читаемом виде
Что бы закончить просмотр нужно нажать на клавишу q
Посмотреть, что находиться в рабочей директории и staging area можно командой
Рабочую директорию можно переключить на предыдущее состояние выполнив команду
Только перед тем как это делать выполните git status и убедитесь, что у вас нет никаких локальных и не зафиксированных изменений. Иначе Git не поймет, как ему переключаться. git status подскажет вам что можно сделать с локальными изменениями что бы можно было переключиться. Этого правила следует придерживаться и при всяких других переключениях рабочей области.
Ветка
Ветка в Git это подвижный указатель на один из коммитов. Обычно ветка указывает на последний коммит в цепочке коммитов. Ветка берет свое начало от какого-то одного коммита. Визуально это можно представить вот так.
Сделать новую ветку и переключиться на нее можно выполнив команды
git pull
git checkout –b
Просто сделать ветку, не переключаясь на нее можно командой
переключиться на ветку
Важно понимать, что ветка берет свое начало не от ветки, а от последнего коммита который находиться в той ветке, в которой вы находились.
Ветка обычно заканчивается специальным merge коммитом, который говорит, что ветку нужно объединить с какой-то другой веткой. В merge коммите содержатся две ссылки на два коммита которые объединяются в одну ветку.
Существует другая ситуация при объединении веток, в которой merge может произойти без merge commit. Дело в том, что если в одной из веток не произошло никаких изменений, то необходимость в merge commit с двумя предками отпадает. В таком случае, при слиянии веток, Git просто сделает пометку о том, что дальше будут идти коммиты той ветки с которой эта ветка была объединена. Такая схема merge называется слияние-перемотка (fast-forward merge), визуально это можно представить вот так.
Во всех этих случаях, после того, как ветка объединяется с другой веткой, все коммиты сделанные в ней, попадают в ветку с которой она была объединена. Так же важно понимать, что merge это не двунаправленная операция. Если смержить ветку задачи в мастер ветку, то в мастер ветке появится код, который находился в ветке задачи, а в ветке задачи не появиться новый код из мастер ветки. Если нужно что бы это произошло, нужно смержить мастер ветку в ветку задачи.
Что бы смержить одну ветку в другую нужно вначале переключиться на ту ветку, в которую вы хотите смержить
Потом получить последние изменения сделанные в этой ветке выполнив
Затем выполнить команду
Так выглядит работа с ветками в общих чертах.
Популярные схемы работы с ветками в Git
Теперь можно описать популярные схемы работы с ветками в гите.
Ветки нужны для того, чтобы программисты могли вести совместную работу над проектом и не мешать друг другу при этом. При создании проекта, Git создает базовую ветку. Она называется master веткой. Она считается центральной веткой, т.е. в ней содержится основной код приложения.
Классическая схема работы с ветками
Обычно перед тем как взяться за решение какой-то задачи, программист заводит новую ветку от последнего рабочего коммита мастер ветки и решает задачу в этой новой ветке. В ходе решения он делает ряд коммитов, после этого тестирует код непосредственно в ветке задачи. А после того как задача решена, делают merge обратно в мастер ветку. Такую схему работы часто используют с юнит тестами и автоматизированным деплоем. Если юнит тесты будут покрывать весь код, то можно настроить деплой так, что вначале будут прогоняться все тесты в ветке задачи. А после этого, если они прошли успешно, будет происходить merge и деплой. При такой схеме можно добиться полной автоматизации при тестировании и деплои.
Именная ветка
Неопытные программисты заводят себе именную ветку и работают всегда в ней. Они решают по одной задачи за раз, и когда заканчивают решение одной из задач, делают новый Pull запрос через Web интерфейсе (об этом чуть ниже). Недостаток этого подхода в том, что так можно решать только одну задачу и нельзя быстро переключиться на решение другой задачи. Еще один недостаток в том, что ветки так со временем будут все сильнее расходиться и код в ветке программиста рано или поздно устареет относительно мастер ветки и его придется обновить. Для этого можно либо смержить мастер ветку в ветку программиста, либо завести новую ветку для этого программиста от последнего рабочего состояния в мастер ветке. Правда к тому времени, как это произойдет программист уже может освоить гит в достаточной мере что бы перейти на “классическую” схему работы. Таким образом эта схема имеет место быть для неопытных пользователей Git.
Схема с dev веткой
Другая схема очень похожа на классическую, только в ней помимо мастер ветки есть еще девелоперская ветка, которая деплоится на тестовый сервер. Такую ветку обычно называют dev. Схема работы при этом такая. Программист перед выполнением новой задачи заводит для нее ветку от последнего рабочего состояния в мастер ветке. Когда он заканчивает работу над задачей, то мержит ветку задачи в dev ветку самостоятельно. После этого, совместными усилиями задача тестируется на тестовом сервере вместе с остальными задачами. Если есть ошибки, то задачу дорабатывают в той же ветке и повторно мержат с dev веткой. Когда тестирование задачи заканчивается, то ВЕТКУ ЗАДАЧИ мержат с мастер веткой. Важно заметить, что в этой схеме работы с мастер веткой нужно мержить ветку задачи, а не dev ветку. Ведь в dev ветке будут содержаться изменения, сделанные не только в этой задаче, но и в других и не все эти изменения могут оказаться рабочими. Мастер ветка и dev ветка со временем будут расходиться, поэтому при такой схеме работы периодически заводят новую dev ветку от последнего рабочего состояния мастер ветки. Недостатком этого подхода является избыточность, по сравнению с классической схемой. Такую схему работы с ветками часто используют если в проекте нет автоматизированных тестов и все тестирование происходит вручную на сервере разработки.
Так же следует отметить что эти схемы работы можно комбинировать между собой, если в этом есть какая-то необходимость.
Pull запросы
С этим понятием имеется путаница. Дело в том, что в Git есть две совершенно разные вещи, которые можно назвать Pull запросом. Одна из них, это консольная команда git pull. Другая это кнопка в web интерфейсе репозитория. На github.com она выглядит вот так
Про эту кнопку и пойдет речь дальше.
Если программист достаточно опытный и ответственный, то он обычно сам сливает свой код в мастер ветку. В противном случае программист делает так называемый Pull запрос. Pull запрос это по сути дела запрос на разрешение сделать merge. Pull запрос можно сделать из web интерфейса Git, или при помощи команды git request-pull. После того как Pull запрос создан, остальные участники могут увидеть это, просмотреть тот код который программист предлагает внести в проект, и либо одобрить этот код либо нет. Merge через pull запросы имеет свои плюсы и минусы. Минус в том, что для тесной команды опытных программистов такой подход будет лишним. Это будет только тормозить работу и вносить в нее оттенки бюрократии.
С другой стороны, если в проекте есть не опытные программисты, которые могут сломать код, то Pull запросы могут помочь избежать ошибок, и быстрее обучить этих программистов наблюдая за тем какие изменения они предлагают внести в код.
Так же Pull запросы подходят для широкого сообщества программистов, работающих с открытым исходным кодом. В этом случае нельзя заранее сказать что-то о компетенции таких разработчиков и о том, что они хотят изменить в коде.
Конфликты
Конфликты возникают при мердже веток если в этих ветках одна и та же строка кода была изменена по-разному. Тогда получается, что Git не может сам решить какое из изменений нужно применить и он предлагает вручную решить эту ситуацию. Это замедляет работу с кодом в проекте. Избежать этого можно разными методами. Например, можно распределять задачи так, чтобы связанные задачи не выполнялись одновременно различными программистами.
Другой способ избежать этого, это договориться о каком-то конкретном стиле кода. Тогда программисты не будут менять форматирование кода и вероятность того, что они изменят одну и ту же строчку станет ниже.
Еще один хороший совет, который поможет вам избежать конфликтов при работе в команде, это вносить минимум изменений в код при решении задач. Чем меньше строчек вы поменяли, тем меньше вероятность что вы измените ту же самую строку что и другой программист в другой задаче.
После того, как в мастер ветке достигается состояние, которое можно считать стабильным оно отмечается тегом с версией этого состояния. Это и есть то что называют версией программы.
Делается это вот так
Что бы передать ветки в удаленный репозиторий нужно выполнить команду
Теги удобны еще и тем, что можно легко переключиться на то состояние кода которое отмечено тегом. Делается это с помощью все той же команды
Различные системы деплоя и автоматизированной сборки используют теги для идентификации того состояния, которое нужно задеплоить или собрать. Так сделано потому, что если мы будем собирать или деплоить код последней версии, то есть риск, что какой-то другой программист в этот момент внесет какие-то изменения в мастер ветку, и мы соберем не то что хотели. К тому же так будет проще переключаться между рабочими и проверенными состояниями проектов.
Если вы будете придерживаться этих правил и “классической” схемы работы с ветками, то вам будет проще интегрировать ваш Git с другими системами. Например, с системой непрерывной интеграции или с репозиторием пакетов, таким как packagist.org. Обычно сторонние решения и всякие расширения рассчитаны именно на такую схему работы с гитом, и если вы сразу начнете делать все правильно, то это может стать большим плюсом для вас в дальнейшем.
Это обзор основных моментов при работе с Git. Если вы хотите узнать про Git больше, то я вам посоветую прочитать книгу Pro Git. Вот здесь.
В этой статье была приведена упрощенная схема представления коммитов. Но перед тем как ее написать я решил разобраться как именно хранятся коммиты на диске. Если вас тоже заинтересует этот вопрос, то вы можете прочитать об этом вот здесь.