Git basics · Lesson 5 of 6 · about 35 minutes

GitHub and pull requests

Put your repository on GitHub, push and pull, understand your laptop's record of GitHub's branches, and run the pull request workflow that real teams use for every change.

By the end you'll know

  • How to connect your repository to GitHub, and push and pull commits
  • What a pull request is, and the everyday workflow from branch to merge
  • The three ways to merge a pull request, and why many teams squash
  • What origin/main is, the difference between fetch and pull, and why fetch --prune exists
  • What to do when GitHub rejects your push

From your laptop to GitHub

Everything so far has lived on your laptop. Lesson 1 described the other half: a copy of your repository on GitHub, which everyone agrees to share, and which deployments are built from. This lesson connects the two. You'll need a free GitHub (opens in a new tab) account.

You'll use the GitHub CLI, gh, GitHub's own command-line tool. It creates repositories and pull requests without leaving the terminal, and it handles logging in for Git too, which otherwise trips up a lot of beginners: GitHub doesn't accept your account password from Git. Install it:

BASH
brew install gh

(On Windows, winget install --id GitHub.cli; on Linux, see the GitHub CLI install page (opens in a new tab).) Then log in:

BASH
gh auth login

It asks a few questions. Choose GitHub.com, then HTTPS, answer Yes when it asks to authenticate Git with your GitHub credentials, and pick Login with a web browser. It shows a one-time code, opens GitHub in your browser, and you paste the code there. After that, both gh and git can talk to your account.

Put your repository on GitHub

From your git-practice folder:

BASH
gh repo create git-practice --private --source=. --remote=origin --push

That one command does four things: creates a repository called git-practice on your GitHub account (--private so only you can see it), uses this folder as its source, names the connection origin, and pushes your commits. Open your GitHub profile in the browser: the repository is there, with your whole history from Lessons 2 to 4.

The connection is called a remote, and origin is the conventional name for the main one. See it:

BASH
git remote -v
TEXT
origin	https://github.com/yourname/git-practice.git (fetch)
origin	https://github.com/yourname/git-practice.git (push)

git status now says something new:

TEXT
On branch main
Your branch is up to date with 'origin/main'.

Git is comparing your main with GitHub's. Remember that line; it's about to matter.

Push and pull

Two commands move commits between your laptop and GitHub:

  • git push sends your new commits to GitHub.
  • git pull gets commits from GitHub that you don't have yet, and adds them to your branch.

The first time you push a new branch, add -u (short for "upstream"): git push -u origin branch-name. It pushes the branch and remembers that your branch goes with GitHub's branch of the same name, so after that, plain git push and git pull know where to go. gh repo create --push did this for main already.

The pull request workflow

You could push straight to main. Teams almost never do. Instead, every change goes through a pull request (PR): "here are my changes on this branch; please review them and merge them into main". A pull request gives a change a place to be reviewed by someone else, checked by automated tests (that's GitHub Actions, covered in the CI/CD track), and often previewed: many hosting services build a test version of a website for every PR. And it leaves a numbered record of why each change was made.

Think of submitting an article to an editor. You don't slip your changes straight into the printed magazine. You hand in a draft with a note saying what you changed and why. The editor reads it, asks for fixes, checks the facts, and only then does it go to print. A pull request is that submission: the branch is the draft, the PR is the note, and merging is going to print.

The whole workflow, which you'll repeat for every change:

The pull request workflowOn your laptop: 1, start from an up-to-date main with git switch main and git pull. 2, make a branch with git switch -c. 3, commit your changes. 4, push the branch with git push -u origin. On GitHub: 5, open a pull request with gh pr create. 6, review the changes, the automated checks and the preview. 7, merge it with gh pr merge. Then back on your laptop: 8, switch to main and pull, and start again.On your laptop1. Up-to-date maingit pull2. Make a branchgit switch -c3. Commitgit commit4. Push the branchgit push -uOn GitHub5. Open a PRgh pr create6. Review and checkchanges, checks, preview7. Mergegh pr merge8. Update mainswitch, then pullstart the next oneNothing reaches main until it has been through a pull request.
The loop for every change: branch, commit, push, open a pull request, review it, merge it, then update your main and start the next one.

Try it: your first pull request

1 and 2. Start from an up-to-date main, and make a branch:

BASH
git switch main
git pull
git switch -c improve-readme

3. Make a change, and commit it:

BASH
echo "Practice repository for the Git basics track." >> README.md
git commit -am "Explain what this repository is for"

4. Push the branch to GitHub. It's a new branch, so use -u:

BASH
git push -u origin improve-readme
TEXT
 * [new branch]      improve-readme -> improve-readme
branch 'improve-readme' set up to track 'origin/improve-readme'.

5. Open a pull request. --base main says which branch it should merge into, and --fill uses your commit message as the PR's title and description, so it doesn't stop to ask:

BASH
gh pr create --fill --base main

It prints the PR's web address. 6. Look at it, the way a reviewer would:

BASH
gh pr view --web

That opens the PR in your browser. Click the Files changed tab: every line added in green, every line removed in red, the same as git diff. This is where reviewers comment on specific lines. On a real project, this is also where you'd see the automated checks pass or fail, and a link to the preview.

7. Merge it, squashing its commits into one (more on that next), and delete the branch on GitHub and on your laptop:

BASH
gh pr merge --squash --delete-branch

8. gh switches you back to main for you. Bring your main up to date with the merge you just made on GitHub:

BASH
git switch main
git pull
git log --oneline -2

Your change is on main, as a single commit, with the PR's number in its message, like (#1). That's the loop. Every change, every time.

Three ways to merge a pull request

GitHub offers three, and each leaves a different history behind:

MethodWhat lands on mainGood for
Merge commitEvery commit from the branch, plus a merge commit joining them (Lesson 4)Keeping the full, detailed history of how a change was built
SquashOne new commit containing all the branch's changesA clean history: one commit per pull request
RebaseThe branch's commits, copied one by one onto the end of main, with no merge commitA straight line of history that keeps every commit

Many teams squash: a branch often has messy commits like "fix typo" and "try again", and nobody needs those on main forever. One commit per pull request, titled after the PR, is easy to read and easy to revert (Lesson 3). The trade-off, which you met in Lesson 4: a squashed branch's own commits never appear on main, so git branch -d can't tell it was merged.

Your laptop's record of GitHub

Run this:

BASH
git branch -r
TEXT
  origin/main

This is a remote-tracking branch: your laptop's record of what a branch on GitHub looked like the last time it checked. origin/main is "GitHub's main, as of my last fetch". It's what git status compared against when it said "up to date with 'origin/main'".

The record only updates when you ask:

  • git fetch updates the record from GitHub, and changes nothing else: your branches and files stay exactly as they are. It's "go and look, but don't touch".
  • git pull is a fetch followed by a merge into your current branch. It's "go and look, and bring it in".

When in doubt, git fetch is always safe. Then git status tells you whether GitHub has commits you don't, before you decide what to do about it.

Why --prune exists. When a branch is deleted on GitHub, your laptop's record of it isn't, not even when you deleted it yourself with --delete-branch. Run git branch -r after a few pull requests and you'll find origin/improve-readme and friends still listed, long after GitHub threw them away, and they pile up. --prune clears records of branches that no longer exist on GitHub:

BASH
git fetch --prune

Any stale records are listed as - [deleted] and removed. It never touches your own branches or files.

Clone: a coworker's copy

You've only ever started with git init. When a repository already exists on GitHub, you download it with clone, which brings the full history (Lesson 1). Pretend you're a coworker, and clone your repository into a second folder next to the first:

BASH
cd ..
gh repo clone git-practice git-practice-copy
cd git-practice-copy
git log --oneline -3

The same commits, in a brand-new folder, already connected to GitHub as origin. Run git branch -r here and you'll see one extra line, origin/HEAD -> origin/main: a clone also records which branch is GitHub's default, and that's all it means. (Without gh, it's git clone with the repository's web address, which GitHub's green Code button shows.) Keep both folders open in two terminal windows: you're about to be two people at once.

