linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it works
  • Уроки
  • База знаний
  • Собеседование
home/git/lessons/git-lab-17-1-branch-protection

lesson ── git-labs ── ~30 мин ── 9 шагов

Branch protection и CODEOWNERS в локальном forge

Цель - руками настроить branch protection на main, увидеть как protection блокирует прямой push, опробовать CODEOWNERS-эквивалент через required reviewers, и увидеть отказ force-push.

Forge здесь - Gitea (ADR-015), локальный клон GitHub. REST API большой частью совпадает с GitHub'овским. В sandbox два контейнера: workstation (терминал) и forge (Gitea на forge:3000). Сеть air-gapped, github.com недоступен.

Демо-репо student/demo уже создан в Gitea на первом старте.

▶ интерактивный sandbox

Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.

запустить sandbox →

stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя

Шаги

  1. 01

    Подожди, пока forge поднимется

    Контейнер forge (Gitea) стартует около 5-10 секунд - сначала запускается web-сервер, потом entrypoint создаёт админа и demo-репо. Проверь, что API отвечает:

    bash
    # -f = fail на 4xx/5xx, -s = silent, -S = показать ошибку при -s
    # >/dev/null 2>&1 = подавить и stdout, и stderr - нам важен только exit-code
    until curl -fsS http://forge:3000/api/v1/version >/dev/null 2>&1; do
      echo "waiting for forge..."
      sleep 1
    done
    curl -s http://forge:3000/api/v1/version    # -s без -f = просто без прогресс-бара

    Должен вывести что-то вроде {"version":"1.22.x"}. Если зависло на минуту - forge не поднялся (это редко, но проверь логи через Reset sandbox).

    подсказка

    Если ничего не пишется - sandbox ещё запускается, дай 30 секунд.

    ✓ Forge готов отвечать API-запросам.

  2. 02

    Создай токен для API-обращений

    Студенческий аккаунт уже создан: student / student-pass-only-for-sandbox. Для удобства - сохрани креды в переменную:

    bash
    export USER=student
    export PASS=student-pass-only-for-sandbox
    export REPO=demo

    Проверь, что креды работают:

    bash
    # -u user:pass = HTTP Basic Auth для Gitea API
    curl -s -u $USER:$PASS http://forge:3000/api/v1/user | head -50

    Должен вывести JSON с твоим юзером.

    ✓ Логин работает. Дальше клонируем demo-репо.

  3. 03

    Клонируй demo-репо

    bash
    cd /home/student/work
    # креды прямо в URL (user:pass@host) - удобно для урока, в проде так не делать
    git clone http://student:student-pass-only-for-sandbox@forge:3000/student/demo.git
    cd demo
    git log --oneline

    Репо клонировался через топологию (DNS-имя forge). В нём один коммит от auto_init - это README.md. Дальше ты будешь его защищать.

    ✓ Demo-репо клонирован.

  4. 04

    Включи branch protection на main

    Через REST API: PUT на /api/v1/repos/{owner}/{repo}/branch_protections.

    bash
    # -X POST = метод запроса; -H = заголовок; -d = тело (JSON)
    curl -fsS -u $USER:$PASS \
      -X POST http://forge:3000/api/v1/repos/student/demo/branch_protections \
      -H 'Content-Type: application/json' \
      -d '{
        "branch_name": "main",
        "enable_push": false,
        "enable_merge_whitelist": false,
        "required_approvals": 1,
        "block_on_rejected_reviews": true,
        "dismiss_stale_approvals": true,
        "enable_status_check": false
      }'

    Видишь JSON-ответ с настроенным правилом. main теперь защищён: прямой push заблокирован, merge только через PR с approve.

    ✓ Protection включён на main.

  5. 05

    Попробуй сделать прямой push в main - увидь отказ

    bash
    cd /home/student/work/demo
    echo "direct edit" >> README.md
    git add README.md && git commit -m "direct edit"
    git push origin main          # отказ: "Protected branch hook declined"

    Должна выйти ошибка от сервера: remote: Protected branch hook declined. Push отклонён - это и есть защита в действии. Откатим локальный коммит, чтобы не было хвоста:

    bash
    git reset --hard origin/main  # снять локальный коммит, синхронизироваться с remote
    подсказка

    Если push прошёл - protection не включился. Проверь шаг enable-protection ещё раз.

    ✓ Прямой push отклонён. Дальше - правильный путь через PR.

  6. 06

    Сделай ветку, push в неё, открой PR

    bash
    cd /home/student/work/demo
    git switch -c feat/typo
    echo "improved readme" > README.md
    git add . && git commit -m "fix: improve README"
    git push -u origin feat/typo  # на feature-ветку защита не распространяется

    Push в feature-ветку прошёл - на неё защита не распространяется. Дальше - открой PR через API:

    bash
    # head = ветка с изменениями, base = куда мержить (как на GitHub: head -> base)
    curl -fsS -u $USER:$PASS \
      -X POST http://forge:3000/api/v1/repos/student/demo/pulls \
      -H 'Content-Type: application/json' \
      -d '{"title":"fix: improve README","body":"better wording","head":"feat/typo","base":"main"}'

    Ответом - JSON с "number": 1. PR открыт.

    ✓ PR открыт. Дальше попробуем смержить без approve.

  7. 07

    Попробуй смержить PR без approve - увидь отказ

    bash
    # -i = включить response-заголовки в вывод (нужны для увидеть HTTP 405)
    curl -i -u $USER:$PASS \
      -X POST http://forge:3000/api/v1/repos/student/demo/pulls/1/merge \
      -H 'Content-Type: application/json' \
      -d '{"Do":"merge"}'

    Должен прийти 405 Method Not Allowed с сообщением вроде pull request does not meet required approvals. Это и есть required_approvals: 1 в действии.

    В GitHub UI это «Merge button disabled» с подсказкой «1 approval required». В Gitea - HTTP 405 от API. Поведение то же.

    ✓ Merge без approve заблокирован.

  8. 08

    Поставь approve через API и смерджи

    В реальной команде approve ставит другой человек. Здесь у нас один student - в режиме урока можно approve самому (Gitea это разрешает через API; в GitHub strict-mode настройки этого не позволяют, но это деталь конфига).

    bash
    # POST review: event=APPROVED ставит approve, body = текст комментария
    curl -fsS -u $USER:$PASS \
      -X POST http://forge:3000/api/v1/repos/student/demo/pulls/1/reviews \
      -H 'Content-Type: application/json' \
      -d '{"event":"APPROVED","body":"lgtm"}'
    # после approve тот же merge-запрос проходит
    curl -fsS -u $USER:$PASS \
      -X POST http://forge:3000/api/v1/repos/student/demo/pulls/1/merge \
      -H 'Content-Type: application/json' \
      -d '{"Do":"merge"}'

    Теперь merge прошёл - approve есть, protection доволен. Проверь:

    bash
    git fetch && git log --oneline origin/main    # видишь init + merge-коммит PR

    Видишь два коммита в main: init + merge-коммит твоего PR.

    ✓ PR смержен с approve. Полный цикл пройден.

  9. 09

    Попробуй force-push в main - тоже отказ

    Branch protection также блокирует переписывание истории на main.

    bash
    cd /home/student/work/demo
    git switch main
    git pull
    git commit --allow-empty -m "rebase-source"     # --allow-empty = коммит без изменений
    git reset --hard HEAD~2       # снять 2 коммита - переписать историю
    git push --force-with-lease origin main         # попытка force-push: будет отклонена

    Должна выйти ошибка protected branch hook declined. force-push отклонён.

    Откатим состояние:

    bash
    git fetch && git reset --hard origin/main      # вернуться к remote-состоянию main
    подсказка

    Если push прошёл - проверь, что enable_force_push в protection НЕ включён.

    ✓ Force-push в main отклонён. Урок пройден - защита работает.

Что ты узнал

Branch protection - правила на стороне сервера, которые блокируют операции, не вписывающиеся в политику. Required approvals не дают мержить без review. Force-push отдельно запрещается. CODEOWNERS в Gitea выражается через protected_files + ручное назначение reviewers.

команды

  • curl -u user:pass -X PUT .../branch_protectionsвключить protection на ветке
  • curl -X POST .../pullsоткрыть PR через API
  • git push origin mainесли main protected - получишь отказ
  • git push --force-with-lease origin mainтоже отказ на protected

концепции

  • · branch protection живёт на forge, не в git'е
  • · required approvals блокирует merge до review
  • · CODEOWNERS в Gitea = protected_files + reviewers, в GitHub - отдельный файл

← предыдущая

pre-commit framework: автоматизация перед каждым коммитом

Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки