Git-стратегия для 50 разработчиков: внедрение Trunk-Based Development в банке

Исходная ситуация: хаос в 50 голов

Банк ЦифроБанк — цифровой банк с мобильным приложением на 2 млн пользователей и 15 backend-микросервисами. 50 разработчиков в 8 командах, монорепозиторий на 1.2 GB, GitLab Self-Hosted.

Проблемы, с которыми обратился клиент:

  • Merge-конфликты — разработчики тратили до 4 часов в неделю на разрешение конфликтов. Длинноживущие feature-ветки (2-4 недели) расходились с main на сотни коммитов.
  • Сломанный main — main ломался 3-5 раз в неделю. Отсутствовали branch protection rules, любой мог запушить напрямую.
  • Релизы-кошмары — release manager вручную собирал cherry-pick из десятков веток. Релиз занимал 2 дня, откат — полдня.
  • Нет единого процесса — 3 команды использовали GitFlow, 2 — GitHub Flow, 3 — «кто как хочет». Нет соглашения о коммит-мессаджах, нет автоматического версионирования.

Наша задача: внедрить единую Git-стратегию, интегрировать с CI/CD, автоматизировать релизный процесс и сократить time-to-production с 2 недель до 1 дня.

Сравнение стратегий: GitFlow vs GitHub Flow vs Trunk-Based

Мы провели воркшоп с тимлидами всех 8 команд, где разобрали три основных подхода:

КритерийGitFlowGitHub FlowTrunk-Based
Основные веткиmain + develop + release + hotfixmain + featuremain (trunk)
Время жизни feature-веткиДни-неделиДниЧасы (max 1 день)
Частота интеграцииПри merge в developПри merge в mainНесколько раз в день
СложностьВысокая (5 типов веток)НизкаяСредняя (нужны feature flags)
Подходит дляВерсионный софт, мобильные приложенияSaaS, непрерывный деплойВысокая скорость, CI/CD зрелость
Merge-конфликтыЧастые (длинные ветки)СредниеРедкие (короткие ветки)

Выбор: модифицированный Trunk-Based Development с short-lived feature branches (максимум 1 рабочий день). Причины:

  • 50 разработчиков в монорепе — длинноживущие ветки гарантируют конфликты
  • Банк уже имел зрелый CI/CD (GitLab CI) — можно деплоить часто
  • Мобильное приложение выпускается еженедельно — но backend деплоится непрерывно

Для мобильного приложения мы сохранили элемент GitFlow: release-ветки для подготовки версий в App Store/Google Play.

Protected branches и merge request policy

Первый шаг — защита main от прямых пушей. Мы настроили строгие правила в GitLab:

# GitLab Protected Branches Configuration
# Settings → Repository → Protected Branches

# Branch: main
# Allowed to merge: Maintainers
# Allowed to push: No one
# Allowed to force push: No one
# Require approval: Yes

# Merge Request Approval Rules:
# - Minimum 2 approvals required
# - At least 1 approval from CODEOWNERS
# - Author cannot approve own MR
# - All threads must be resolved
# - Pipeline must succeed
# .gitlab/CODEOWNERS — владельцы кодовых модулей
# Каждая команда отвечает за свои сервисы

