Rebasing a branch which is public

7

2

I'm failing to understand how to use git-rebase, and I consider the following example.

Let's start a repository in ~/tmp/repo:

$ git init

Then add a file foo

$ echo "hello world" > foo

which is then added and committed:

$ git add foo
$ git commit -m "Added foo"

Next, I started a remote repository. In ~/tmp/bare.git I ran

$ git init --bare

In order to link repo to bare.git I ran

$ git remote add origin ../bare.git/
$ git push --set-upstream origin master

Next, lets branch, add a file and set an upstream for the new branch b1:

$ git checkout -b b1
$ echo "bar" > foo2
$ git add foo2
$ git commit -m "add foo2 in b1"
$ git push --set-upstream origin b1

Now it is time to switch back to master and change something there:

$ echo "change foo" > foo
$ git commit -a -m "changed foo in master"
$ git push

At this point in master the file foo contain changed foo, while in b1 it is still hello world. Finally, I want to sync b1 with the progress made in master.

$ git checkout b1
$ git fetch origin
$ git rebase origin/master

At this point git st returns:

# On branch b1
# Your branch and 'origin/b1' have diverged,
# and have 2 and 1 different commit each, respectively.
#   (use "git pull" to merge the remote branch into yours)
#
nothing to commit, working directory clean

At this point the content of foo in the branch b1 is change foo as well. So what does this warning mean? I expected I should do a git push, git suggests to do git pull... According to this answer, this is more or less it, and in his comment @FrerichRaabe explicitly say that I don't need to do a pull. What's going on here? What is the danger, how should one proceed? How should the history be kept consistent? What is the interplay between the case described above and the following citation:

Do not rebase commits that you have pushed to a public repository.

taken from pro git book.

I guess it is somehow related, and if not I would love to know why. What's the relation between the above scenario and the procedure I described in this post.

Dror

Posted 2013-10-29T23:20:43.187

Reputation: 1 510

The reason git st gives that output is because git knows that your local b1 branch is tracking origin/b1, so that's what you want to rebase onto. You ran git rebase origin/master though, so you rebased ("replayed") your b1 commits on top of origin/master. – Frerich Raabe – 2013-10-30T08:18:06.603

@FrerichRaabe: Let me try and rephrase. You say that assuming origin/master and master are updated, I should rebase origin/b1 on origin/master, and then do git pull when b1 is checked out to pull the rebase to the local repository? – Dror – 2013-10-30T09:07:13.837

No, you don't need a git pull and you never rebase a remote branch (e.g. origin/master) on anything else. – Frerich Raabe – 2013-10-30T10:17:20.757

@FrerichRaabe So what is the right way here? The warnings one gets mean that after the rebase origin/b1 and b1 are not the same. This is rather obvious, but what's the right way to fix it? Or does fixing it means messing the history as @heavyd explained? – Dror – 2013-10-30T11:20:33.763

I don't know what the "right way" is because I don't know what you're trying to do. You clearly rebased b1 onto master and that means that b1 and origin/b1 diverged. I suggest that you run gitk --all in your repository to see a visualization of what happened. Note that b1 is repased onto master, i.e. anything in master is also contained in b1. But origin/b1 is still the same as before. To change this, you could do a git push --force... – Frerich Raabe – 2013-10-30T12:59:16.590

...which means that origin/b1 will be made to point to the same thing your b1 points to. But this means that you rebased a branch in a repository which others might have pulled from, which should be done only very carefully as the pro git book correctly notes. – Frerich Raabe – 2013-10-30T13:00:38.393

@FrerichRaabe but if I'm the only one (by decision) who pull/pushes to/from origin/b1 then git push --force should be safe, right? I want to have b1 and origin/b1 to be in sync. – Dror – 2013-10-30T13:13:55.863

You're safe in that case, right. – Frerich Raabe – 2013-10-30T13:24:56.417

1I would add as long as that one repo is the only repo pushing/pulling from the public remote repo you're safe. I've run into problems where I was the only one working on a project, but I'm working from two different machines. If branches ever diverge in that situation you can still have a mess. – heavyd – 2013-10-30T13:28:59.403

Answers

7

The reason you do not want to rebase commits that you have pushed to a public repository is because the git-rebase command changes history.

Now, what does that mean and why is it bad? First, I would suggest reading this section of the Git book. From that you'll learn that commits consist of a pointer to a tree object (snapshot of the files) and a pointer to the parent commit. Now when you "change history" by rebasing commits on top of new commits, you're changing the parent pointer of commits you've already made, which in turns changes the id of your commits.

