Как делать11 октября 2026 г.7 мин чтения
Основы программированияШаг 11 из 15

GitHub: форк, pull request и код-ревью

Коротко

Pull request — это предложение слить ветку с вашими изменениями в основную ветку проекта: на GitHub вокруг него собираются описание, обсуждение, ревью и автоматические проверки. Порядок такой: создать ветку, закоммитить, отправить её командой git push -u origin ветка, открыть pull request с понятным описанием, ответить на замечания новыми коммитами и после одобрения слить. Без прав записи в репозиторий сначала делают форк — свою копию проекта.

РуководствоУровень: НачальныйGit и GitHubПрактика: ~30–60 мин

После материала вы сможете

  • отправить ветку на удалённый репозиторий и открыть pull request с описанием, связанной задачей и чек-листом
  • выбрать модель работы: ветка в общем репозитории или форк с remote upstream
  • ответить на замечания ревью новыми коммитами и обновить ветку через merge или rebase с --force-with-lease
  • объяснить, что дают защита ветки, обязательные проверки, SSH-ключи и fine-grained токены

Проверено на

Git 2.54 (Alpine Linux 3.24)OpenSSH 10.3markdownlint-cli2 0.23

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Удалённый репозиторий: remote, push, fetch, pull
  2. 02Форк или ветка в общем репозитории
  3. 03Ветка и pull request: описание, связанная задача, чек-лист
  4. 04Код-ревью: как давать и принимать замечания
  5. 05Обновление ветки: merge или rebase
  6. 06Слияние pull request и уборка
  7. 07Защита ветки и обязательные проверки
  8. 08SSH-ключ или токен: безопасный доступ

Удалённый репозиторий: remote, push, fetch, pull

Локальный репозиторий из урока о Git живёт только на вашем компьютере. Чтобы работать в команде, нужен удалённый репозиторий (remote) — копия на сервере, с которой все синхронизируются. Удалённым репозиториям дают короткие имена: основной по традиции называется origin. На GitHub адрес выглядит как https://github.com/user/repo.git или, для SSH, git@github.com:user/repo.git.

Ниже весь сценарий воспроизведён локально: роль GitHub играет «голый» репозиторий без рабочей папки (git init --bare), а вместо URL — путь к нему. Команды Git при этом те же, что и с GitHub. Сначала руководитель проекта публикует репозиторий, затем разработчица Айгерим клонирует его:

terminal
$ git remote add origin ../../server/coffee-site.git
$ git remote -v
origin	../../server/coffee-site.git (fetch)
origin	../../server/coffee-site.git (push)
$ git push -u origin main
To ../../server/coffee-site.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.
$ cd ../../aigerim
$ git clone ../server/coffee-site.git
Cloning into 'coffee-site'...
done.
$ cd coffee-site
$ git branch -vv
* main c390cc0 [origin/main] Initial landing page
Git 2.54, Alpine Linux (реальный вывод). -u связывает локальную ветку с удалённой: дальше хватает git push и git pull без аргументов.

git clone URL

Что делает
Скачивает репозиторий целиком и настраивает remote origin

git push -u origin ветка

Что делает
Отправляет коммиты ветки на сервер и запоминает связь

git fetch

Что делает
Забирает новые коммиты с сервера в origin/…, не трогая ваши ветки

git pull

Что делает
fetch плюс слияние в текущую ветку

git remote -v

Что делает
Показывает, куда смотрят удалённые репозитории

Сервер принимает push, только если ваша ветка продолжает его историю. Если кто-то успел отправить свои коммиты, push будет отклонён — сначала нужно забрать чужие изменения:

terminal
$ git switch main
Switched to branch 'main'
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ git commit -am "Mention contacts in README"
[main f307c0e] Mention contacts in README
 1 file changed, 1 insertion(+)
$ git push origin main
To /w2/aigerim/../server/coffee-site.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '/w2/aigerim/../server/coffee-site.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Git 2.54 (реальный вывод): прямой push в main, который успел уйти вперёд. В командах с защищённой веткой main такой push запрещён в принципе — изменения идут через pull request.

Форк или ветка в общем репозитории

Документация GitHub описывает две модели совместной работы. Выбор зависит от того, есть ли у вас права записи в репозиторий.

