github pull and merge

Git Pull and Merge on GitHub: Commands and PR Options

By Zest | Updated | 8 min read
A laptop on a desk with code on screen and the words PULL & MERGE

git pull downloads new commits from the remote and merges them into your current branch: it is git fetch followed by git merge (or git rebase, if you configure it). A pull request is different. It is GitHub’s page for proposing that one branch be merged into another, with review and automated checks first. When the pull request is approved, you merge it with one of three methods: a merge commit, squash and merge, or rebase and merge.

Git pull vs a pull request

A laptop on a desk showing code under a PULL and MERGE banner

git pull runs two commands:

  • git fetch downloads new commits, branches and tags from the remote. It doesn’t touch your working files; it only updates remote-tracking branches such as origin/main.
  • git merge then merges those changes into the branch you have checked out.

A pull request happens on GitHub, not on your machine. Both end in a merge; the pull request adds review and checks before it.

Workflow Use it to Best for
git pull Bring your local branch up to date with its remote branch. Updating main on your machine before you start work.
Pull request Propose changes to a shared branch and get them reviewed. Any change to a branch your team shares, and open-source contributions.

Use git pull to update main. Use a pull request to get your feature into it.

Keeping your local branch in sync

Two laptops on a desk showing GIT FETCH and GIT MERGE, with a FETCH FIRST sign

git pull fetches and merges in one step, so you see conflicts or a surprise merge commit before you’ve seen what changed. Splitting it into two steps lets you look first.

Fetch first, then merge

git fetch origin

# Commits on the remote that you don't have yet
git log main..origin/main

# Then either merge them...
git merge origin/main

# ...or replay your local commits on top of them
git rebase origin/main
  • git merge origin/main adds a merge commit that ties the two histories together. Nothing is rewritten.
  • git rebase origin/main sets your local commits aside, moves your branch to origin/main, and replays your commits on top. History stays linear, but your commits get new hashes, so don’t rebase commits you have already pushed to a branch others use.

Pull with rebase

git pull --rebase fetches and then rebases your local commits instead of merging, so your log doesn’t fill with “Merge branch ‘main’ of …” commits. To make it the default:

git config --global pull.rebase true

If you haven’t set a default and both your branch and the remote have new commits, recent versions of Git stop with “Need to specify how to reconcile divergent branches.” Pick one: pull.rebase false (merge), pull.rebase true (rebase) or pull.ff only (only fast-forward, and fail otherwise).

The pull request process

A pull request is where teammates review your change and CI runs before the change reaches the shared branch. Push your branch, then open the pull request on GitHub or with the GitHub CLI:

git switch -c fix-login-error
# ...edit and commit...
git push -u origin fix-login-error
gh pr create --base main --title "Fix login failure on invalid password"

Writing a pull request reviewers can approve

A reviewer who can’t tell what a pull request does will leave it waiting. Give them:

  • A specific title. “Fix login failure on invalid password”, not “Fix bug”.
  • The why. What problem does this solve? Link the issue or ticket.
  • The how. One or two sentences on the approach.
  • How to test it. The steps a reviewer can follow to check the change.

Keep pull requests small. In IonWarp’s data (our sister product, an AI code reviewer, across 1,232 reviewed pull requests in our own repos), 11% of pull requests under 100 changed lines got a should-fix flag on first review, against 57% of those with 1,000–2,999 lines (our write-up; one small team, so treat it as a data point, not a benchmark). More on reviews in code review good practices.

Cleaning up commit history

Before you ask for review, you can turn “WIP” and “fix typo” commits into a few meaningful ones with an interactive rebase, git rebase -i origin/main. It lets you:

  • squash related commits into one,
  • reword commit messages,
  • reorder commits so they tell the story of the change.

If your team uses squash and merge, this matters less: the pull request becomes a single commit on main anyway.

Before you merge

  • Your branch is up to date with the base branch, and any conflicts are resolved.
  • All required checks are green.
  • The title and description still describe what the pull request does.
  • You have the approvals your team requires.

Choosing a merge method on GitHub

GitHub offers three ways to merge a pull request, and each leaves a different history on main:

