EdL
EdL

Reputation: 435

How to bring fork up to date to make clean pull requests?

I am making contributions to an open source repo on a forked copy on github

I normally only fix the odd bug as I find it so every few months, in the mean time lots of other developmnent happens. I made pull requests into my fork to get the new changes.

Now they appear in my pull requests back to the original repo. enter image description here

How do I make clean pull requests that only contain commits with each fix? I have tried git fetching the original repo locally and doing a merge from that but I got the same problem.

Upvotes: 3

Views: 198

Answers (1)

Schwern
Schwern

Reputation: 164669

tl;dr: Branch off upstream/master. Don't commit to your local master.

If your branch has local changes and you just merge with upstream you'll still have those local changes. When you submit a PR based on this merged master it will contain those local changes.

Let's demonstrate. Here's what you look like after merging with local changes.

# Your situation after merging with local changes.
# D - E - F are new changes from upstream.
# 1 - 2 - 3 is your local changes.

A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [master]

Now you make a branch off master and add some commits.

A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [master]
                       \
                        4 - 5 - 6 [feature]

If you submit feature as a PR with upstream/master as the base, it will drag in 1 - 2 - 3 as well as 4 - 5 - 6, because those all contain differences from upstream/master.

This is why one avoids committing directly to master, you no longer have a common base for new branches. Instead, do all your work in branches and submit them as PRs.


The simplest and safest thing is to branch your work off upstream/master instead of master. Then it doesn't matter what state your fork is in.

$ git checkout -b feature upstream/master
# then make a few commits

                      4 - 5 - 6 [feature]
                     /
A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [master]

If you have an existing branch off master, rebase it onto upstream/master. This will rewrite each commit as if you'd written it on top of upstream/master. There may be conflicts, fix them as needed.

# Before with feature based on master.

A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [master]
                       \
                        4 - 5 - 6 [feature]

# Rebase onto upstream/master the commits from master to feature.
$ git rebase --onto upstream/master master feature

                      4A - 5A - 6A [feature]
                     /
A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [master]
                       \
                        4 - 5 - 6

The original 4 - 5 - 6 will eventually be deleted.


To restore your fork's master, return your master to your upstream's master.

First, make a new branch at your existing master to preserve any local changes.

$ git checkout master
$ git branch dev  # or whatever you want to call it

A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [master]
                        [dev]

Now any local changes you made to master will be preserved in your dev branch. You can call it anything you like, but avoid a branch name that already exists upstream.

Then move your local master to your upstream's master.

$ git checkout master
$ git reset --hard upstream/master

                      [master]
A - B - C - D - E - F [upstream/master]
         \           \
          1 - 2 - 3 - M [dev]

Branches in Git are just labels that point at commits. git reset is how you move them around as you like. This is moving your local master to where upstream/master is pointing. --hard refers to what to do about the staging area and checked out files (working copy). --hard says to also reset them to upstream/master.

Now your master is their upstream/master. dev has your local changes to master.

Finally, push your new local master up to origin. Since it moved it will have to be forced. Do not use --force use the safer git push --force-with-lease.

If you want to update your local master, simply git pull. A git pull is just a git fetch plus a git merge. We'll do a git pull as two separate steps to demonstrate.

# Fetch new commits, that's G and H.
$ git fetch upstream

                      G - H [upstream/master]
                     /
A - B - C - D - E - F [master]
         \           \
          1 - 2 - 3 - M [dev]

$ git checkout master
$ git merge upstream/master

                            [master]
                      G - H [upstream/master]
                     /
A - B - C - D - E - F
         \           \
          1 - 2 - 3 - M [dev]

Since master is a direct ancestor of upstream/master no merge commit is necessary. Git will "fast-forward" master to upstream/master.

Upvotes: 3

Related Questions