# Payment service
services/payment/**    @team-payments

# Auth service
services/auth/**       @team-platform

# Mobile BFF (Backend For Frontend)
services/mobile-bff/** @team-mobile-backend

# Infrastructure and CI/CD
.gitlab-ci.yml         @team-devops
infra/**               @team-devops
helm/**                @team-devops

# Shared libraries — нужен ревью от архитектора
libs/shared/**         @chief-architect @team-platform
# .gitlab/merge_request_templates/Default.md
## Что изменено


## Зачем


## Как тестировать


## Чеклист
- [ ] Unit-тесты добавлены/обновлены
- [ ] Документация обновлена (если API изменилось)
- [ ] Миграция БД обратно-совместима
- [ ] Feature flag добавлен (если фича неготова к релизу)

После внедрения CODEOWNERS количество багов, дошедших до production, сократилось на 35% — ревью от владельца модуля перехватывало проблемы, которые пропускал generic review.

Conventional Commits и автоматическое версионирование

Хаотичные commit messages вроде «fix», «wip», «asdf» — бич больших команд. Мы внедрили стандарт Conventional Commits с автоматической проверкой:

# .commitlintrc.yml — правила для commit messages
extends:
  - '@commitlint/config-conventional'
rules:
  type-enum:
    - 2
    - always
    - [feat, fix, perf, refactor, docs, test, ci, chore, revert]
  scope-enum:
    - 2
    - always
    - [payment, auth, mobile-bff, notifications, shared, infra]
  subject-max-length:
    - 2
    - always
    - 72
  body-max-line-length:
    - 2
    - always
    - 100

# Примеры правильных коммитов:
# feat(payment): add recurring payment support
# fix(auth): prevent token refresh race condition
# perf(mobile-bff): cache user profile for 5 minutes
# docs(payment): update API documentation for v2 endpoints
# Git hook для проверки commit message (pre-commit)
# .husky/commit-msg
#!/bin/sh
npx --no -- commitlint --edit "$1"

# Серверный hook в GitLab (для тех, кто обходит pre-commit):
# .gitlab-ci.yml
commitlint:
  stage: validate
  script:
    - npx commitlint --from $CI_MERGE_REQUEST_DIFF_BASE_SHA --to HEAD
  rules:
    - if: $CI_MERGE_REQUEST_IID
# Автоматическое версионирование через semantic-release
# .releaserc.yml
branches:
  - main
plugins:
  - '@semantic-release/commit-analyzer'
  - '@semantic-release/release-notes-generator'
  - '@semantic-release/changelog'
  - '@semantic-release/gitlab'
  - '@semantic-release/git'

# semantic-release анализирует коммиты и автоматически:
# feat → MINOR (1.2.0 → 1.3.0)
# fix → PATCH (1.2.0 → 1.2.1)
# feat! или BREAKING CHANGE → MAJOR (1.2.0 → 2.0.0)
# Генерирует CHANGELOG.md и создаёт GitLab Release

За первый месяц semantic-release автоматически выпустил 47 версий без участия release manager. Каждый merge в main — потенциальный релиз.

CI/CD интеграция: от коммита до production за 20 минут

Git-стратегия без CI/CD — просто формальность. Мы выстроили pipeline, который гарантирует качество кода на каждом этапе:

# .gitlab-ci.yml — основной pipeline
stages:
  - validate
  - test
  - build
  - deploy-staging
  - deploy-production

variables:
  DOCKER_BUILDKIT: 1

# Этап 1: валидация (30 секунд)
commitlint:
  stage: validate
  script:
    - npx commitlint --from $CI_MERGE_REQUEST_DIFF_BASE_SHA --to HEAD
  rules:
    - if: $CI_MERGE_REQUEST_IID

lint:
  stage: validate
  script:
    - make lint
  rules:
    - if: $CI_MERGE_REQUEST_IID

# Этап 2: тесты (3-5 минут, параллельно)
unit-tests:
  stage: test
  parallel: 4
  script:
    - make test-unit SHARD=$CI_NODE_INDEX TOTAL=$CI_NODE_TOTAL
  coverage: '/^TOTAL\s+\d+\s+\d+\s+(\d+%)$/'
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == "main"

integration-tests:
  stage: test
  services:
    - postgres:15
    - redis:7
  script:
    - make test-integration
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == "main"

# Этап 3: сборка Docker-образа (2-3 минуты)
build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# Этап 4: деплой в staging (автоматический)
deploy-staging:
  stage: deploy-staging
  script:
    - helm upgrade --install $CI_PROJECT_NAME ./helm \
        --set image.tag=$CI_COMMIT_SHA \
        --namespace staging
    - kubectl rollout status deployment/$CI_PROJECT_NAME -n staging --timeout=120s
  environment:
    name: staging
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# Этап 5: деплой в production (ручное подтверждение)
deploy-production:
  stage: deploy-production
  script:
    - helm upgrade --install $CI_PROJECT_NAME ./helm \
        --set image.tag=$CI_COMMIT_SHA \
        --namespace production
    - kubectl rollout status deployment/$CI_PROJECT_NAME -n production --timeout=180s
  environment:
    name: production
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Pipeline от push до staging: 8-12 минут. С ручным approve production: 15-20 минут. Против прежних 2 дней.

Hotfix-процесс и release management для мобильного приложения

Для backend hotfix — это обычный merge в main с повышенным приоритетом ревью. Для мобильного приложения нужен отдельный процесс:

# Hotfix backend (Trunk-Based)
# 1. Создаём ветку от main
git checkout main && git pull
git checkout -b hotfix/DBANK-4521-payment-timeout

# 2. Фиксим, коммитим
git add .
git commit -m "fix(payment): increase timeout for slow providers to 30s

Closes DBANK-4521"

# 3. Push + MR с пометкой HOTFIX
git push -u origin hotfix/DBANK-4521-payment-timeout
# В GitLab: MR с label "hotfix" → автоматически назначает
# ревьюеров из team-payments + team-devops
# Один approve достаточно (вместо двух для обычных MR)
# Release management для мобильного приложения
# Используем release-ветки (элемент GitFlow)

# 1. Создание release-ветки каждый вторник
git checkout main
git checkout -b release/mobile-2.14.0
git push -u origin release/mobile-2.14.0

# 2. В release-ветке — только bugfixes
# .gitlab-ci.yml автоматически запускает build для App Store/Play Store

# 3. После релиза — tag + merge обратно в main
git checkout main
git merge release/mobile-2.14.0 --no-ff
git tag -a mobile-v2.14.0 -m "Mobile release 2.14.0"
git push origin main --tags

# 4. Мобильный hotfix:
git checkout release/mobile-2.14.0
git checkout -b hotfix/mobile-crash-on-login
# fix → MR в release-ветку → cherry-pick в main
git cherry-pick abc1234 -x  # -x добавляет reference на оригинальный коммит

Для трекинга версий мы создали GitLab dashboard, где release manager видит: какие фичи в какой release-ветке, статус тестирования, готовность к деплою в App Store.

Monorepo: особенности работы с 1.2 GB репозиторием

Монорепо ЦифроБанка на 1.2 GB создавало специфические проблемы: git clone занимал 4 минуты, git status — 8 секунд. Мы оптимизировали работу:

# Partial clone — клонирование без blob-ов (качает только при checkout)
git clone --filter=blob:none git@gitlab.cifrobank.ru:main/monorepo.git
# Размер клона: 120 MB вместо 1.2 GB
# git checkout подгружает файлы по требованию

# Sparse checkout — работа только с нужными директориями
git sparse-checkout init --cone
git sparse-checkout set services/payment libs/shared
# Теперь рабочая копия содержит только payment-сервис и shared-библиотеки

# Ускорение git status через fsmonitor
git config core.fsmonitor true
git config core.untrackedcache true
# git status: 8 секунд → 0.3 секунды
# CI/CD оптимизация: запуск тестов только для изменённых сервисов
# .gitlab-ci.yml
.detect-changes: &detect-changes
  script:
    - |
      CHANGED_DIRS=$(git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA HEAD | \
        cut -d'/' -f1-2 | sort -u)
      echo "Changed: $CHANGED_DIRS"
      
      if echo "$CHANGED_DIRS" | grep -q "services/payment"; then
        export RUN_PAYMENT_TESTS=true
      fi
      if echo "$CHANGED_DIRS" | grep -q "libs/shared"; then
        export RUN_ALL_TESTS=true  # shared lib → тестируем всё
      fi

# Среднее время CI при точечных изменениях: 4 минуты вместо 25

Альтернатива монорепо — polyrepo (отдельный репозиторий на каждый сервис). Мы обсудили это с командой, но решили оставить монорепо: атомарные изменения в нескольких сервисах, единый CI/CD, проще рефакторинг shared-кода. Проблемы производительности решаются partial clone и sparse checkout.

Результаты

Через 2 месяца после внедрения единой Git-стратегии:

МетрикаДо проектаПосле проекта
Время от коммита до production2 недели20 минут (backend)
Merge-конфликты/неделю25-303-5
Сломанный main/неделю3-5 раз0 (за 2 месяца — 1 раз)
Время на разрешение конфликтов4 часа/разработчик/неделя15 минут
Ручная работа release manager2 дня на релиз0 (автоматическое версионирование)
Частота деплоев1 раз в 2 недели8-12 раз в день

Главный культурный сдвиг: разработчики перестали бояться мержить в main. Short-lived ветки (часы, не недели) устранили главную причину конфликтов. Feature flags позволяют мержить незаконченные фичи без риска для пользователей. Подробнее о DevOps-практиках для enterprise — на itfresh.ru.

Часто задаваемые вопросы

GitFlow создаёт длинноживущие ветки, которые в монорепе с 50 разработчиками превращаются в merge-hell. Trunk-Based с short-lived branches (часы, не недели) минимизирует конфликты и позволяет деплоить в production несколько раз в день. Для банковского регулирования важен не процесс ветвления, а quality gates (тесты, ревью, approval).
Feature flags. Мы используем Unleash (self-hosted): незаконченная фича мержится в main за feature flag, который включён только для команды разработки. Когда фича готова — flag включается для 5% пользователей (canary), затем для 100%. Это ключевой инструмент для Trunk-Based Development.
Первые 2 недели разработчики жалуются. Через месяц — не представляют, как работали без них. Автоматический CHANGELOG, semantic versioning, возможность быстро найти коммит по типу (все fix за последний месяц) — это экономит десятки часов при расследовании инцидентов.
Monorepo, если: команды часто меняют shared-код, нужны атомарные изменения в нескольких сервисах, CI/CD зрелый. Polyrepo, если: сервисы полностью независимы, команды автономны, разные технологические стеки. Для ЦифроБанка с общими библиотеками и частым рефакторингом monorepo был правильным выбором.
Quality gates на каждом этапе: lint + SAST (статический анализ безопасности) в validate, unit + integration тесты, CODEOWNERS approval, ручное подтверждение production-деплоя. Частые деплои не означают снижение качества — наоборот, маленькие изменения легче ревьюить и откатывать.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#git branching strategy#GitFlow vs Trunk-Based#protected branches policy#merge request code review#CI/CD git интеграция#semantic versioning#release management git#hotfix process
Комментарии 0

Оставить комментарий

загрузка...