Общий репозиторий (shared repository)

  • Все участники имеют право push в один репозиторий
  • Каждая задача — отдельная ветка, pull request открывается внутри репозитория
  • Типично для небольших команд и закрытых проектов

Форк и pull request (fork and pull)

  • Вы создаёте форк — собственную копию чужого репозитория — и пушите туда
  • Pull request открывается из ветки форка в исходный репозиторий (upstream)
  • Типично для open source: права у владельца просить не нужно

Форк создаётся кнопкой Fork на странице репозитория или командой gh repo fork. Дальше вы клонируете свой форк, а исходный репозиторий добавляете вторым remote — по соглашению из документации GitHub он называется upstream. Синхронизация форка — это fetch из upstream, слияние и push в свой форк:

terminal
$ git clone ../server/aigerim-fork.git coffee-site
Cloning into 'coffee-site'...
done.
$ cd coffee-site
$ git remote add upstream ../../server/coffee-site.git
$ git remote -v
origin	/w/forkwork/../server/aigerim-fork.git (fetch)
origin	/w/forkwork/../server/aigerim-fork.git (push)
upstream	../../server/coffee-site.git (fetch)
upstream	../../server/coffee-site.git (push)
$ git fetch upstream
From ../../server/coffee-site
 * [new branch]      main       -> upstream/main
$ git merge upstream/main
Updating 6cb64ae..ee2dd24
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
$ git push origin main
To /w/forkwork/../server/aigerim-fork.git
   6cb64ae..ee2dd24  main -> main
Git 2.54 (реальный вывод). Форк смоделирован как копия «серверного» репозитория (git clone --bare); на GitHub его создаёт кнопка Fork.

Ветка и pull request: описание, связанная задача, чек-лист

Работа начинается с ветки от свежего main. Одна ветка — одна задача: так pull request остаётся небольшим и его реально проверить.

terminal
$ git switch -c feature/contact-form
Switched to a new branch 'feature/contact-form'
$ git add contact.html
$ git commit -m "Add contact form page"
[feature/contact-form 76e59a8] Add contact form page
 1 file changed, 7 insertions(+)
 create mode 100644 contact.html
$ git push -u origin feature/contact-form
To /w/aigerim/../server/coffee-site.git
 * [new branch]      feature/contact-form -> feature/contact-form
branch 'feature/contact-form' set up to track 'origin/feature/contact-form'.
$ git status
On branch feature/contact-form
Your branch is up to date with 'origin/feature/contact-form'.

nothing to commit, working tree clean
Git 2.54 (реальный вывод; файл contact.html создан в редакторе перед git add).
  1. Откройте репозиторий на GitHub

    После push GitHub показывает жёлтую плашку с кнопкой Compare & pull request для свежей ветки.

  2. Выберите ветки

    base — куда сливать (обычно main), compare — ваша ветка. Для форка нажмите compare across forks и выберите head fork.

  3. Напишите заголовок и описание

    Заголовок — что делает изменение. Описание — по шаблону ниже: что сделано, как проверить, скриншоты.

  4. Создайте pull request или черновик

    Create Pull Request — сразу на ревью. Create Draft Pull Request — черновик: его нельзя слить, и владельцы кода не получают автоматический запрос ревью.

Чтобы каждый pull request начинался с одинаковой структуры, положите шаблон в репозиторий — GitHub подставит его в описание автоматически. Строка Closes # с номером задачи свяжет PR с issue: по документации, при слиянии в ветку по умолчанию связанная задача закроется сама (ключевые слова работают, только если PR нацелен на ветку по умолчанию).

.github/pull_request_template.md
## Что сделано

Кратко: какую задачу решает pull request и что изменилось для пользователя.

Closes #

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

1. Переключитесь на ветку и запустите проект.
2. Откройте страницу или выполните сценарий: …
3. Ожидаемый результат: …

## Скриншоты

Для изменений интерфейса — до и после (desktop и mobile).

## Чек-лист автора

- [ ] Ветка обновлена от `main`, конфликтов нет
- [ ] Проверки (сборка, тесты, линтер) проходят
- [ ] В коде нет ключей, паролей и персональных данных
- [ ] Документация и `.env.example` обновлены, если нужно
- [ ] Pull request решает одну задачу
Шаблон описания pull request. Проверено markdownlint-cli2 0.23 (правило MD041 о заголовке первого уровня отключено: заголовок PR задаётся отдельно).

