
Git flow: как работают с ветками и Pull Request в командах
Почему нельзя коммитить прямо в main, зачем нужен Pull Request и почему его нельзя мержить сразу. Пошаговый флоу работы через ветку — так же, как это устроено в компаниях.
Почти все IT школы так или иначе обучают Git'у, но большинство из них ограничиваются общим описанием технологии и тем, как залить код на условный Github. Мало кто фокусируется на том, как устроена работа с гитом у команд в айти, какие есть правила, роли и запреты, и приводит это к тому, что код-то в итоге лежит в Github, но не там или не так.
В этой статье разберем, как именно устроена работа с гитом в командах, три популярных вида ветвления в компаниях, пошаговый флоу работы с фичами через ветку и Pull Request, разбор частых ошибок и шпаргалка команд. Базового рассказа о том, что такое Git, не будет. Почти.
Изначально я писал этот гайд для тестировщиков, поэтому, если тебе интересно тестирование, можно начать с гайда «Как стать тестировщиком». А если уже пишешь код и не понимаешь, почему нельзя просто закоммитить в main — читай дальше.
Зачем мы создаём разные ветки?
Я часто говорю студентам, что Git можно представить как ячейки сохранений в игре, где каждый коммит (снимок кода) — это буквально как сохранение в игре, где можно в любой момент вернуться в нужный момент и не только посмотреть, что происходило на тот момент, но и переиграть ситуацию из этой точки.
А теперь представим, что с твоего компьютера играет сразу несколько человек. Если вы все будете использовать одни и те же ячейки сохранений, то очень быстро запутаетесь, где и чьё прохождение. И будете здорово мешать друг другу.
В играх такая проблема может решаться возможностью создания нескольких профилей — например, ты создал профиль 1, у Никиты профиль 2, а у Ани — 3. Сохранения профилей отделены между собой, и они не мешают друг другу.
В Git похожую роль выполняет система ветвления.
Каждый разработчик, тестировщик-автоматизатор, data scientist или любой другой человек, который трогает код, создаёт себе под конкретную задачу ветку. В ней будет что-то вроде копии приложения, которое видят все, но при этом это будет изолированное пространство, где ни ты не мешаешь другим, ни другие не мешают тебе.
Только, в отличие от игровых профилей, ветка заводится не под человека, а под задачу: за неделю их у тебя может быть пять.
Когда код будет готов, ветка объединится с основной — и изменения окажутся в общем приложении.
И ещё одно, без чего дальше будет непонятно: коммит сначала сохраняется только у тебя на компьютере. Пока ты не отправишь ветку на сервер командой git push, для остальных её просто не существует — ни коллеги, ни ревьюер твой код не увидят.

