Home > Back-end >  Is it safe to use git rebase on a shared feature branch?
Is it safe to use git rebase on a shared feature branch?

Time:07-17

A colleague of mine created a feature branch feature/dotnet6 from our main branch developing to work on migrating some of our .NET Framework projects to .NET 6.

His feature branch has been around for a while now and is 19 commits ahead and 218 commits behind developing.

A few days ago I started migrating one of our API .NET Framework projects to .NET 6.

In preparation for this I created my own feature branch feature/dotnet6-web-api from my colleague's feature branch feature/dotnet6 as I require some of the work he has already done.

After doing some work on my feature branch feature/dotnet6-web-api it is now 46 commits ahead and 218 commits behind developing.

Once the work on my branch is completed I will create a PR to merge my feature branch feature/dotnet6-web-api into my colleagues feature branch feature/dotnet6.

At which point we will have a single feature branch feature/dotnet6 that will contain ALL of the .NET 6 migration work which can then be PR'd and merged back into our main branch developing.

I would like to update both my colleague's feature branch feature/dotnet6 and my feature branch feature/dotnet6-web-api with the work that has been committed to our main branch developing since the feature branches were created.

I thought that I would checkout my colleague's feature branch feature/dotnet6 and rebase on developing. e.g.

git checkout developing
git pull

git checkout feature/dotnet6
git rebase developing
git push --force

Once that was complete I would checkout my feature branch feature/dotnet6-web-api and rebase on my colleague's feature branch feature/dotnet6. e.g.

git checkout feature/dotnet6
git pull

git checkout feature/dotnet6-web-api
git rebase feature/dotnet6
git push --force

My concerns are:

  1. By rebasing my colleague's feature branch feature/dotnet6 will that effect him?
  2. When it comes to merging the final version of my colleague's feature branch feature/dotnet6 into our main branch developing will this cause problems as the commits from the rebase will have different SHA's to the "same" commits already in our main branch developing?

Would I be better off merging our main branch developing into both of our feature branches instead or rebasing and the potential issues it could cause?

CodePudding user response:

TLDR: stick with merges

As a general rule, you should never rebase shared branches. To more directly answer your questions:

  1. Yes, rebasing will affect him. The way a rebase essentially works is it creates a temp branch off the branch you're rebasing, resets the head of your branch to the HEAD of origin/developing (or whatever commit you tell it to) and then cherry-picks your commits from the temp branch so they are on top. Cherry-picking changes the commit hashes because it creates a new commit. This means that when your colleague goes to pull, he'll have mismatching commit hashes which will cause quite a bit of confusion for git. If he has local changes of his own that he hasn't pushed, that can cause problems as well.
  • One possible way around this is git pull --rebase which performs a rebase instead of a merge. It also attempts to look at differences in the code so it can identify commits that have the same changes but different hashes. I've used it before to share branches with my colleagues and it generally works. That said, for a long-lived branch like your colleague's, I wouldn't risk it.
  1. Potentially. If you did a rebase this would normally not affect the commit hashes on origin/developing unless you specified a base prior to it. If this were to happen, git would likely show them as conflicts since it won't know whether to go with the changes in developing or your changes.

It's much safer for both you and your colleague to do the following:

git checkout feature/dotnet6
git pull origin developing   # This will merge the latest developing into your branch without the need to checkout your local copy of developing and pull.
git push
git checkout feature/dotnet6-web-api
git pull origin developing
git push

Git is excellent at merging, and this will also allow both of you to merge into each other later.

If you really want a linear history though, the two of you could do a rebase after merging your code together but before raising the PR. That way the history is linear, the merges are flattened and you won't step on each other's toes during development. However, the one downside of this approach is git won't be aware that you merged your branch into your colleague's since git basically just checks to see if the HEAD of one branch is present in another to determine if they're merged. Changing the commit hashes throws this detection off.

But that can get a bit complicated. Honestly, just stick with merges unless your project maintainers/managers have a rule against merge commits.

CodePudding user response:

As a rule of thumb:

Generally, it is frowned upon to rebase shared branches.

Perhaps though the general case does not apply to you, since I would also suggest:

Although it is frowned upon to rebase shared branches, as long as everyone sharing the branch knows what to do if the branch is rebased, then it is probably fine to do it as long as everyone is informed.

It sounds like the only person that would be affected by branch feature/dotnet6 getting rebased, is you, and by the time you finish reading this answer you'll "know what to do", so I'd say go for it (and this is what I would probably do in your shoes as well).

You really only need to tweak the second half of your plan. The commands where you intended to rebase the second feature branch onto the first feature branch simply needs to be changed from:

git checkout feature/dotnet6
git pull

git checkout feature/dotnet6-web-api
git rebase feature/dotnet6
git push --force

to:

# Assuming you have a copy of feature/dotnet6 before it is rebased,
# skip the part where you update your local copy of feature/dotnet6
git fetch
git rebase feature/dotnet6 feature/dotnet6-web-api --onto origin/feature/dotnet6-web-api
git push --force-with-lease # using --force-with-lease is usually preferred

The problem with regular rebase is you will also be replaying all of the old commits from feature/dotnet6 that are no longer on the new rebased branch. It's possible they would all simply fall out because they are empty, but it's also possible this will cause pointless conflicts during the rebase. By using the --onto option you can specify exactly where to start placing the commits to be replayed, and in this case we will place them onto the new version of that branch that is present in your remote tracking branch. This method takes advantage of the fact that you have previously checked out feature/dotnet6 and branched off of it, before that branch was rebased.

For completeness, here is another way you can accomplish your goal, using a slightly different tweak to the rebase command:

git checkout feature/dotnet6
git fetch
git reset --hard @{u} # Note @{u} is shorthand for origin/feature/dotnet6

git checkout feature/dotnet6-web-api
git rebase --fork-point feature/dotnet6
git push --force

The reason for the reset instead of the pull is so you don't merge the new copy of the commits with the old ones you already have on the branch. Note that this is identical to first deleting the branch, fetching, and then checking it out again. At this point your local copy of feature/dotnet6 will be identical to the remote copy.

Now for the rebase; you'll notice I added the option --fork-point, which tells Git to look into your reflogs of feature/dotnet6 to see if your copy of that branch has been rewritten, and if so (which it was), try to determine a better starting point for the rebase. Note --fork-point only works here if you previously had checked out a copy of feature/dotnet6 prior to it being rebased, and presumably you did.

Additional Notes:

  1. The fact that you are willing to rebase both of these feature branches makes me question the value of using a PR to merge your web-api branch into the dotnet6 branch. You both are essentially collaborating as is, so it might make more sense to either wait until your branch is finished and just PR it into developing, or once the dotnet6 branch is done it could be PR'd into developing and then you can repeat this rebase process one final time, but the last time use --onto origin/developing after that PR is completed. All this being said, it really doesn't matter; if you want to PR your branch into the dotnet6 branch it certainly doesn't hurt anything.
  2. I like rebasing. My personal preference is to always rebase unless there's a good reason not to. For example, I would probably almost never rebase developing because there's a good reason not to. And if multiple people were sharing feature/dotnet6 I probably wouldn't rebase that either. It's pretty easy to just merge origin/developing into feature/dotnet6 and be done with it. Even if you don't rewrite feature/dotnet6 you can still rebase your feature branch onto it since it's not shared. Again, for the context of this question though I'd probably rebase both branches, since you now know how to handle the fancy rebase afterwards.
  • Related