Тот же pull request можно создать из терминала через GitHub CLI — флаги из официального руководства gh pr create:

bash
gh pr create --base main --title "Add contact form page" \
  --body-file .github/pull_request_template.md --draft
Фрагмент по документации GitHub CLI; в уроке не запускался, чтобы ничего не создавать на GitHub.

Код-ревью: как давать и принимать замечания

Ревьюер смотрит вкладку Files changed, оставляет комментарии к строкам и завершает ревью одним из трёх решений. Пока ревью не отправлено, комментарии видны только автору ревью — их можно править.

Comment

Значение по документации GitHub
Общая обратная связь без явного одобрения или запроса изменений

Approve

Значение по документации GitHub
Изменения готовы к слиянию

Request changes

Значение по документации GitHub
Замечания, которые автор должен учесть до слияния
  • Изменение решает задачу из описания и ничего лишнего
  • Проверки (сборка, тесты, линтер) зелёные
  • Понятны имена, нет мёртвого и закомментированного кода
  • Обработаны ошибки и пустые состояния, а не только «счастливый путь»
  • Нет секретов, паролей и персональных данных; ввод пользователя проверяется
  • Для интерфейса: проверены мобильная версия и доступность с клавиатуры
  • Каждое замечание объясняет «почему» и предлагает вариант

Автору важно помнить: замечание касается кода, а не человека. Исправления делаются новыми коммитами в ту же ветку — pull request обновится сам, а ревьюер увидит, что изменилось с прошлого раза. Мелкую правку ревьюер может прислать как suggestion: автор применяет её кнопкой Commit suggestion, и GitHub создаёт коммит в ветке PR.

terminal
$ git diff
diff --git a/contact.html b/contact.html
index d909d01..b60a3dc 100644
--- a/contact.html
+++ b/contact.html
@@ -2,6 +2,6 @@
 <title>Contacts</title>
 <form method="post" action="/api/lead">
   <label>Name <input name="name"></label>
-  <label>Phone <input name="phone" type="tel"></label>
+  <label>Phone <input name="phone" type="tel" required></label>
   <button>Send</button>
 </form>
$ git commit -am "Make phone field required after review"
[feature/contact-form f9d0d45] Make phone field required after review
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
To /w/aigerim/../server/coffee-site.git
   76e59a8..f9d0d45  feature/contact-form -> feature/contact-form
Git 2.54 (реальный вывод): ответ на замечание «телефон должен быть обязательным» — правка, коммит и push в ту же ветку.

Обновление ветки: merge или rebase

Пока шло ревью, в main появились чужие коммиты. Ветку стоит обновить до слияния — так конфликты и падения тестов проявятся заранее. GitHub умеет сделать это кнопкой Update branch (слиянием или через Update with rebase), а локально есть два способа.

terminal
$ git fetch origin
From /w/aigerim/../server/coffee-site
   c390cc0..08e9c80  main       -> origin/main
$ git log --oneline --graph --all
* f9d0d45 Make phone field required after review
* 76e59a8 Add contact form page
| * 08e9c80 Update opening hours
|/  
* c390cc0 Initial landing page
$ git merge origin/main -m "Merge origin/main into feature/contact-form"
Merge made by the 'ort' strategy.
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
To /w/aigerim/../server/coffee-site.git
   f9d0d45..4b8ff1d  feature/contact-form -> feature/contact-form
Способ 1 — слить main в ветку. Git 2.54 (реальный вывод).
terminal
$ git fetch origin
From /w2/aigerim/../server/coffee-site
   c390cc0..08e9c80  main       -> origin/main
$ git rebase origin/main
Rebasing (1/1)Successfully rebased and updated refs/heads/feature/contact-form.
$ git push
To /w2/aigerim/../server/coffee-site.git
 ! [rejected]        feature/contact-form -> feature/contact-form (non-fast-forward)
error: failed to push some refs to '/w2/aigerim/../server/coffee-site.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git push --force-with-lease
To /w2/aigerim/../server/coffee-site.git
 + aea2fe5...0d33d81 feature/contact-form -> feature/contact-form (forced update)
