Reputation: 7
I've forked a repository and want to maintain and make changes to it for a long time. These changes will never be merged back into the original repo (OR). However, I do want to pull in any new changes from OR. When adding these commits to my repository, I do not want many merge commits; if there are conflicts, I know they're unavoidable. But a majority of the commits should be clean to merge.
Is there a way to "rebase" these commits from the OR?
I think git cherry-pick
is a way to do it, but the OR will have many changes made to it that I don't want to cherry-pick every single time.
I've thought about checking out OR/master, rebasing it on top of my dev branch, and then ff merge it back to my dev branch.
git checkout -b ORmaster OR/master
git rebase -i dev
git checkout dev
git merge --ff-only ORmaster
But this means every time I want to merge from OR/master, I have to check out a new branch and repeat this.
Is there an easier way of doing this?
PS: I'm also not sure how to word the title. If anyone can word it better, please let me know.
EDIT: I've added what I think I want to happen.
A--B--C <-- OR/master, origin/master, master
I add add some changes to my branch
A--B--C <-- OR/master
\
D--E--F <-- master
Someone else makes changes to OR/master.
A--B--C--G <-- OR/master
\
D--E--F <-- master
I want these changes, so I want to apply them to mine
A--B--C--G <-- OR/master
\
D--E--F--G' <-- master
I want to make another change, and someone makes changes to OR
A--B--C--G--I <-- OR/master
\
D--E--F--G'-H <-- master
I want to apply the OR change to my branch.
A--B--C--G--I <-- OR/master
\
D--E--F--G'-H--I' <-- master
As I understand it, this will prevent any merge commits, while keeping my repo up-to-date.
Upvotes: 0
Views: 1098
Reputation: 490078
I've revised this based on the edited question. It's certainly possible to do this, and you can make it a little easier to do the cherry-picking automatically, but you're going to hit the occasional stumbling block where merge would be better.
First, let's look at how to automate the cherry-picking process. Under the assumption that the OR
repository only ever adds commits—as in the drawings in the updated question—what we need is a marker of some sort, so that we know which commit is the last one we cherry-picked earlier.
This marker can take any form, and there's a built-in one that might serve, specifically the reflog for OR/master
itself. But for illustration if nothing else—and for more flexibility in the presence of reflog expiration or anything else that might cause problems—we can use an explicit branch or tag name. A branch name is the most suitable since we'll move it in the usual way that Git expects branch names to move.
Let's take, for instance, this stage:
A--B--C--G <-- OR/master
\
D--E--F--G' <-- master
(after one cherry-pick). To remember that we've specifically left off at G
, we should just set a branch name to point to G
:
A--B--C--G <-- marker, OR/master
\
D--E--F--G' <-- master
When we get to the next time there is some update, it does not matter how many commits we have added to our own `master as we will have:
I <-- OR/master
/
A--B--C--G <-- marker
\
...
The commits to copy are therefore simply marker..OR/master
and we can run:
git checkout master # if needed
git cherry-pick marker..OR/master # copy the new commits
and when this succeeds, fast-forward our marker
to OR/master
any way we like, including the rather clever (if a bit scary):
git push . OR/master:marker
(which will refuse to do anything unless the update to marker
is a fast-forward operation).
For an illustration of when it goes wrong, consider what happens if OR/master
gains a series of commits that includes a merge:
...--L
\
I--M--N <-- OR/master
/
A--B--C--G <-- marker
\
...
Now the range of commits, marker..OR/master
, includes commit M
and its second-parent L
and whatever goes before L
that's not reachable from G
. Which commits should we copy? We can literally cherry-pick M
, as long as we do it separately with a -m
argument—which will really always be -m 1
—and this may work for your situation. But it is a concern and you should be aware of it (and know what you plan to do about it). If you use git merge
, as in my original suggestion below, it's not a problem: everything just works the way it should, automatically.
(Original answer is below the dividing line.)
All git rebase
does is copy commits (as if by git cherry-pick
—sometimes literally by git cherry-pick
), then move a branch name.
You can always copy any commit that you have, so yes, you can rebase commits that you obtained from some other Git repository. That's not going to help you with your actual goal, though. What you should be doing is using git merge
.
Look at it this way: your fork is just a clone, and your clone of your fork is a clone of your clone. Except for having an always-on-line place to stash your own clone (your fork), you're essentially just cloning the original clone. We can therefore label the stuff in your local repository this way:
...--F--G--H <-- OR/master, origin/master, master
where each uppercase letter represents a unique commit hash ID. (We'll run out after 26 commits, so you can see why Git's actual hash IDs are so big and ugly—they have to be unique forever, across every commit everyone ever makes in these repositories, past or future.)
Now, at some point, you've made your own commit(s) and pushed them to origin
(your own fork) so now you have:
...--F--G--H <-- OR/master
\
I--J <-- master, origin/master
Your origin/master
is always going to match your own master
after you git push origin master
so we don't even need to bother with it here.
Meanwhile, though, the folks who own the OR repository make their own commits so that after git fetch OR
, you have this:
...--F--G--H--K--L <-- OR/master
\
I--J <-- master
You can copy their commits K
and L
to your K'
and L'
so that you have:
...--F--G--H--K--L <-- OR/master
\
I--J <-- master
\
K'-L' <-- dev
and then you can fast-forward your master
so that you have:
...--F--G--H--K--L <-- OR/master
\
I--J--K'-L' <-- dev, master
Eventually they'll make more commits:
...--F--G--H--K--L--N--O <-- OR/master
\
I--J--K'-L' <-- dev, master
(I've skipped M
to reserve it) and perhaps you have as well:
...--F--G--H--K--L--N--O <-- OR/master
\
I--J--K'-L' <-- dev
\
P--Q <-- master
but if you now git checkout dev
and git rebase OR/master
, that's just going to copy your I
and J
to I'
and J'
and copy your K'
that's a copy of their K
to a new K"
and so on:
I'-J'-K"-L" <-- dev
/
...--F--G--H--K--L--N--O <-- OR/master
\
I--J--K'-L'
\
P--Q <-- master
This really isn't getting you anywhere.
If you merge, however, you get this:
...--F--G--H--K--L <-- OR/master
\ \
I--J--M <-- master
Now they make their own commits and you make yours:
...--F--G--H--K--L----N---O <-- OR/master
\ \
I--J--M--P--Q <-- master
and once again you just git merge OR/master
to incorporate their work into your work:
...--F--G--H--K--L----N---O <-- OR/master
\ \ \
I--J--M--P--Q--R <-- master
When you do this merging, you must resolve any merge conflicts once. If you rebase, you will have to re-resolve all past merge conflicts again. (Using git rerere
can help with that, but there is a limit to how helpful this is.) The reason you only have to solve them once is two-fold:
M
, and later R
, the next merge starts with the common merge base, which is L
, and then O
: you only have to look at things they have done since the last shared commit.It's the merge commit itself that establishes the updated shared-commit merge base. Without merge commits, you do not get an updated merge base. Merge commits are good; embrace them. :-)
Upvotes: 5