Reputation: 137
I've looked at a ton of articles about how to rebase with Git and it makes sense...or at least I think it does. However, I'm struggling with the following...
For this scenario we have the following branches:
Ok, so now let's say that origin/master is ahead by 1 commit. So, I was told from articles and the dev team I am working on is to do the following on the jheigs branch:
$ git add ...
$ git commit ...
$ git status (ensure everything is up-to-date locally)
$ git pull (again, check and ensure that everything is ready to go)
$ git pull --rebase origin master
$ git push (defaulting to origin/jheigs)
When the rebase runs fine, what I have run into is that origin/jheigs and local jheigs have HEAD commits that don't match (given the above rebase), so at times I may have to pull and then push, which can cause conflicts. What I am confused about is...should I instead be using:
$ git push --force origin jheigs (?)
Second question...now, let's say that I've pushed and jheigs has been rebased correctly with origin/master. No conflicts existed and my jheigs and origin/jheigs are now ahead of master by 1 commit. Ok, a day goes by and I need to make more changes. So I make those changes on jheigs and add/commit. However, origin/master has no additional updates. Essentially I'm still ahead of origin/master by 1 commits (soon to be 2). Do I still follow the same process above? Or do I just add/commit and push to origin/jheigs without rebasing since I'm already ahead of origin/master?
I apologize that this is so length, but I thought I had this figured out and it's just not as smooth as I thought it would be. I want to be careful with rebasing, so any help is appreciated!
Upvotes: 0
Views: 100
Reputation: 239682
When the rebase runs fine, what I have run into is that origin/jheigs and local jheigs have HEAD commits that don't match (given the above rebase)
Yes, but the difference is your additional un-pushed work. After a git pull --rebase
, the upstream HEAD will be an ancestor of your local HEAD.
so at times I may have to pull and then push
Which is generally the correct thing to do
which can cause conflicts.
It shouldn't, unless someone else pushes in between your pull and push (in which case, you need to do it again, to incorporate their work).
Ok, a day goes by and I need to make more changes. So I make those changes on jheigs and add/commit. However, origin/master has no additional updates. Essentially I'm still ahead of origin/master by 1 commits (soon to be 2). Do I still follow the same process above? Or do I just add/commit and push to origin/jheigs without rebasing since I'm already ahead of origin/master?
Either is fine. If upstream hasn't moved, git pull --rebase
will do nothing.
Upvotes: 0
Reputation: 487755
Well, first, I would (and do) just avoid git pull
entirely, or mostly: it's meant to be convenient, but it turns out to be inconvenient. It just runs git fetch
followed by a second Git command that affects whatever branch you have checked out. Sometimes I don't want or need the fetch; other times, I want to look around at what happened when the fetch ran, before I do any second command to affect the current branch.
As you have probably read, git rebase
is really about copying (some) commits. You have some collection of commits in your repository, and now, for whatever reason, the shine has gone off some commit(s), and you want to make newer, better, prettier, shinier commits.
The way to make all of this make sense is to draw commit graphs. Remember that there are multiple clones—different repositories that contain mostly the same sets of commits, but not exactly the same sets—involved!
You can have Git do this for you: git log --all --decorate --oneline --graph
, or git log (with) A DOG. It's good exercise to do it by hand at first though. Also, when you do it by hand, you can draw these out horizontally, which tends to make more sense.
Remember that each commit, identified by its unique hash ID, is read-only. Each commit points back to its parent commit. A branch name like master
or jheigs
, or a remote-tracking name like origin/master
or origin/jheigs
, points to (records the hash ID of) the tip commit, and Git works backwards from these tips:
...--C--D--E <-- master
\
F--G <-- jheigs (HEAD)
This might be the graph fragment of a repository where commit E
is the tip of master
, and E
points back to D
, while you've added two commits of your own: G
, which is the tip of jheigs
and which points back to F
, and earlier, you added F
pointing back to D
.
Note that here, commits D
and earlier are on both branches.
A git rebase
will copy1 some commits to new-and-improved commits. For instance, here you might want to copy F
to a new-and-improved F'
, where the main difference is that F'
is attached to E
, not to D
:
F' [new and improved!]
/
...--C--D--E <-- master
\
F--G <-- jheigs (HEAD)
Once you've copied F
to F'
, you can now copy G
to new-and-improved G'
:
F'-G' [new and improved!]
/
...--C--D--E <-- master
\
F--G <-- jheigs (HEAD)
And now that all of the commits that are "on" (reachable from) jheigs
, but not on master
, have been copied to the new-and-improved version, you can have Git peel the jheigs
label off commit G
and paste it onto G'
:
F'-G' <-- jheigs (HEAD)
/
...--C--D--E <-- master
\
F--G [abandoned]
The old, dull F
and G
commits that were so shiny and nice yesterday are now junk, replaced with the shiny new F'
and G'
. This is because your branch name jheigs
now points to the last of the new copied commits.
1This copying is done as if by git cherry-pick
. Depending on which rebase command you use, it may actually be done with git cherry-pick
.
fetch
and push
The graph above is for your repository, but there's a second repository involved here. They have their own branch names, and their own commits. The commits that you have, that they also have, share commit hash IDs. There might be some commits that you have that they don't; and there might be some commits that they have that you don't.
If you think they might have commits that you don't, you should run git fetch
or git fetch origin
. This has your Git call up their Git, at the URL you have listed under the name origin
. Your Git calls up their Git and has them list all of their commits (by hash IDs) as given by their branch names.
If they have commits that you don't, your Git now downloads those commits. Your Git also changes their branch names, such as master
or jheigs
, so that they read origin/master
and origin/jheigs
. These new names won't interfere with your branch names.
Now that you have all the commits that they have, plus all the commits that you had before, your repository perhaps looks like this—let's assume you haven't done git rebase
yet:
H <-- origin/master
/
...--C--D--E <-- master
\
F--G <-- jheigs (HEAD), origin/jheigs
Your origin/*
is your Git's memory of their branch names. This means that their master
identifies commit H
. You have commit H
now, thanks to git fetch
. Their jheigs
identifies commit G
, just like yours.
If you now run git rebase master
, your Git will copy your F
and G
to a new F'
and G'
, built to come after commit E
, which is where your master
points. You probably would like to have them come after commit H
instead.
Here, you can do this pretty easily: you can just run git rebase origin/master
. This tells your Git to find commits that you have (i.e., F
and G
) that are not reachable from your origin/master
(i.e., their master
, as far as you can remember anyway) and put the copies after commit H
. The result looks like this:
F'-G' <-- jheigs (HEAD)
/
H <-- origin/master
/
...--C--D--E <-- master
\
F--G <-- origin/jheigs
Note that none of the origin/*
names have moved, and your own master
has not moved either. However, their jheigs
which you call origin/jheigs
still remembers commit G
.
Now you need git push --force jheigs
, to tell them: Throw away commits F
and G
in favor of the new shiny F'
and G'
. Once they agree to do that, your Git will remember their jheigs
as pointing to G'
:
F'-G' <-- jheigs (HEAD), origin/jheigs
/
H <-- origin/master
/
...--C--D--E <-- master
With no names to find them, commits F
and G
appear to vanish entirely.
Note that there may be a third Git repository out there, and a fourth or more, with another origin/jheigs
in it. If so, all those other repositories must take some action (such as running git fetch
) to get the new commits and update their own origin/jheigs
names.
Further, they might have built their own commits atop your commits before you decided to throw out your commits in favor of new-and-improved ones. If so, they may be forced to copy their commits just like you copied yours. That might even be a good thing, or it might make them annoyed.
Hence, if you're ever about to rebase commits that other people have, or might have, you should be reasonably sure that it's OK to make work for them. If no one else has the original commits, it's obviously safe to copy-and-replace them. If everyone else has agreed that this kind of copy-replace is supposed to happen, it's still fine. So rebase if it's OK to do so, and if it makes sense to do so.
Upvotes: 2