$ git log --oneline --graph --all
* 0d33d81 Add contact form page
* 08e9c80 Update opening hours
* c390cc0 Initial landing page
Способ 2 — перебазировать ветку на свежий main (отдельный прогон того же сценария). Git 2.54 (реальный вывод).

merge

  • Историю не переписывает, обычный git push
  • Появляется коммит слияния
  • Безопасно, если в ветке работает несколько человек

rebase

  • Линейная история без коммита слияния
  • Коммиты пересоздаются — нужен git push --force-with-lease
  • Только для своей ветки, на которую никто не опирается

Слияние pull request и уборка

Когда ревью одобрено и проверки прошли, человек с правом записи нажимает Merge. Способ слияния настраивается в репозитории:

Create a merge commit

Что попадает в main
Все коммиты ветки и коммит слияния; по документации — слияние с опцией --no-ff

Squash and merge

Что попадает в main
Один коммит со всеми изменениями; промежуточные коммиты в main не видны

Rebase and merge

Что попадает в main
Коммиты ветки по одному поверх main, без коммита слияния; GitHub создаёт новые хеши
terminal
$ git fetch origin
From ../../server/coffee-site
 * [new branch]      feature/contact-form -> origin/feature/contact-form
$ git merge --no-ff origin/feature/contact-form -m "Merge feature/contact-form: contact form page"
Merge made by the 'ort' strategy.
 contact.html | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 contact.html
$ git push
To ../../server/coffee-site.git
   08e9c80..6cb64ae  main -> main
Git 2.54 (реальный вывод): локальный эквивалент кнопки Create a merge commit на стороне руководителя проекта. На GitHub это делает кнопка Merge в pull request.

После слияния автор обновляет свой main и удаляет ветку — локально и на сервере (на GitHub для удалённой ветки есть кнопка Delete branch прямо в pull request):

terminal
$ git switch main
Switched to branch 'main'
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ git pull
From /w/aigerim/../server/coffee-site
   08e9c80..6cb64ae  main       -> origin/main
Updating c390cc0..6cb64ae
Fast-forward
 contact.html | 7 +++++++
 index.html   | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)
 create mode 100644 contact.html
$ git branch -d feature/contact-form
Deleted branch feature/contact-form (was 4b8ff1d).
$ git push origin --delete feature/contact-form
To /w/aigerim/../server/coffee-site.git
 - [deleted]         feature/contact-form
$ git log --oneline --graph -7
*   6cb64ae Merge feature/contact-form: contact form page
|\  
| *   4b8ff1d Merge origin/main into feature/contact-form
| |\  
| |/  
|/|   
* | 08e9c80 Update opening hours
| * f9d0d45 Make phone field required after review
| * 76e59a8 Add contact form page
|/  
* c390cc0 Initial landing page
Git 2.54 (реальный вывод). В графе видна вся история: два коммита задачи, обновление ветки и коммит слияния.

Защита ветки и обязательные проверки

Чтобы в main нельзя было попасть в обход ревью, владелец репозитория включает правило защиты ветки (Settings → Branches) или ruleset — набор правил; в отличие от классического правила, на одну ветку могут действовать несколько rulesets сразу. По документации GitHub, каждое правило по умолчанию запрещает force push в защищённую ветку и её удаление. Основные настройки:

  • Require pull request reviews before merging — нужное число одобрений; опционально — снимать одобрение после новых коммитов и требовать ревью владельцев кода (CODEOWNERS).
  • Require status checks before merging — сливать можно только при зелёных проверках CI; в строгом режиме ветка ещё и должна быть обновлена от base.
  • Require conversation resolution before merging — все обсуждения в PR должны быть закрыты.
  • Require linear history — запрет коммитов слияния: только squash или rebase.
  • Do not allow bypassing the above settings — по умолчанию администраторы могут обходить правила; эта настройка распространяет их и на администраторов.

Если попытаться слить в защищённую ветку pull request с незавершённым или отклонённым ревью, документация приводит такой ответ сервера: remote: error: GH006: Protected branch update failed for refs/heads/main. Доступность защиты зависит от тарифа: в публичных репозиториях она есть на бесплатном плане, в приватных — начиная с GitHub Pro и Team.

SSH-ключ или токен: безопасный доступ

