Post

Типичный Git workflow: main, develop, feature и Pull Request

Типичный 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-ветке.

This post is licensed under CC BY 4.0 by the author.