Типичный Git workflow: main, develop, feature и Pull Request
В реальных проектах работа обычно строится не вокруг отдельных команд, а вокруг workflow — набора правил, которые позволяют нескольким разработчикам одновременно работать над одним репозиторием, проходить code review и безопасно выпускать изменения.
Даже в небольших pet-проектах такой подход быстро начинает окупаться: история коммитов становится понятнее, новые задачи не мешают стабильной версии приложения, а исправления замечаний после ревью не превращаются в хаотичный набор изменений.
Рассмотрим типичный workflow, который одинаково хорошо подходит для pet-проектов, учебных проектов, небольших команд и большинства Java-разработчиков, использующих GitHub.
Что получится в итоге
В качестве базовой схемы будем использовать три типа веток:
1
2
3
4
5
6
7
8
main
│
└── develop
│
├── feature/add-tests
├── feature/refactor-logging
└── feature/add-statistics
- main — стабильная версия проекта;
- develop — ветка активной разработки;
- feature/* — отдельные задачи, которые после завершения вливаются в
developи затем попадают вmain.
Настройка Git
Перед началом работы стоит проверить, от какого имени Git будет создавать коммиты. В каждом коммите сохраняется автор:
1
Author: Some Author <some-author@example.com>
Именно эта информация попадает в историю репозитория и затем отображается на GitHub, GitLab или в Gerrit.
Проверить текущие настройки можно так:
1
2
git config --get user.name
git config --get user.email
Если команды ничего не вывели, значит имя и email для текущего репозитория не настроены.
Глобальная настройка
Глобальная настройка применяется ко всем репозиториям пользователя:
1
2
git config --global user.name "Some Author"
git config --global user.email "some-author@example.com"
Проверить глобальные значения можно так:
1
2
git config --global --get user.name
git config --global --get user.email
Такой вариант удобен, если все проекты ведутся от одного аккаунта.
Локальная настройка репозитория
Если для разных проектов используются разные аккаунты, лучше настраивать автора локально внутри конкретного репозитория:
1
2
git config user.name "study-account"
git config user.email "study@example.com"
Эти настройки сохраняются в .git/config текущего проекта и действуют только в нем. Например, в личном pet-проекте можно использовать один email:
1
2
git config user.name "Some Author"
git config user.email "some-author@example.com"
А в учебном репозитории — другой:
1
2
git config user.name "study-account"
git config user.email "study@example.com"
Почему это важно
Если забыть проверить автора, можно случайно создать коммиты от рабочей учетной записи в личном проекте или наоборот. Технически Git это не сломает, но история репозитория станет менее аккуратной, а вклад на GitHub может не привязаться к нужному профилю.
Поэтому перед первой задачей в новом репозитории полезно выполнить короткий чек:
1
2
3
git config --get user.name
git config --get user.email
git remote -v
Эти три команды сразу показывают, кто будет автором коммитов, какой email попадет в историю, куда будут отправляться изменения.
Работа с несколькими аккаунтами и SSH
Практически любой Git-хостинг (GitHub, GitLab, Gerrit) поддерживает работу по протоколу SSH (Secure Shell). Особенность этого протокола заключается в том, что аутентификация выполняется не по логину и паролю, а с помощью пары криптографических ключей:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────┐
│ Private key │
│ ~/.ssh/id_ed25519 │
│ │
│ Хранится только │
│ на вашем компьютере│
└─────────┬───────────┘
│
│ SSH
│
┌─────────▼───────────┐
│ GitHub / Gerrit │
│ │
│ Public key │
│ Добавлен в профиль │
└─────────────────────┘
Как это работает
При создании ключей генерируется пара:
- Private key — приватный ключ, который хранится только на вашем компьютере и никогда никому не передается.
- Public key — публичный ключ, который добавляется в настройки GitHub, GitLab или Gerrit.
При подключении сервер отправляет случайный запрос (challenge), который может быть подписан только соответствующим приватным ключом. Если подпись успешно проверяется с помощью публичного ключа, сервер убеждается, что подключается именно владелец учетной записи, и предоставляет доступ.
Таким образом, пароль при каждом git push или git pull вводить не требуется — вся аутентификация происходит автоматически на уровне протокола SSH.
Один ключ или несколько?
Например, если используется:
- личный GitHub;
- учебный GitHub;
- рабочий Gerrit.
Можно добавить один и тот же публичный ключ во все три сервиса и действительно работать без каких-либо ограничений Технически это абсолютно допустимый вариант, но удобнее придерживаться простого правила:
Один аккаунт — один SSH-ключ.
Это делает конфигурацию более понятной и избавляет от многих вопросов при дальнейшей работе с Git.
Когда одного ключа достаточно
Если используется один GitHub-аккаунт и несколько личных репозиториев, скорее всего, создавать дополнительные ключи нет никакого смысла.
Достаточно одной пары:
1
2
3
Private key ─────────► Public key
~/.ssh/id_ed25519 GitHub
Когда появляются несколько ключей
Ситуация меняется, если используются разные учетные записи или разные организации.
Например:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Личный GitHub
▲
│
id_ed25519_personal
Учебный GitHub
▲
│
id_ed25519_study
Рабочий Gerrit
▲
│
id_rsa_company
Теперь каждый сервис использует собственный ключ.
Какие преимущества это дает
Во-первых, становится проще понимать, какой ключ используется для конкретного подключения.
Во-вторых, можно независимо заменить или удалить любой ключ, не затрагивая остальные сервисы. Например, если рабочий ноутбук был заменен или доступ в рабочий Gerrit больше не нужен, достаточно удалить только рабочий ключ — личный и учебный GitHub продолжат работать без каких-либо изменений.
В-третьих, такая схема хорошо масштабируется. При появлении нового сервиса не приходится менять существующую конфигурацию — достаточно создать еще одну пару ключей и добавить новый Host в ~/.ssh/config.
Файл ~/.ssh/config
Когда ключей становится несколько, SSH необходимо подсказать, какой из них использовать. Для этого существует файл: ~/.ssh/config. В нем каждому подключению можно задать имя (Host) и ключ. В результате получится такая схема:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~/.ssh/config
┌─────────────────────────────────┐
│ github-personal │
│ ~/.ssh/id_ed25519_personal │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ github-study │
│ ~/.ssh/id_ed25519_study │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ gerrit-company │
│ ~/.ssh/id_rsa_company │
└─────────────────────────────────┘
Далее остается только настроить SSH, чтобы при обращении к github-personal использовался один ключ, а при обращении к github-study — другой.
Конфигурация SSH
Все подключения хранятся в файле ~/.ssh/config. Например:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Host gerrit.company.local
HostName gerrit.company.local
Port 29418
User developer
IdentityFile ~/.ssh/id_rsa_company
Host github-study
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_study
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
Теперь каждый Host становится отдельной точкой входа.
Проверка подключения
После настройки SSH полезно убедиться, что каждая учетная запись действительно использует свой ключ. Предположим, в файле ~/.ssh/config определены два подключения:
1
2
github-personal
github-study
Проверяем личный GitHub
1
ssh -T git@github-personal
При успешной настройке GitHub ответит примерно так:
1
2
Hi personal-user! You've successfully authenticated,
but GitHub does not provide shell access.
Имя пользователя в приветствии позволяет сразу понять, что используется именно личная учетная запись.
Проверяем учебный GitHub
1
ssh -T git@github-study
Ответ должен быть другим:
1
2
Hi study-user! You've successfully authenticated,
but GitHub does not provide shell access.
Это означает, что SSH выбрал другой ключ и успешно авторизовал вторую учетную запись.
Почему это полезно
Если обе команды выводят разных пользователей, значит конфигурация настроена корректно:
1
2
3
4
5
ssh -T git@github-personal
→ Hi personal-user!
ssh -T git@github-study
→ Hi study-user!
Если же обе команды приветствуют одного и того же пользователя, скорее всего используется один и тот же SSH-ключ.
Проверка текущего remote
Перед первым push полезно убедиться, куда именно отправляются изменения.
1
git remote -v
Например:
1
2
origin git@github-study:username/project.git (fetch)
origin git@github-study:username/project.git (push)
или:
1
2
origin git@github.com:study-account/project.git (fetch)
origin git@github.com:study-account/project.git (push)
Смена remote
Предположим, проект должен работать через другую учетную запись. Достаточно изменить URL:
1
2
git remote set-url origin \
git@github-study:study-account/project.git
Проверяем результат:
1
git remote -v
Получим:
1
2
origin git@github-study:study-account/project.git (fetch)
origin git@github-study:study-account/project.git (push)
Полезный чек перед первым push
Если проект только что создан или был перенесен между аккаунтами, обычно выполняют четыре команды:
1
2
3
4
git config --get user.name
git config --get user.email
git remote -v
ssh -T git@github.com
Они позволяют быстро убедиться:
- коммиты будут создаваться от нужного пользователя;
- используется правильный email;
- изменения отправятся в нужный репозиторий;
- SSH-аутентификация работает корректно.
Такая привычка избавляет от большинства проблем при работе с несколькими аккаунтами.
Структура веток: main, develop и feature
В небольших pet-проектах часто можно встретить разработку прямо в main. Пока проект состоит из нескольких коммитов, это кажется удобным. Однако уже после первой доработки, исправления ошибок и code review становится понятно, что такой подход быстро приводит к путанице.
Поэтому даже для небольших проектов удобно использовать три типа веток:
1
2
3
4
5
6
7
main
│
└── develop
│
├── feature/add-tests
├── feature/refactor-logging
└── feature/add-statistics
main
main — это ветка стабильных релизов.
Она должна содержать код, который можно считать готовым к использованию. В большинстве случаев разработка непосредственно в main не ведется. Все изменения попадают сюда через Pull Request из develop.
Получается следующая схема:
1
2
3
4
5
6
7
feature/*
│
▼
develop
│
▼
main
Таким образом main становится точкой сборки стабильных версий проекта.
develop
develop — ветка интеграции.
Именно здесь собираются изменения из нескольких feature-веток. Например:
1
2
3
4
5
develop
|
├── feature/add-tests
├── feature/refactor-logging
├── feature/add-statistics
После прохождения code review изменения последовательно вливаются в develop. Когда накопленный набор изменений готов к публикации, создается Pull Request:
1
2
3
4
5
develop
│
▼
main
После Merge ветка main становится новой стабильной версией проекта.
feature/*
Каждая новая задача выполняется в отдельной ветке, например:
1
2
3
4
feature/add-tests
feature/refactor-logging
feature/add-statistics
feature/update-readme
Создается она всегда от develop:
1
2
3
4
git checkout develop
git pull
git checkout -b feature/add-tests
или
1
2
3
4
git switch develop
git pull
git switch -c feature/add-tests
В процессе работы появляются коммиты в feature/*:
1
2
3
4
5
feat: add WordComparator tests
test: add WordHintGenerator tests
refactor: improve test readability
После завершения работы создается Pull Request ветки feature/* в develop:
1
2
3
4
feature/add-tests
│
▼
develop
После Merge feature-ветка удаляется. Следующая задача снова начинается от актуального develop.
После Merge Pull Request
После успешного Merge работа с Git еще не заканчивается. Чтобы следующая задача начиналась с актуального состояния репозитория, полезно выполнить несколько простых действий.
Обновляем main
Переключаемся на основную ветку и получаем последние изменения с сервера:
1
2
git checkout main
git pull
Теперь локальная ветка синхронизирована с удаленной:
1
2
3
4
origin/main
│
▼
main
Синхронизируем develop
Если develop обновляется через отдельный Pull Request, достаточно выполнить:
1
2
git checkout develop
git pull
Если же используется локальная интеграция, можно выполнить Merge из main:
1
2
git checkout develop
git merge main
После этого обе постоянные ветки содержат актуальные изменения:
1
2
3
main
│
└── develop
Удаляем временную ветку
После Merge feature-ветка больше не нужна.
Локально ее можно удалить командой:
1
git branch -d feature/add-tests
При необходимости удаляется и удаленная ветка:
1
git push origin --delete feature/add-tests
Начинаем новую задачу
Новая задача всегда создается от актуального develop:
1
2
3
4
git checkout develop
git pull
git checkout -b feature/next-task
После этого рабочее окружение снова готово к разработке.
Почему это удобно
Такой подход дает несколько преимуществ:
mainвсегда остается стабильной;developсодержит актуальную разработку;- каждая задача изолирована;
- Pull Request содержит изменения только одной задачи;
- историю коммитов легко читать спустя месяцы.
Даже если проект состоит из одного разработчика, такая организация быстро становится привычкой и практически полностью совпадает с workflow, который используется в большинстве команд.
Типичный цикл разработки
После настройки репозитория ежедневная работа обычно сводится к одному и тому же сценарию.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Новая задача
│
▼
feature/my-task
│
▼
Коммиты
│
▼
Push
│
▼
Pull Request
│
▼
Code Review
│
▼
Исправления
│
▼
Merge в develop
│
▼
Pull Request
│
▼
Merge в main
Работа над задачей
Каждая новая задача начинается с актуальной ветки develop:
1
2
3
4
git checkout develop
git pull
git checkout -b feature/my-task
Во время разработки создаются небольшие осмысленные коммиты:
1
2
git add .
git commit -m "feat: implement my task"
Pull Request
После завершения работы ветка публикуется:
1
git push -u origin feature/my-task
и создается Pull Request в develop.
Если во время ревью появляются замечания, они исправляются обычными коммитами:
1
2
3
git add .
git commit -m "refactor: apply review feedback"
git push
Создавать новый Pull Request не нужно — новые коммиты автоматически попадут в существующий. После исправления замечаний discussion можно закрыть кнопкой Resolve conversation.
Merge
После успешного code review Pull Request вливается в develop, а временная feature-ветка удаляется. Когда набор изменений готов к публикации, создается еще один Pull Request:
1
2
3
4
develop
│
▼
main
После Merge main становится новой стабильной версией проекта, а цикл начинается заново.
Подготовка к следующей задаче
После Merge полезно привести локальный репозиторий в актуальное состояние:
1
2
3
4
5
git checkout main
git pull
git checkout develop
git pull
или, если develop обновляется локальным Merge:
1
2
git checkout develop
git merge main
После этого можно удалить уже влитую feature-ветку:
1
git branch -d feature/my-task
и начать новый цикл разработки:
1
git checkout -b feature/next-task
Такой workflow позволяет поддерживать main в стабильном состоянии, использовать develop как ветку интеграции и изолировать каждую задачу в собственной feature-ветке.