При push по HTTPS GitHub не принимает пароль от аккаунта: документация прямо говорит, что парольная аутентификация для Git удалена. Вместо пароля вводят персональный токен доступа (personal access token) или используют менеджер учётных данных, например Git Credential Manager. Второй путь — SSH-ключ: пара файлов, где закрытый ключ остаётся на компьютере, а открытый добавляется в настройки GitHub.

bash
ssh-keygen -t ed25519 -C "your_email@example.com"
Команда из документации GitHub. Программа спросит путь к файлу и парольную фразу — задайте её. Проверено неинтерактивным эквивалентом в OpenSSH 10.3.
terminal
$ git remote -v
origin	https://github.com/YOUR-USERNAME/coffee-site.git (fetch)
origin	https://github.com/YOUR-USERNAME/coffee-site.git (push)
$ git remote set-url origin git@github.com:YOUR-USERNAME/coffee-site.git
$ git remote -v
origin	git@github.com:YOUR-USERNAME/coffee-site.git (fetch)
origin	git@github.com:YOUR-USERNAME/coffee-site.git (push)
Git 2.54 (реальный вывод): переключение уже склонированного репозитория с HTTPS на SSH.
  • GitHub рекомендует fine-grained токены вместо classic: доступ только к нужным репозиториям и правам, со сроком действия.
  • С токеном обращаются как с паролем: не вставляют в код, в URL репозитория и в сообщения.
  • Закрытый SSH-ключ (id_ed25519 без .pub) никому не передают; на GitHub загружают только .pub.
  • Утёкший токен или ключ сразу отзывают в настройках GitHub и выпускают новый.
Как проверено4 · пройдено1 · частично2 · не запускалосьсверено 11 октября 2026 г.
  • запущено, работает

    Сценарий с remote: git init --bare как «GitHub», remote add, push -u, clone, branch -vv, ветка + push -u, правка после ревью + push, fetch и обновление ветки merge, слияние --no-ff (эквивалент Create a merge commit), pull, удаление ветки локально и на сервере, форк (clone --bare) + remote upstream + синхронизация

    docker run --rm alpine/git:latest (Git 2.54.0, Alpine 3.24, bash 5.3) — транскрипт-раннер в /w, блоки кода вырезаны скриптом из транскрипта (code.mjs); пути вида /w/aigerim/../server/coffee-site.git — реальные пути внутри контейнера. Локальный Git Bash 2.55 не подошёл: push в bare-репозиторий во временной папке падал с Filename too long (MAX_PATH Windows)

  • запущено, работает

    Обновление ветки через rebase: отклонённый обычный push и git push --force-with-lease; отклонённый push в ушедший вперёд main

    отдельный прогон того же сценария в alpine/git:latest (Git 2.54.0), реальный вывод

  • запущено, работает

    Шаблон .github/pull_request_template.md (RU и EN)

    markdownlint-cli2 0.23.3 (markdownlint 0.41.1): 0 замечаний при отключённом MD041 (первая строка не H1 — заголовок PR задаётся отдельно). Подстановка шаблона на GitHub не проверялась (NOT_RUN — веб)

  • запущено, работает

    git remote set-url с HTTPS на SSH

    alpine/git:latest, реальный вывод git remote -v до и после

  • частично

    ssh-keygen -t ed25519 -C "your_email@example.com"

    OpenSSH 10.3p1 в alpine/git: проверен неинтерактивный эквивалент с -f/-N/-q (ключ создан во временном контейнере и уничтожен вместе с ним); интерактивный ввод парольной фразы не запускался; добавление ключа на GitHub и ssh -T git@github.com не выполнялись

  • не запускалось

    gh pr create --base main --title … --body-file … --draft

    флаги сверены с cli.github.com/manual/gh_pr_create и docs.github.com (Creating a pull request); не запускалось, чтобы ничего не создавать на реальном GitHub (и gh недоступен в этом окружении)

  • не запускалось

    Веб-шаги GitHub: Fork, Compare & pull request, draft, ревью (Comment/Approve/Request changes, suggestions), Update branch, Merge/Squash/Rebase, Delete branch, защита веток и rulesets, GH006

    описаны по docs.github.com на дату проверки; на реальном GitHub ничего не создавалось

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.