A pull request is a proposal to merge a branch with your changes into the project's main branch: on GitHub it gathers the description, discussion, review and automated checks. The order is: create a branch, commit, push it with git push -u origin branch, open a pull request with a clear description, answer review comments with new commits and merge after approval. Without write access to the repository you fork it first — make your own copy of the project.
After this you will be able to
- Push a branch to a remote repository and open a pull request with a description, a linked issue and a checklist
- Choose a workflow: a branch in a shared repository or a fork with an upstream remote
- Answer review comments with new commits and update the branch via merge or rebase with
--force-with-lease - Explain what protected branches, required checks, SSH keys and fine-grained tokens give you
Know beforehand
Verified with
Git 2.54 (Alpine Linux 3.24)OpenSSH 10.3markdownlint-cli2 0.23
Checked against docs: October 11, 2026
Contents
- Remote repositories: remote, push, fetch, pull
- Fork or branch in a shared repository
- Branch and pull request: description, linked issue, checklist
- Code review: giving and receiving feedback
- Updating the branch: merge or rebase
- Merging the pull request and cleaning up
- Protected branches and required checks
- SSH key or token: secure access
Remote repositories: remote, push, fetch, pull
The local repository from the Git lesson lives only on your computer. To work in a team you need a remote repository — a copy on a server everyone syncs with. Remotes get short names: the main one is conventionally called origin. On GitHub the address looks like https://github.com/user/repo.git or, for SSH, git@github.com:user/repo.git.
The whole scenario below is reproduced locally: a bare repository with no working directory (git init --bare) plays the part of GitHub, and a path stands in for the URL. The Git commands are the same as with GitHub. First the project lead publishes the repository, then the developer, Aigerim, clones it:
$ 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-u links the local branch to the remote one, so plain git push and git pull work from then on.git clone URL
- What it does
- Downloads the whole repository and sets up the
originremote
git push -u origin branch
- What it does
- Sends the branch's commits to the server and remembers the link
git fetch
- What it does
- Downloads new commits into
origin/…without touching your branches
git pull
- What it does
fetchplus a merge into the current branch
git remote -v
- What it does
- Shows where the remotes point
The server accepts a push only if your branch continues its history. If someone else pushed first, your push is rejected — fetch their changes first:
$ 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.Fork or branch in a shared repository
GitHub's documentation describes two collaboration models. Which one you use depends on whether you have write access to the repository.
Shared repository
- Everyone can push to one repository
- Each task is a separate branch; the pull request is opened inside the repository
- Typical for small teams and private projects
Fork and pull
- You create a fork — your own copy of someone else's repository — and push there
- The pull request goes from a branch in your fork to the original (upstream) repository
- Typical for open source: no need to ask the owner for access
A fork is created with the Fork button on the repository page or with gh repo fork. Then you clone your fork and add the original repository as a second remote — by the convention in GitHub's docs it is called upstream. Syncing a fork means fetching from upstream, merging and pushing to your fork:
$ 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 -> maingit clone --bare); on GitHub the Fork button creates it.Branch and pull request: description, linked issue, checklist
Work starts with a branch off a fresh main. One branch, one task: that keeps the pull request small enough to actually review.
$ 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 cleangit add).Open the repository on GitHub
After a push GitHub shows a yellow banner with a Compare & pull request button for the new branch.
Pick the branches
base is where to merge (usually
main), compare is your branch. For a fork, click compare across forks and choose the head fork.Write a title and description
The title says what the change does. The description follows the template below: what was done, how to test, screenshots.
Create a pull request or a draft
Create Pull Request sends it for review. Create Draft Pull Request makes a draft: it cannot be merged, and code owners are not automatically asked to review.
To give every pull request the same structure, add a template to the repository — GitHub inserts it into the description automatically. A Closes # line with an issue number links the PR to the issue: per the documentation, merging into the default branch closes the linked issue automatically (the keywords work only when the PR targets the default branch).
## What changed
In short: which task this pull request solves and what changed for the user.
Closes #
## How to test
1. Check out the branch and run the project.
2. Open the page or run the scenario: …
3. Expected result: …
## Screenshots
For UI changes: before and after (desktop and mobile).
## Author checklist
- [ ] The branch is up to date with `main`, no conflicts
- [ ] Checks (build, tests, linter) pass
- [ ] No keys, passwords or personal data in the code
- [ ] Docs and `.env.example` are updated if needed
- [ ] The pull request solves one taskYou can create the same pull request from the terminal with GitHub CLI — the flags come from the official gh pr create manual:
gh pr create --base main --title "Add contact form page" \
--body-file .github/pull_request_template.md --draftCode review: giving and receiving feedback
The reviewer goes through the Files changed tab, comments on lines and finishes the review with one of three decisions. Until the review is submitted, its comments are visible only to the reviewer and can still be edited.
Comment
- Meaning per GitHub's documentation
- General feedback without explicitly approving or requesting changes
Approve
- Meaning per GitHub's documentation
- The changes are ready to merge
Request changes
- Meaning per GitHub's documentation
- Feedback the author should address before merging
- The change solves the task in the description and nothing more
- Checks (build, tests, linter) are green
- Names are clear; no dead or commented-out code
- Errors and empty states are handled, not just the happy path
- No secrets, passwords or personal data; user input is validated
- For UI: the mobile layout and keyboard accessibility are checked
- Every comment explains the “why” and suggests an option
Authors should remember that a comment is about the code, not the person. Fixes go in as new commits on the same branch — the pull request updates itself, and the reviewer sees what changed since last time. A reviewer can send a small fix as a suggestion: the author applies it with Commit suggestion, and GitHub creates a commit on the PR branch.
$ 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-formUpdating the branch: merge or rebase
While the review was going on, other people's commits landed in main. Update your branch before merging, so conflicts and test failures show up early. GitHub can do it with the Update branch button (as a merge or via Update with rebase); locally there are two ways.
$ 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$ 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 pagemerge
- Does not rewrite history; a normal
git pushworks - Adds a merge commit
- Safe when several people work on the branch
rebase
- Linear history without a merge commit
- Commits are recreated — you need
git push --force-with-lease - Only for your own branch that nobody builds on
Merging the pull request and cleaning up
Once the review is approved and the checks pass, someone with write access clicks Merge. The merge method is configured per repository:
Create a merge commit
- What lands in main
- All the branch's commits plus a merge commit; per the docs, a merge with the
--no-ffoption
Squash and merge
- What lands in main
- One commit with all the changes; intermediate commits do not appear in main
Rebase and merge
- What lands in main
- The branch's commits one by one on top of main, no merge commit; GitHub creates new hashes
$ 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 -> mainAfter the merge the author updates their main and deletes the branch — locally and on the server (on GitHub the pull request also has a Delete branch button for the remote branch):
$ 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 pageProtected branches and required checks
To make sure nothing reaches main without review, the repository owner enables a branch protection rule (Settings → Branches) or a ruleset; unlike a classic rule, several rulesets can apply to the same branch at once. Per GitHub's documentation, every rule by default blocks force pushes to the protected branch and prevents its deletion. The main settings:
- Require pull request reviews before merging — a number of approvals; optionally dismiss approvals after new commits and require review from code owners (CODEOWNERS).
- Require status checks before merging — merge only with green CI checks; in strict mode the branch must also be up to date with the base.
- Require conversation resolution before merging — every discussion in the PR must be resolved.
- Require linear history — no merge commits: squash or rebase only.
- Do not allow bypassing the above settings — by default admins can bypass the rules; this setting applies them to admins too.
If you try to merge a pull request with pending or rejected reviews into a protected branch, the documentation shows this server response: remote: error: GH006: Protected branch update failed for refs/heads/main. Availability depends on the plan: protection is free for public repositories, and for private ones it starts with GitHub Pro and Team.
SSH key or token: secure access
When you push over HTTPS, GitHub does not accept your account password: the documentation states that password-based authentication for Git has been removed. Instead you enter a personal access token or use a credential manager such as Git Credential Manager. The other route is an SSH key: a pair of files where the private key stays on your computer and the public key is added to your GitHub settings.
ssh-keygen -t ed25519 -C "your_email@example.com"$ 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)- GitHub recommends fine-grained tokens over classic ones: access only to the repositories and permissions you need, with an expiration date.
- Treat a token like a password: never put it in code, in the repository URL or in messages.
- Never share the private SSH key (
id_ed25519without.pub); upload only the.pubfile to GitHub. - Revoke a leaked token or key in your GitHub settings right away and issue a new one.
How this was verified4 · passed1 · partial2 · not runchecked 11 October 2026
“Run” means the code was executed in a scratch project or container and the output in the article comes from that run. “Partial” means it was checked against stubs instead of external services. “Not run” means it needs a device, an account or a key; the steps were checked against the official documentation. The verification project files and run logs are kept in the InterLAB archive.