Merge method History on main Keeps Best for
Create a merge commit Non-linear: one merge commit per pull request. Every commit from the branch, unchanged. Large features where the individual commits are a useful record.
Squash and merge Linear: each pull request becomes one commit. The pull request as one change; the individual commits stay visible on the pull request page. Most feature branches, where “WIP” commits add nothing to main.
Rebase and merge Linear: each commit is replayed onto main. Every commit, but rewritten with new hashes. Teams that want a linear history and each commit kept.

Squash and merge is a good default: main gets one clean commit per pull request. Teams that need the full branch history, for audits for example, use merge commits. Whichever you choose, use it consistently.

Merging from the command line

The GitHub CLI merges with the same three methods:

gh pr merge 123 --merge     # create a merge commit
gh pr merge 123 --squash    # squash and merge
gh pr merge 123 --rebase    # rebase and merge

Add --delete-branch to delete the local and remote branch after the merge, or --auto to merge automatically once the required checks pass.

Setting the allowed merge methods

Choose which methods a repository allows in Settings › General › Pull Requests: check or uncheck Allow merge commits, Allow squash merging and Allow rebase merging. To limit methods for a specific branch, use a ruleset: its pull request rule can require particular merge methods, and the Require linear history rule (also available in branch protection) blocks merge commits.

Resolving merge conflicts

A conflict happens when two branches change the same lines differently. Git can’t choose, so it stops the merge and marks the conflicting files for you to fix.

Reading conflict markers

<<<<<<< HEAD
your version of the lines
=======
the incoming version of the lines
>>>>>>> feature-branch
  • <<<<<<< HEAD starts your current branch’s version.
  • ======= separates the two versions.
  • >>>>>>> feature-branch ends the incoming branch’s version.

Edit the block so it contains only the code you want (yours, theirs, or a combination), and delete all three markers. Then stage the file and finish the merge:

git add path/to/file
git commit

Resolving conflicts in VS Code

VS Code highlights each conflict and puts actions above it:

  • Accept Current Change keeps your version.
  • Accept Incoming Change keeps the other branch’s version.
  • Accept Both Changes keeps both, one after the other. Check the result: it can duplicate or break logic.
  • Compare Changes opens a side-by-side diff.

Save the file, then git add and git commit as above.

Resolving conflicts on GitHub

For simple conflicts, where both branches changed the same lines, GitHub has a web conflict editor. On the pull request, click Resolve conflicts, edit the markers, click Mark as resolved for each file, then Commit merge. This merges the whole base branch into your branch. Other conflicts, such as a file one branch deleted and the other edited, have to be resolved locally.

The best prevention is to sync with main often and keep pull requests small, so fewer lines overlap.

Delete the branch after the merge

Once a pull request is merged, delete its branch so stale branches don’t pile up. Locally:

git switch main
git pull
git branch -d fix-login-error

git branch -d refuses to delete a branch that isn’t fully merged into the branch you’re on. After a squash or rebase merge, your local commits never reached main with the same hashes, so Git refuses even though the work is merged. Check that the pull request is merged, then use git branch -D fix-login-error.

On the remote:

git push origin --delete fix-login-error

Other people’s clones keep a stale origin/fix-login-error until they run git fetch --prune.

To skip the remote step, turn on Automatically delete head branches in Settings › General › Pull Requests; GitHub then deletes the branch when its pull request merges. gh pr merge --delete-branch does both sides in one command.

Frequently asked questions

What’s the difference between git pull and git fetch?

git fetch downloads new commits from the remote and updates remote-tracking branches such as origin/main, without touching your files. git pull runs git fetch and then merges (or rebases) those commits into your current branch. Fetch first when you want to see what changed before you integrate it.

How do I undo a merged pull request?

On GitHub, click Revert on the merged pull request; GitHub opens a new pull request that undoes it. From the command line, run git revert -m 1 <merge-commit-hash> and push; -m 1 keeps the first parent, the base branch, as the mainline. Don’t use git reset on a shared branch, because it rewrites history your teammates already have.

Can I merge a pull request with conflicts on GitHub?

No. GitHub disables the merge button until the conflicts are resolved. Fix simple conflicts in GitHub’s conflict editor (Resolve conflicts), or merge the base branch into your branch locally, resolve them, commit and push; the pull request updates on its own.

Which merge method should I use?

Squash and merge suits most feature branches: main gets one clean commit per pull request. Use a merge commit when you need every commit of the branch kept as it was, and rebase and merge when you want a linear history that still keeps each commit. Set the allowed methods in Settings › General › Pull Requests so everyone uses the same one.