The reason this is bad, is that if you share your commits publicly, and others start additional work based on those commits, then you go an change those commits, your trees are no longer in sync.

You can see all of this by issuing some git-log commands as you perform your example. I ran these right before running the rebase command:

$ git log --pretty=oneline origin/master
9b077261d1619803213201d5c7cefb757eb66b67 Changed foo in master
911ce5b247e79682ec9f73ad9a15fd3167b7e76d Added foo

$ git log --pretty=oneline origin/b1
63a57ef54e301314a9dab38de0cd9d88c59a5fba added foo2 in b1
911ce5b247e79682ec9f73ad9a15fd3167b7e76d Added foo

$ git log --pretty=oneline b1
63a57ef54e301314a9dab38de0cd9d88c59a5fba added foo2 in b1
911ce5b247e79682ec9f73ad9a15fd3167b7e76d Added foo

And now after performing the rebase, origin/master and origin/b1 are the same, but b1 is now:

$ git log --pretty=oneline b1
6687c64c37db0ee21a4d87e45d6ccb0913b8686d added foo2 in b1
9b077261d1619803213201d5c7cefb757eb66b67 Changed foo in master
911ce5b247e79682ec9f73ad9a15fd3167b7e76d Added foo

You'll notice that the "added foo2 in b1" commit has a different id than in the previous log commands. If you commit this change to your public repo, you now have two commits that have the same work done in them and it causes problems.

Now assume, instead of rebasing b1 on top of master, you just merged master into b1, your log would look like this:

$ git checkout b1
$ git merge origin/master
$ git log --pretty=oneline b1
518eb2dc6b2da0ff43ddd6837332031cc00eaad1 Merge remote-tracking branch 'origin/master' into b1
9b077261d1619803213201d5c7cefb757eb66b67 Changed foo in master
63a57ef54e301314a9dab38de0cd9d88c59a5fba added foo2 in b1
911ce5b247e79682ec9f73ad9a15fd3167b7e76d Added foo

You'll notice the extra commit that represents the merge of the two previous commits. This history can now be shared and everyone will be happy.

git-log --graph can also help to shed some additional light as to what is going on.

heavyd

Posted 2013-10-29T23:20:43.187

Reputation: 54 755

It seems to me that while merging yields a non aesthetically pleasing branch graph that it is the safest and most practical way to go about it. Why would I want to point out rebase to a team when they are likely to hose the entire repository with it? Just so I can have a pretty branch graph? – computrius – 2017-05-17T15:16:23.363

Can you please refer to the method described in the linked post? In addition, what happens if I know that the origin/b1 is used solely by myself - assuming it is some sort of backup of b1. What effect this assumption has on the situation? Does it make sense in this case to do git push --force or something similar right after the rebase? – Dror – 2013-10-30T07:26:21.347

Honestly, the workflow described in your post is a lot of extra work. You should be able to accomplish the same by just running git merge master when the foo branch is checked out. Again, try it for yourself and use git-log to see what is going on. – heavyd – 2013-10-30T13:34:09.700

Indeed, but there's one big drawback with this merging --- normally master is considered a stable branch whereas foo (or any other branch) is non-stable. Merging stable into unstable is undesired, thus the extra work. I'm trying to figure out whether there are shortcuts or not. – Dror – 2013-10-30T13:37:19.407

1Can you explain why merging stable into unstable is undesired? That shouldn't destabilize the branch any more than it already is. – heavyd – 2013-10-30T13:45:37.080

Maybe "undesired" was too strong of a wording. If you always merge unstable into stable then at the end of the day when the branch is ready and stable for its own, it is only one(!) merge back to master. This yields a cleaner history. Maybe better wording would be that this approach has the above advantage. – Dror – 2013-10-30T20:16:10.383

This is exactly what rebase does. It rewrites the commits you've made in your branch on top of the commits you're rebasing on top of. So if you rebase b1 on top of master, then the new commits in b1 would appear to be on top of all the commits in master. – heavyd – 2013-10-30T20:38:08.850

let us continue this discussion in chat

– Dror – 2013-10-31T07:25:59.880

2

Late at the party, but here is the answer for posterity:

git rebase is meant to be used locally. It re-writes history, which allows for a very nice "main line", but is dangerous in a multi-user environment

If:

  • you are the only one using the remote repository,
  • AND you are using it in one workspace only,

Then it might make sense to do this by forcing the history to be re-written. Do your rebase and then:

git push -f origin remotebranch

If any of these assumptions was not met, you may live to regret this action :-)

Read more in this blog post

Omri Spector

Posted 2013-10-29T23:20:43.187

Reputation: 121