Reputation: 435
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.
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
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