Дарья Меленцова
Разработчица в команде инфраструктуры Яндекса, действующий автор курса «DevOps для эксплуатации и разработки»
Из этой статьи вы узнаете
Зачем нужны системы контроля версий
Откуда взялся Git
Как создать свой репозиторий на GitHub и внести в него изменения
Что такое fork, branch и другие интересные слова из мира Git
Как создать свой Pull Request
Вступление
Бывает, что начинающие разработчики проблематично осваивают Git и не с первого захода понимают логику работы сервиса. Но стоит создать пару репозиториев или, ещё лучше, погрузиться в реальную историю по установке стартапа на рельсы DevOps, как работа с ветками станет дружелюбной, а PR и MR больше не вызовут путаницы. Ошибки в любом случае появятся, но вы будете к ним готовы!
Начало
Когда вы пишете первую программу, всё кажется таким лаконичным, простым и понятным. Но по мере развития ваша программа обрастает новой функциональностью, становится сложнее и больше. Потом и вовсе появляются первые баги. И было бы здорово помнить или иметь возможность смотреть историю изменений, что добавили или убрали в коде, по какой причине мог появиться баг.
Первое и самое просто решение — «А давайте перед каждым изменением сохранять копию программы (просто копировать папку с кодом)?»
На самом деле это будет работать, но до поры до времени. Проект продолжит расти и станет полезным не только вам, но и вашим друзьям, которые захотят добавить в код что-то своё. В рядах программистов прибывает, и надо как-то договариваться, кто какой кусочек кода трогает, а потом ещё синхронизировать изменения, чтобы все фичи добрались до прода.
Настал звёздный час для систем контроля версий, которые запоминают, какое изменение и в каком файле было сделано, а также могут показать историю этих изменений.
Про Git
Существует несколько систем контроля версий: Git, Subversion, Team Foundation Server, Mercurial. Сегодня познакомимся с Git — самой популярной из них, по скромному признанию более 90% разработчиков.
Git появился 7 апреля 2005 года и был создан для управления разработкой ядра Linux. Кстати, создал его тот самый Линус Торвальдс, а сегодня его развитием и поддержкой занимается Дзюн Хамано.
Git — это распределённая система управления версиями: есть один сервер, через который разработчики обмениваются кодом. Разработчик копирует (клонирует) проект к себе на локальную машину, делает изменения и сохраняет их на удалённый сервер. При необходимости другие разработчики могут скопировать эти изменения к себе.
История и копия проекта хранятся локально и чаще всего не нужна дополнительная информация с других клиентов. Вы можете работать с репозиторием и при отсутствии интернета (например, в самолёте), а когда он появится, просто загрузить изменения в удалённый репозиторий на выделенном сервере.
Если у разработчика сломается компьютер, то проект не потеряется, а будет лежать на выделенном сервере. Такой выделенный сервер можно поднять и настроить самостоятельно либо использовать готовые решения.
GitHub — крупнейший веб-сервис, который позволяет заниматься совместной разработкой с использованием Git и сохранять изменения на своих серверах. На самом деле функциональность GitHub намного больше, но сейчас нас интересует только совместная разработка и история изменений. Ещё есть Gitlab, Bitbucket и другие, но мы будем использовать GitHub как самый популярный в настоящее время.
Предварительная настройка
Займёмся предполётной подготовкой.
Для начала зарегистрируйтесь на GitHub: задайте логин, почту и придумайте пароль. После «Создать аккаунт» не забудьте проверить почту и подтвердить её (опрос от Github после подтверждения почты можно пропустить).
В GitHub есть разграничение прав на работу с репозиториями. Можно задавать различные политики: сделать репозиторий публичным и приватным, ограничить права кругу пользователей или кому-то одному, например, разрешить просматривать репозиторий, но не изменять в нём данные.
Для того чтобы сервис определил, кто вы и имеете ли право работать с тем или иным репозиторием, нужно представиться — пройти процесс аутентификации.
GitHub поддерживает безопасность за счёт двух сетевых протоколов, HTTPS и SSH, и вся работа с сервисом происходит через один из них.
Работать с GitHub будем через терминал по SSH. Для этого один раз сгенерируем специальные ключи и добавим один из них в наш аккаунт на GitHub.
Можно работать и через HTTPS, но нужно будет каждый раз вводить пароль и специальный token.
Пара слов про SSH и как он работает. SSH — это сетевой протокол для зашифрованного соединения между клиентом и сервером, через который можно безопасно передавать данные.
При подключении используется пара ключей — открытый (публичный, public) и закрытый (приватный, private). Пользователь создаёт пару ключей при помощи специальной команды и сохраняет закрытый ключ у себя, а открытый кладёт на сервер (в нашем случае на GitHub). А работает это всё благодаря асимметричному шифрованию.
Алгоритм следующий: отправитель (GitHub) шифрует сообщение публичным ключом и передаёт сообщение клиенту (нам), а мы его расшифровываем при помощи приватного ключа, который предусмотрительно сохранили у себя. То, что зашифровано публичным ключом, расшифровать сможет только приватный ключ.
Давайте создадим пару ключей и добавим открытый ключ на GitHub.
Чтобы создать пару ключей, в терминале нужно ввести команду, задать путь для хранения ключей и указать пароль к ключу (необязательно).
Далее будем опираться на то, что путь для ключей дефолтный и пароль на ключи не установлен.
Пароль для ключей нужен как дополнительная мера безопасности, если вдруг ваш приватный ключ попадёт не в те руки.
$ ssh-keygen
Generating public/private rsa key pair.
# путь до ключей, в скобках путь по умолчанию
Enter file in which to save the key (/Users/ifireice/.ssh/id_rsa):
# пароль для ключей, при задании пароля в консоли не отображается ничего, даже звёздочки
# если нажать Enter, ничего не вводя, пароль будет пустым
Enter passphrase (empty for no passphrase):
# повторите пароль
Enter same passphrase again:
# после появится сообщение такого вида
Your identification has been saved in /Users/ifireice/.ssh/id_rsa
Your public key has been saved in /Users/ifireice/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:Zu+HkZPC4ZP0veRmVjuKgylVvljHBNO8mHs+ieFFPvs ifireice@ifireice-osx
The key's randomart image is:
+---[RSA 3072]----+
| o |
| o o |
| = . |
| o + + |
| +S* X |
| oB.@ X . |
| . O.# * . |
| . +.*.% o |
| . o*.+E. |
+----[SHA256]-----+
Бинго, ключи сгенерированы: в заданной директории появятся два файла, id_rsa и id_rsa.pub.
Теперь надо добавить публичный ключ в аккаунт на GitHub:
# выведите содержимое публичного ключа в консоль
$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDJfHIi73sKd6cqm3RwKuY1zl46aAaE6X9Gp
/6zJiY3BiJj95oJjPdpfpPhVFWLIbmT8zFAtOLbX9N4C3b0enHUzgMacP/Kl4AbrAkhLqaua9iD
VNxxiTVxADG1M5525oc/eAvx7y0pXIb9ouWdYJSKa8/TUYFhWlCzV2quY9SA0FaMs7eY41+KWYpG.....
tA0oGxv+7WmXQmQzleLIRG13KQ+VAbL2vabdPcRoGuZavh0smOr/GtVSnLdspZ5RgONMSPWlF2I1YHMR
Q7CIKPs= ifireice@ifireice-osx
$
Скопируйте ключ от символов ssh-rsa и до конца файла и вставьте его в ваш аккаунт на GitHub.
Ну что, с настройкой GitHub пока закончили, осталось установить Git на компьютер. Сделать это можно по официальной инструкции (выберите пункт для вашей ОС).
Терминология
Самое время пополнить ваш Git-словарик, прежде чем создадим первый Pull Request.
Репозиторий (repository) — директория проекта, который отслеживается Git. В директории хранится проект, история изменений и мета-информация проекта (в скрытой директории .git
).
Индекс — хранилка, где лежат имена файлов и их изменения, которые должны быть в следующем коммите. По факту индекс — просто файл. В индекс файлы сами не попадают, их нужно явно добавлять при помощи git add
.
Коммит (commit) — это фиксация изменений в истории проекта (изменения, которые внесены в индекс). Коммит хранит изменённые файлы, имя автора коммита и время, в которое был сделан коммит. Кроме того, каждый коммит имеет уникальный идентификатор, который позволяет в любое время к нему откатиться. Можете считать коммит этакой точкой сохранения.
Ветка (branch) — последовательность коммитов. По сути — ссылка на последний коммит в этой ветке. Ветки не зависят друг от друга — можно вносить изменения в одну, и они не повлияют на другую (если вы явно этого не попросите). Работать вы начинаете в одной ветке — main, увидите чуть позже.
Форк (Fork) — собственное ответвление (fork
) какого-то проекта. Это означает, что GitHub создаст вашу собственную копию проекта, данная копия будет находиться в вашем пространстве имён, и вы сможете легко делать изменения путём отправки (push) изменений.
Пул-реквест — pull request PR (пиар, он же merge request MR(мр)) — предложение изменения кода в чужом репозитории. Допустим, вы забрали к себе чужой репозиторий, поработали с ним и теперь хотите, чтобы ваши изменения попали в оригинальный репозиторий — тогда вы создаёте создаёте PR с просьбой добавить ваши изменения в репозиторий.
Начало работы
Начнём с простого — создадим свой репозиторий и сделаем наш первый коммит.
Зададим параметры:
(1) Repository name: имя репозитория.
(2) Description: описание репозитория.
(3) Тип репозитория: Public (публичный) или Private (приватный). Сейчас выберем публичный — кто угодно может видеть содержимое репозитория.
(4) Ставим галку на «Создать README файл». В этом файле в формате MarkDown описывают проект или прочую документацию. Именно содержимое этого файла можно увидеть, когда заходим на главную страницу репозитория. Примеры 1, 2, 3.
(5) Если известно, на каком языке будет проект, можем добавить шаблон
.gitignore
для этого языка. Сейчас у нас нет какого-то языка, поэтому не будем создавать.gitignore
.(6) Выбираем тип лицензии для нашего кода. В лицензии оговариваются права на проект. Стоит обратить внимание на BSD 3 или MIT, так как они предоставляют хороший баланс прав и ответственности.
(7) По умолчанию имя основной ветки в GitHub носит имя main, но до недавнего времени было master
.
И нажимаем кнопку «Create repository». Успех, у нас есть первый репозиторий!
А что будет, если не добавим README и .gitignore?
На самом деле ничего страшного не произойдёт, но придётся выполнить ещё ряд шагов, чтобы проинициализировать git-репозиторий, прежде чем начать с ним работать.
Итак, мы создали репозиторий на удалённом сервере, теперь пора «забрать» его к себе на локальную машину и внести какие-то изменения.
Чтобы забрать репозиторий, его надо склонировать к себе при помощи команды git clone
и пути до репозитория.
Для начала получим путь до репозитория.
Теперь идём в консоль, переходим в директорию, где хотим хранить проекты, и выполним (git@github.com:ifireiceya/MyFirstRepo.git
— путь, который мы скопировали ранее):
$ git clone git@github.com:ifireiceya/MyFirstRepo.git
Cloning into 'MyFirstRepo'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (4/4), done.
Переходим в новый каталог, где теперь лежит копия нашего проекта с GitHub:
$ cd MyFirstRepo
Можем посмотреть, что уже есть в этой директории:
$ ls -a
.git LICENSE README.md
Видим два знакомых файла,LICENSE
и README.md
, а также одну скрытую директорию .git.
В .git
хранится метаинформация и вся история для проекта. На каждый проект есть только одна директория .git
, и лежит она в корне проекта.
$ ls .git
HEAD # указатель на вашу активную ветку
config # персональные настройки для проекта
description # описание проекта
hooks # pre/post action hooks
index # индексный файл
logs # история веток проекта (где они располагались)
objects # ваши объекты (коммиты, теги и тд)
packed-refs refs # указатели на ваши ветки разработки
Давайте немного настроим Git под себя. Делать это нужно только один раз, потом настройки сохранятся, но при необходимости их можно изменить.
При установке Git была добавлена утилита git config
, которая позволяет просматривать и изменять большинство параметров работы Git’а. Если речь о данных пользователя или способе работы репозитория — git config
будет самым удобным способом настройки.
Настроим имя пользователя и адрес электронной почты. Эта информация важна, потому что включается в каждый коммит.
Поэтому в терминале переходим в Git-репозиторий, для которого задаём настройки, и выполняем:
$ git config user.name "Дарья Меленцова"
$ git config user.email ifireice@example.com
# Если добавить опцию --global, то эти настройки запишутся в настройки пользователя и будут действовать для всех проектов.
# Мы выполняем эту команду без параметра --global, чтобы настройки касались только вашего проекта.
Чтобы настраивать ещё больше параметров с помощью
git config
, прочитайте эту документацию.
Вносим изменения
Теперь нужно внести изменения в проект. Но перед этим посмотрим две полезных команды:
git status
— показывает текущее состояние файлов в репозитории (какие файлы изменились, удалились, добавились);git log
— показывает историю изменений (это про зафиксированные изменения, то есть коммиты).
Выполним эти команды и посмотрим, что они выведут для нашего репозитория.
Вбиваем git status
:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
И видим, что у нас нет изменений. Говорят, «нет коммитов в репозитории». Конечно, мы успели только клонировать репозиторий и ещё ничего не делали.
Идём дальше и пробуем git log
, который покажет, что в проекте был только один Initial commit
— когда мы создавали репозиторий с README.md
:
$ git log
commit 9ae1cbcc77f3b64d604612d4a599bdbb8b1cf204 (HEAD -> main, origin/main, origin/HEAD)
Author: ifireiceya <117034707+ifireiceya@users.noreply.github.com>
Date: Mon Oct 31 00:01:05 2022 +0300
Initial commit
(END)
Убедились, что у нас нет неучтённых изменений. Пора бы уже что-то сделать!
Открываем любимый текстовый редактор и создаём новый файл с именем hw.py
.
Это будет небольшая программа на Python, которая при запуске печатает «Hello World!» (внезапно):
$ vi hw.py
print("Hello World!")
Отлично, код написан и даже хранится локально в нашем репозитории (мы же в директории проекта всё делали).
Теперь наша задача — сохранить изменения в «оригинальный» (удалённый) репозиторий. Для этого нужно:
Познакомить Git с новым файлом, то есть добавить файл в индекс —
git add
.Зафиксировать (закоммитить) изменения —
git commit
.Синхронизировать изменения с сервером —
git push
.Посмотреть в репозиторий и убедиться, что всё сработало.
Делаем!
Появился файл hw.py
, но он красный. Паника! Всё сломалось?!
Нет, всё идёт по плану, но прежде чем продолжить, стоит обсудить состояние файлов с точки зрения Git’а.
По мнению Git’а, файл может пребывать в одном из четырёх состояний:
Неотслеживаемый (untracked).
Изменённый (modified) — файл, в котором есть изменения, но он ещё не добавлен в коммит (не зафиксирован).
Отслеживаемый (staged) — файл, который добавили в индекс.
Зафиксированный (committed) — файл уже сохранён в локальной базе, и в нём не было изменений с последнего коммита.
В связке с состоянием файлов используют три основных секции проекта:
Рабочая директория (working directory) — это директория, которая содержит в себе то, с чем вы работаете, или то, что вы извлекли из истории проекта в данный момент. Рабочая директория — это временное место, где вы можете модифицировать файлы, а затем выполнить коммит.
Область индексирования (staging area) — индекс-файл в каталоге Git, который содержит информацию о том, что попадёт в следующий коммит.
Каталог Git — место, где Git хранит метаданные и базу объектов вашего проекта. Помните ещё про
.git
?
Что происходит на практике
Мы добавили новый файл hw.py
и видим, что у него состояние untracked
, то есть неважно, что мы делаем с файлом, Git проигнорирует любые изменения в нём.
Чтобы Git начал следить за изменениями в файле, его нужно добавить в индекс.
Для этого используем команду git add <имя файла>
.
Кстати, вы заметили, что Git довольно дружелюбный и часто подсказывает команды, которые нужно выполнить?
$ git add hw.py
# если нужно добавить много файлов и не хочется описывать, можно использовать команду
# git add .
# но стоит точно понимать, что добавляем, иначе придётся потом удалять файлы из индекса
# кстати, для удаления используется команда git rm, но стоит почитать доку перед использованием
И ещё не забывайте о файле .gitignore
, где перечислены папки и файлы репозитория, которые Git не должен отслеживать и синхронизировать их состояние (не добавлять их в индекс). Обычно в него добавляют файлы логов, результаты сборки и другое. Поддерживает шаблоны. Кстати, .gitignore — тоже файл, который надо добавить в индекс.
Если файл попадает в правила
.gitignore
, то он не появится вgit status
.Если файл был добавлен в индекс, а потом добавлено правило для файла в
.gitignore
— файл всё равно будет отслеживаться и его надо явно удалить из индекса.
Посмотрим, как изменилось состояние нашего файла:
Давайте зафиксируем изменения, так как наша задача сейчас решена: мы написали программу на Python и хотим сказать Git, что вот теперь мы закончили работать с файлом и надо запомнить текущее состояние.
Для этого нужно закоммитить файл с помощью команды git commit
.
При создании обычно лаконично описывают коммит, используя ключ -m
:
$ git commit -m "add python hello world"
[main 6d8a5c3] add python hello world
1 file changed, 1 insertion(+)
create mode 100644 hw.py
Пара слов о том, как писать сообщения для коммитов:
максимум 50 символов;
осознанно и понятно, как будто пишете для человека, который должен понять, что происходит внутри коммита;
сообщение стоит начинать с заглавной буквы;
если меняли код, пишите исходный код в сообщении.
$ git log
$ git push
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 341 bytes | 341.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:ifireiceya/MyFirstRepo.git
9ae1cbc..6d8a5c3 main -> main
Предлагаем проверить, что наши изменения есть на GitHub. Идём в репозиторий и смотрим на него.
Задача немного посложнее
Всё здорово, но мы не всегда создаём репозитории, и часто нам нужно добавлять новые фичи или исправления в уже существующий репозиторий, да ещё и в чужой.
Например, есть у нас любимый опенсорсный проект, в который мы хотим принести добро и закрыть им какой-нибудь Issue.
В учебных целях используем репозиторий из этой статьи на GitHub. Там нужно исправить опечатку, которую нашли в статье. Например, вот эту очепятку.
Но будем делать это с позиции внешних пользователей в чужом репозитории.
Репозиторий хранится в ifireice/git, а изменения делает пользователь ifireiceya.
Пользователь ifireiceya не имеет доступа в ifireice/git, и ему придётся работать через Fork, то есть нужно сперва сделать копию этого репозитория к себе и вести разработку у себя, а потом отправить в основной репозиторий запрос на изменения — Pull Request.
Но обо всём по порядку. Сначала делаем Fork.
Откроется окно для создания нового форка (fork).
Изменится владелец репозитория (1), и опционально можно изменить описание проекта.
Вы можете делать любые изменения в собственной копии, и они никак не отразятся в оригинальном репозитории.
Теперь клонируем форк-репозиторий к себе на машинку и ведём разработку.
Только мы будем работать чуть-чуть по-другому, не как с нашим репозиторием.
В нашем репозитории мы работали в ветке main
и все изменения сохраняли в ней.
А теперь у нас большой проект, и над ним одновременно могут трудиться несколько разработчиков. Чтобы разные изменения не смешивались в кучу и чтобы один разработчик не мешал другому, разработка ведётся в разных независимых версиях продукта — ветках (branch). Когда работа закончена, все изменения сливаются в одну главную ветку.
Клонируем репозиторий и создаём отдельную ветку, в которой будем устранять опечатку:
$ git clone git@github.com:ifireiceya/git.git
$ cd git
# создадим новую ветку и сразу же переключимся на неё, чтобы работать там
$ git checkout -b fix-misprint
Switched to a new branch 'fix-misprint'
Чтобы посмотреть, какие ветки есть в проекте и какая сейчас активна, используется команда git branch
:
$ git branch
* fix-misprint
main
# * помечена текущая активная ветка
На самом деле практика работать с ветками распространена не только при разработке в чужих репозиториях (collaborators), куда у вас нет доступа, но и в своих. Есть несколько стратегий выделения веток, но об этом не сейчас. Просто знайте, что есть ветки и с их помощью удобно вести разработку.
Можем посмотреть, что изменилось с последнего коммита, при помощи команды git diff (красным с “-” то, что было, зелёным с “+” — то, что стало):
$ git diff
$ git status
On branch fix-misprint
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -am "Поправили опечатку"
[fix-misprint 188caa7] Поправили опечатку
1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
fatal: The current branch fix-misprint has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin fix-misprint
Упс, fatal. Читаем подсказку от Git и выполняем:
$ git push --set-upstream origin fix-misprint
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 402 bytes | 402.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'fix-misprint' on GitHub by visiting:
remote: https://github.com/ifireiceya/git/pull/new/fix-misprint
remote:
To github.com:ifireiceya/git.git
* [new branch] fix-misprint -> fix-misprint
Branch 'fix-misprint' set up to track remote branch 'fix-misprint' from 'origin'.
Успех!
Почему произошёл fatal: простой git push предполагает, что ветка, которую отслеживает текущая локальная ветвь, уже существует на удалённом сервере. У нас ветка новая и была создана только локально, поэтому нам нужно её создать, указав --set-upstream
.
Проверим, что ветка появилась на GitHub.
Форк сделали, ветку отвели, ошибку поправили, осталось отправить изменения в оригинальный репозиторий.
Для этого создаём Pull request.
И увидим такую картину.
(1) репозиторий, в который хотим добавить изменения. — ifireice/git
(2) ветка, в которую хотим добавить изменения — main
(3) репозиторий, из которого хотим добавить изменения — ifireiceya/git
(4) ветка, из которой хотим добавить изменения — main
На этом сейчас наша работа завершена. Ответственные за репозиторий посмотрят ваши изменения, примут их, или попросят что-то дописать, или отклонят изменения.
Будем считать, что у нас всё хорошо и наши изменения приняли без вопросов.
Как это выглядит на стороне ревьюверов:
На этом пока всё. Увидимся в следующих выпусках про Git и не только.
Что изучили
Поговорили про системы контроля версий
Настроили себе GitHub
Создали первый репозиторий и внесли в него изменения
Узнали про ветки, форки и остальное
Сделали первый ПР
Что НЕ изучили
Как забрать файл из другой ветки (Cherry-pick)
Что ещё почитать
Отличная книга Pro Git (есть на русском и английском)
Trunk-Based Development — стратегия отведения веток
GitFlow — другая стратегия отведения веток