In my real setup

This website has gone through more than 30 pull requests so far, and this lesson is one of them. Every one follows the loop above, with the same commands: git switch -c, commit, git push -u origin, gh pr create --fill --base main, gh pr merge --squash --delete-branch, then git switch main && git pull. The step that matters most for me is 6: my host, Vercel, builds a preview of the site for every PR, so I can check a new lesson on a real web address before it's live.

I squash everything, so each lesson is one commit on main, titled after its PR. And I learned about --prune the practical way: while writing these lessons I found about 30 stale origin/ records on my laptop, from branches GitHub had deleted long before, almost all of them with --delete-branch. GitHub also has a setting, Automatically delete head branches, that deletes a PR's branch when it's merged. I have it on for my CivicGrid repositories, and I'd forgotten to turn it on for this one.

Break it on purpose

Push when GitHub has moved on. As the coworker, in git-practice-copy, make a commit and push it:

BASH
echo "- learn git pull" >> todo.txt
git commit -am "Add git pull to the to-do list"
git push

Now, as yourself, back in your original git-practice folder, make a different commit, and push:

BASH
cd ../git-practice
echo "- learn git push" >> ideas.txt
git commit -am "Add git push to the ideas"
git push
TEXT
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/yourname/git-practice.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally.

GitHub refused. Its main has your coworker's commit, which you don't have, and accepting your push would throw that commit away. Git never lets a push silently erase someone else's work. The fix is what the hint says: pull first.

BASH
git pull
TEXT
hint: You have divergent branches and need to specify how to reconcile them.
...
fatal: Need to specify how to reconcile divergent branches.

A second refusal, and the one that confuses almost everyone. Divergent means both sides have commits the other doesn't: your commit and your coworker's. Git can combine them in two ways (merge or rebase), and recent versions of Git won't pick one for you. For now, use a merge, which you know from Lesson 4:

BASH
git pull --no-rebase --no-edit
git push

This time the push goes through: main on GitHub now has both commits, joined by a merge commit. To make plain git pull merge by default from now on, set it once:

BASH
git config --global pull.rebase false

The best fix of all is to avoid the collision in the first place: start every change from an up-to-date main (git pull first), and keep changes on branches, merged through pull requests.

Clean up

Keep both folders and the GitHub repository for Lesson 6. When you finish the track, you can delete the GitHub repository from its Settings page on GitHub (at the very bottom, under "Danger Zone").

Check yourself

  1. What is origin in a Git repository?

  2. Why push a new branch with git push -u origin branch-name?

  3. What's the main reason teams use pull requests instead of pushing straight to main?

  4. A pull request is merged with Squash. What lands on main?

  5. What's the difference between git fetch and git pull?

  6. Your push is rejected because the remote contains work you don't have. What do you do?

Key terms

  • Remote: a connection to another copy of the repository. origin is the usual name for GitHub's.
  • git push / git pull: send your commits to a remote / get its new commits and merge them in.
  • Upstream (-u): the remote branch your branch is paired with.
  • Pull request (PR): a request to review and merge a branch's changes into another branch.
  • Squash merge: merging a PR as a single new commit.
  • Remote-tracking branch: your laptop's record of a remote's branch, like origin/main.
  • git fetch: updates those records without changing your branches or files. --prune removes records of deleted branches.
  • git clone: downloads a repository, with its full history, into a new folder.
  • Divergent branches: both sides have commits the other doesn't, so they need combining.

Go deeper