Почему main нельзя трогать напрямую
main — это ветка, которая считается основной. В реальных проектах из неё собирается сборка, которая уедет к пользователям; в учебных — это «чистовик», который прошёл ревью. Правило простое: в main попадает только то, что кто-то проверил.
Поэтому в компаниях main обычно защищён технически — это называется branch protection. Ты физически не сможешь запушить в него напрямую: гит вернёт ошибку. Не потому что тебе не доверяют, а потому что человек, который «на секундочку поправил прямо в main», рано или поздно кладёт прод в пятницу вечером.
Защита может быть разная — часто могут быть автоматические проверки кода (линтером, например), и почти всегда — проверки реальными людьми. В компаниях, в которых я работал, свой аппрув (буквально "разрешаю это пускать в продакшн") должны поставить минимум несколько людей с разными компетенциями.
Впрочем, если это твой сольный или учебный проект, никакой защиты не будет, если ты сам её не настроишь. Поэтому приходится ограничивать себя самостоятельно.
Даже если я один, все равно нужна отдельная ветка?
В большинстве случаев — не помешает.
Но вот когда это прям обязательно:
- Если ты делаешь какое-то приложение, которое подключено к хостингу, который автоматически делает билды, когда видит коммиты в ветке. Сейчас есть много сервисов (Railway, Timeweb Cloud, Heroku и сотни других), которые сильно упрощают процесс выкладки приложений. Если они увидят изменения в главной ветке (а обычно это main), они запустят сборку и публикацию билда. Т.е. если мы закомитим что-то неготовое и/или непротестированное (что мы делаем довольно часто), рискуем, что пользователи это увидят. Нехорошо.
- Если у тебя учебный проект с проверкой ревьюером. Часто в таких проектах проверяется не только код, но и умение правильно работать с Git. Так как в реальной работе почти всегда ответвляются и потом создаем Pull Request / Merge Request, именно это ожидается и в учебных проектах.
Впрочем, если ты делаешь что-то для себя, у тебя нет автоматических процессов публикации и есть полный контроль над релизами (если они нужны), создавать ветки не обязательно, хотя они и сильно помогают с организацией кода и задач.
Своя ветка — это черновик. В ней можно ломать, экспериментировать, коммитить каждые пять минут. Никто, кроме тебя, этого не увидит, пока ты сам не откроешь Pull Request. Это главное преимущество.
Как это устроено в компаниях: три подхода
Единственно правильной схемы ветвления нет — есть несколько распространённых. Разберём коротко, чтобы ты понимал, о чём говорят на собеседовании.
Git Flow
Основная для крупных компаний, в которых работал я.
Две долгоживущие ветки: main — то, что в проде, develop — то, что готовится к следующему релизу.
От develop отпочковываются feature/*, перед релизом создаётся release/*, а срочные правки прода идут через hotfix/* сразу в main.
Потом все фичи сначала объединяются (или вливаются) в этот develop, а уже потом, когда собран релиз, ветка develop объединяется с main. И так по кругу.
Тяжеловат, но логичен там, где есть версии и релизные циклы: мобильные приложения, коробочные продукты, банки. Именно этот флоу по-хорошему должен отрабатываться в учебных проектах — потому что в нём хорошо видно разделение «чистовик / рабочая ветка».
GitHub Flow
Упрощённая версия: есть только main и короткоживущие ветки под задачи. Сделал ветку → закоммитил → открыл Pull Request → прошло ревью и CI → смержили в main → задеплоили. Никакого develop. Так работает большинство веб-продуктов и опенсорс-проектов.
Trunk-based development
Ещё жёстче: ветки живут часы, а не дни, мержатся в main по несколько раз в день, недоделанный функционал прячется за фича-флагами. Требует зрелого CI и хорошего покрытия тестами.
Схемы разные, но три вещи в них одинаковые:
- работа ведётся не в main, а в отдельной ветке;
- изменения попадают в основную ветку только через Pull Request;
- PR не мержится, пока нет апрува и зелёного CI.
Если ты усвоил эти три пункта, ты впишешься в процесс почти любой команды. Остальное — детали конкретного проекта.
PR или MR?
В GitHub это Pull Request (PR), в GitLab — Merge Request (MR). Это одно и то же: запрос «влейте, пожалуйста, мою ветку в основную». Названия разные, суть одинаковая.
Хочешь попробовать себя в тестировании?
Пройди бесплатный краш-курс — за час поймёшь, подходит ли тебе профессия.
Флоу того, как это работает: пошагово
Дальше — конкретная последовательность, которую можно практиковать самостоятельно. Она же почти один в один воспроизводит то, что ты будешь делать на работе. На примере нового (пустого) проекта.
Шаг 1. Создай репозиторий и почти пустой main
В main на старте должен лежать минимум: README и .gitignore. Смысл в том, чтобы у ветки была точка отсчёта, и чтобы потом в Pull Request было видно только твою работу, а не «добавлено 40 файлов, среди которых где-то есть код».
# вариант А: репозиторий уже создан на GitHub с README — просто клонируем
git clone git@github.com:username/my-project.git
cd my-project
# вариант Б: начинаем с нуля локально
mkdir my-project && cd my-project
git init
git branch -M main
echo "# My project" > README.md
git add README.md
git commit -m "chore: init repository"
git remote add origin git@github.com:username/my-project.git
git push -u origin mainШаг 2. Переключись на develop
Весь код пишем в отдельной ветке. Создаём её от актуального main:
Одна оговорка про название. В учебных проектах develop обычно используют именно так — как рабочую ветку под задачу, чтобы не плодить сущности. В полноценном Git Flow, который мы разобрали выше, develop живёт постоянно и служит для сборки релиза, а под задачу ты бы ответвлялся от него в feature/название-задачи. Механика команд при этом ровно та же, меняются только имена веток.
git switch main # убедились, что стоим на main
git pull # подтянули актуальное состояние
git switch -c develop # создали ветку develop и перешли на неё
git branch --show-current # проверка: должно вывести developswitch или checkout?
git switch -c develop и git checkout -b develop делают одно и то же. switch появился позже и умеет только переключать ветки, поэтому им сложнее случайно снести свои изменения. В старых гайдах увидишь checkout — это не ошибка.
Шаг 3. Пиши код и коммить маленькими порциями
Коммит — это не «сохранение в конце дня». Это логически завершённый кусочек: добавил страницу — коммит, написал тест — коммит, поправил баг — коммит. Так ревьюеру — тому, кто будет проверять твой код, — видно ход мысли, а тебе легко откатить одну неудачную идею, не теряя всё остальное.
git status # что изменилось
git add tests/test_login.py # добавляем конкретные файлы, а не git add . вслепую
git commit -m "test: добавил проверку авторизации с валидными данными"
git log --oneline # смотрим, что получилосьШаг 4. Запушь ветку
Пока ветка живёт только у тебя на компьютере, для остальных её не существует. Отправляем на сервер:
git push -u origin develop
# -u нужен только в первый раз: он связывает локальную ветку с удалённой.
# Дальше достаточно просто: git pushШаг 5. Открой Pull Request
Заходишь в репозиторий на GitHub — там уже будет плашка «Compare & pull request». Если её нет: вкладка Pull requests → New pull request.
Самое важное здесь — правильно выбрать две ветки:
- base — куда вливаем. Это
main. - compare — что вливаем. Это
develop.
Если перепутать местами, гитхаб честно напишет, что различий нет, либо предложит влить main в твою ветку — а это совсем другая операция. В большинстве случаев это не то, чего мы хотим.

Шаг 6. Заполни описание
Пустой PR с заголовком «Update» — это как баг-репорт с текстом «не работает». Другому человеку нужно понимать, что ты сделал и как это проверить. Минимальный шаблон:
## Что сделано
- Добавил автотесты на форму авторизации (позитивный и негативный кейсы)
- Вынес локаторы в отдельный page object
## Как проверить
1. Установить зависимости: pip install -r requirements.txt
2. Запустить: robot tests/
3. Ожидаемо: 5 passed, 0 failed
## Вопросы к ревью
- Не уверен, правильно ли выбрал ожидание в test_login.py:24Шаг 7. Отправь ссылку на PR и остановись
Если ты хочешь показать свою работу, надо скопировать ссылку на Pull Request, а не на репозиторий и не архив с кодом. И на этом твои действия заканчиваются: дальше ход ревьюера.
Не мержи PR до ревью
Кнопка Merge pull request зелёная и очень манящая. Не трогай её, пока работу не проверили. Смерженный PR — это работа, которая не прошла ревью: комментарии оставлять уже некуда, а «чистовик» ты испортил сам непроверенной работой.
Почему нельзя мержить сразу
Тут важно понять смысл слова «request». Pull Request — это запрос, просьба к команде влить твой код. Не кнопка «я закончил». Ты просишь — решает ревьюер.
Что ломается, когда автор мержит сразу, не дожидаясь ревью:
- Ревью теряет смысл. Код уже в основной ветке. Замечания превращаются в «ну, в следующий раз учту».
- Комментарии некуда вешать. В открытом PR ревьюер комментирует конкретные строки, ты отвечаешь, спор виден всем. В закрытом остаётся только переписка в чате.
- Непроверенный код становится эталоном. Следующий человек скопирует твой подход, считая, что раз это в main — значит, так правильно.
- В реальном проекте это ещё и деплой. Мерж в main часто автоматически запускает выкатку. Ты не «сдал работу» — ты выложил её пользователям.
В компаниях на это не полагаются: как я уже сказал, часто branch protection требует минимум одного апрува, зелёного CI и иногда обязательного ревью от владельцев кода (файл CODEOWNERS). Кнопка мержа просто не активна, пока условия не выполнены. Учебный процесс отличается только тем, что здесь запрет держится на договорённости, а не на настройках репозитория.
Пришли правки — что делать
Главное, что нужно запомнить: новый PR создавать не нужно. Pull Request привязан к ветке, а не к набору коммитов. Всё, что ты допушишь в develop, автоматически появится в том же PR.
git switch develop # если вдруг ушёл на другую ветку
# ...правим код по замечаниям...
git add .
git commit -m "fix: вынес ожидание в переменную по замечанию ревью"
git push # PR обновится сам, ничего пересоздавать не надоДальше — правила хорошего тона, которые реально экономят время обеим сторонам:
- Отвечай на каждый комментарий: «исправил», «сделал по-другому, потому что...», «не согласен, вот почему».
- Нажимай Resolve conversation только на том, что действительно закрыл.
- Когда всё поправил — запроси повторное ревью (кнопка Re-request review), а не жди, что ревьюер сам догадается.
- Не спорь ради спора, но и не соглашайся молча с тем, что считаешь неверным. Ревью — это диалог, а не экзамен.

Как называть ветки, коммиты и PR
Ветки
В учебных задачах хватит develop. В реальных проектах ветку называют по типу задачи и её сути, часто с номером тикета:
- ❌
new,test2,vasya-branch,fix - ✅
feature/login-page,fix/checkout-500,test/api-smoke,QA-142-add-cart-tests
Коммиты
Сообщение коммита отвечает на вопрос «что изменится в проекте, если это применить». Многие команды используют Conventional Commits — префикс типа изменения:
feat:— новая функциональностьfix:— исправление багаtest:— тестыdocs:— документацияrefactor:— переписал без изменения поведенияchore:— рутина: зависимости, конфиги, CI
Примеры:
❌ Плохо: fix, работает, final, final2, asdf
✅ Хорошо: fix: корзина не пересчитывала сумму при удалении товара
✅ Хорошо: test: добавил негативные кейсы на валидацию email
Заголовок PR
Заголовок PR — это заголовок всей работы, его читают в списке из десятков других. Он должен отвечать на вопрос «что тут сделано», без чтения диффа. Ровно как заголовок баг-репорта: конкретно и целиком предложением.
Ошибки, которые я вижу на каждом потоке курса
1. Весь код в main, PR нет
Самая частая. Человек работает как привык: закоммитил, запушил, прислал ссылку на репозиторий. Технически код есть — процесса нет. Ревьюеру нечего открывать и негде комментировать.
2. Создал PR и сразу смержил
PR висит в разделе Closed, ветка удалена, обсуждать нечего. Формально «сдал» — по факту получилось ровно то же самое, что и в первой ошибке, только длиннее.
3. Перепутаны base и compare
PR «main → main» или «develop → develop». Гитхаб показывает 0 изменённых файлов. Всегда проверяй стрелочку в заголовке PR: слева — куда вливаем, справа — что вливаем.
4. После правок создаётся новый PR
Появляется PR №2, №3, №4, а в первом остались все комментарии. Просто пуш в ту же ветку обновляет существующий PR — это его основная фича.
5. Файлы залиты через кнопку Upload files
Вся история — один коммит «Add files via upload». Это не работа с гитом, это загрузка файлов в облако. Ревьюер не видит, как ты шёл к результату.
6. Один коммит на всё
«done», 47 изменённых файлов. Такой PR невозможно ревьюить по частям и невозможно частично откатить. Дели работу на осмысленные шаги.
7. В репозитории лишнее
node_modules/, venv/, .idea/, __pycache__/, а иногда и .env с паролями. Заведи .gitignore до первого коммита — вычищать потом больно.
8. Force push в общую ветку
git push --force переписывает историю на сервере. Если ветку кто-то уже скачал — его коммиты могут просто исчезнуть. В свою личную ветку до ревью — иногда допустимо, в общую — никогда.
Никогда не пушь --force в чужую или общую ветку
Если очень нужно переписать историю своей ветки, используй git push --force-with-lease: он откажется затирать чужие коммиты, если на сервере появилось что-то новое. И предупреди тех, кто с этой веткой работает.
Если уже запутался — как выбраться
Первое действие всегда одно: понять, где ты находишься.
git status # текущая ветка и незакоммиченные изменения
git branch --show-current # только имя ветки
git log --oneline --graph --all -20 # картина истории: кто куда разошёлся
git remote -v # куда вообще пушимПисал в main, а надо было в develop (и ещё не пушил)
Ситуация решается легко: создаём ветку прямо здесь — коммиты остаются с ней — и возвращаем main к состоянию сервера.
git switch -c develop # твои коммиты теперь в develop
git push -u origin develop
git switch main
git reset --hard origin/main # ВНИМАНИЕ: main вернётся к состоянию на сервере,
# все локальные изменения в main пропадут
git switch developreset --hard удаляет незакоммиченные изменения
Выполняй его только после того, как убедился, что нужная работа уже лежит в коммитах ветки develop. Проверить: git log --oneline develop -5.
Уже запушил в main
История на сервере — общая: то, что ты уже запушил, могли забрать другие. Поэтому переписывать её в одиночку нельзя — сначала напиши ревьюеру или тому, кто ещё работает с репозиторием.
В учебной ситуации обычно просят создать ветку от текущего состояния и продолжить работу в ней. В боевой — откатывают через git revert, который делает новый коммит, отменяющий изменения, не ломая историю.
Забыл файл в последнем коммите
Если коммит ещё не запушен — можно дополнить его вместо создания нового.
git add забытый_файл.py
git commit --amend --no-edit # добавит файл в последний коммит, сообщение оставит
# Если коммит уже запушен — не амендь, а сделай обычный новый коммит.Всё сломал и не понимаю, что произошло
git reflog — журнал всех перемещений HEAD за последние недели. Даже «потерянные» коммиты почти всегда находятся там.
git reflog # список: где HEAD был раньше
git switch -c rescue a3f9c21 # a3f9c21 — хеш нужного коммита из вывода reflogПравило спокойствия
Всё, что попало в коммит, восстановимо. Невосстановимо только то, что ты не закоммитил. Поэтому при первых признаках паники — сначала коммить (или git stash), потом экспериментируй.
Шпаргалка: весь флоу работы (с ревью) одним куском
Сохрани себе и сверяйся, пока не станешь писать всё это на автомате.
# 1. Забрать актуальный main
git switch main
git pull
# 2. Создать рабочую ветку
git switch -c develop
# 3. Работать: код → коммиты маленькими порциями
git status
git add путь/к/файлу
git commit -m "feat: краткое описание изменения"
# 4. Отправить ветку на сервер
git push -u origin develop
# 5. На GitHub: Pull requests → New pull request
# base: main ← compare: develop
# заполнить описание, нажать Create pull request
# 6. Отправить ссылку на PR ревьюеру. НЕ МЕРЖИТЬ.
# 7. Правки по ревью — в ту же ветку
git add .
git commit -m "fix: правки по замечаниям ревью"
git push
# 8. Merge нажимает ревьюер после апрува.Что в итоге
Гит-флоу выглядит как бюрократия ровно до того момента, пока не попадёшь в команду из десяти человек, которые правят один и тот же код. Там выясняется, что ветки, PR и ревью — единственное, что удерживает проект от превращения в кашу.
Три вещи, которые стоит унести из статьи:
- main — чистовик. В него попадает только проверенное, и попадает через Pull Request.
- PR — это запрос, а не кнопка «готово». Мерж — только после апрува. Кто именно нажмёт кнопку, автор или ревьюер, зависит от команды.
- Правки идут в ту же ветку. Новый PR создавать не нужно — старый обновится сам.
И да, это спрашивают на собеседованиях: «расскажите, как у вас был устроен процесс работы с ветками». Ответ «я коммитил в main» звучит примерно как «баг-репорты я писал в личку разработчику».
Хочешь освоить тестирование полностью?
Полная программа подготовки — от основ до автоматизации, с проверкой работ и код-ревью.