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/mainis, the difference betweenfetchandpull, and whyfetch --pruneexists - 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:
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:
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:
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:
git remote -v
origin https://github.com/yourname/git-practice.git (fetch)
origin https://github.com/yourname/git-practice.git (push)
git status now says something new:
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 pushsends your new commits to GitHub.git pullgets 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.
The whole workflow, which you'll repeat for every change:
Try it: your first pull request
1 and 2. Start from an up-to-date main, and make a branch:
git switch main
git pull
git switch -c improve-readme
3. Make a change, and commit it:
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:
git push -u origin improve-readme
* [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:
gh pr create --fill --base main
It prints the PR's web address. 6. Look at it, the way a reviewer would:
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:
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:
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:
| Method | What lands on main | Good for |
|---|---|---|
| Merge commit | Every commit from the branch, plus a merge commit joining them (Lesson 4) | Keeping the full, detailed history of how a change was built |
| Squash | One new commit containing all the branch's changes | A clean history: one commit per pull request |
| Rebase | The branch's commits, copied one by one onto the end of main, with no merge commit | A 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:
git branch -r
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 fetchupdates 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 pullis afetchfollowed 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:
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:
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:
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:
cd ../git-practice
echo "- learn git push" >> ideas.txt
git commit -am "Add git push to the ideas"
git push
! [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.
git pull
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:
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:
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
What is origin in a Git repository?
Why push a new branch with git push -u origin branch-name?
What's the main reason teams use pull requests instead of pushing straight to main?
A pull request is merged with Squash. What lands on main?
What's the difference between git fetch and git pull?
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.
originis 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.
--pruneremoves 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
- Chapter 2.5 of Pro Git (opens in a new tab) covers remotes, fetch, pull and push.
- GitHub's guide About pull requests (opens in a new tab) explains reviews, checks and merging in more depth.
- The GitHub CLI manual (opens in a new tab) lists every
ghcommand.