How to avoid merge-commit hell on GitHub/BitBucket
Asked Answered
M

4

138

We're ending up with a lot of commits like this in our repo:

Merge branch 'master' of bitbucket.org:user/repo

This happens every time a developer syncs his/hers local fork to the top-level repo.

Is there anyway to avoid this merge-commit hell from cluttering all the repo log? Can one avoid them when initiating the pull-requests in some way?

I know I can do git rebase if this is done in my local VM only, is there any equivalence in the GitHub/BitBucket UI?

How do you guys do it?

Monarchist answered 3/5, 2013 at 11:58 Comment(0)
S
197

Rebase Feature Branches Before Merging

If you want to avoid merge commits, you need to ensure all commits are fast-forwards. You do this by making sure your feature branch rebases cleanly onto your line of development before a merge like so:

git checkout master
git checkout -b feature/foo

# make some commits

git rebase master
git checkout master
git merge --ff-only feature/foo

Rebase also has a lot of flags, including interactive rebasing with the -i flag, but you may not need that if you're keeping things as simple as possible and want to preserve all of your branch history on a merge.

Use the --ff-only Flag

Aside from rebasing, the use of the --ff-only flag will ensure that only fast-forward commits are allowed. A commit will not be made if it would be a merge commit instead. The git-merge(1) manual page says:

--ff-only

Refuse to merge and exit with a non-zero status unless the current HEAD is already up-to-date or the merge can be resolved as a fast-forward.

Sackcloth answered 3/5, 2013 at 12:14 Comment(6)
This is a great answer. I use rebase as often as possible. I didn't know about the --ff-only flag though. Pretty cool!Leader
Thanks for the rebase and --ff-only advices. However, as said in my question, how can I do this within the UI of GitHub/BitBucket?Monarchist
@Niklas I'm pretty sure you will need to resort to the CLI to do what you want. GitHub doesn't expose the full power of Git; just a subset of its features plus some graphical and social-networking value-adds. Good luck!Sackcloth
One thing to note with this process is that, before merging the topic branch (feature/foo) back into master, you'd better git pull origin master (if using a remote), to ensure the master branch is up-to-date. If updates were found, be sure to once again rebase master onto the topic branch before merging it back into master.Blasphemy
@CodeGnome don't call it "resorting" to the CLI...in reality you should be warning about "resorting" to the UI!Vertebrate
Also an interesting on the same topic article: derekgourlay.com/blog/git-when-to-merge-vs-when-to-rebaseHandwork
J
20

"Todd A. Jacobs" already mentioned "rebase" is the concept here. This is just a more detailed way of doing things.

Let's say you're on the master branch

$ git branch
  * master

You want to make a fix, so create a “fixbranch” which is branched from the master

$ git checkout -b fixbranch

Maybe you would have worked for a couple of days on this branch and had a couple of commits.

The day you wanted to push your commits to central master repo! Checkout master and get the latest changes from the central master repo

$ git checkout master
$ git pull origin master

Rebase your fixbranch with the master to have a clean history and resolve the conflicts if any in the local repo itself.

$ git checkout fixbranch
$ git rebase master

Now fixbranch is uptodate to with the central master, let me merge fixbranch into the master branch

 $ git checkout master
 $ git merge fixbranch

I’m done! let me push local master to the central master

$ git push origin master

https://git-scm.com/book/en/v2/Git-Branching-Rebasing

Jhvh answered 18/10, 2018 at 8:44 Comment(0)
S
1

Todd's answer covers the command line. (--ff-only, which can also configure as the default merging strategy for the git command-line.)

If you use GitLab, you're a maintainer of a repository and you're merging merge requests from other developers. If you want to avoid the merge requests from being created, open Settings -> Merge Requests in the repository and force the fast-forward merges only:

enter image description here

This will prevent the merge commits only when merging using merge requests. If you use also the command-line git merge, you need the above solution in addition.

The disadvantage of this setting is that if you have many people working on the same sources, they will have to often rebase their feature branches, as changes from their colleagues will be getting merged before theirs. But it might be a small price to pay for having a nice commit history.

How does it look like? If merging a merge request with the fast-forward strategy isn't possible, developers will get a small Rebase button on the page with their merge request:

enter image description here

They can either click on the button to rebase their feature branch using the web UI, or they can do the rebase on the command-line and force-push their feature branch to the remote. This usually triggers another pipeline to build and test the change again.

If you use GitHub, it's simpler. At first, GitHub tries rebasing the feature branches automatically, as soon as the target branch of the pull request changes. If the rebasing has no conflicts, you don't need to do it manually. Secondly, if you want to avoid the merge commits from being created, perform the merge using the last menu item from the dropdown, not by clicking on button directly, which allows creating merge commits:

enter image description here

Supernaturalism answered 4/5, 2023 at 18:30 Comment(2)
If someone uses default git pull to update their feature branch with upstream (main/master/develop) then they will get a lot of Merged 'develop' into merges (back-merges). Then they will probably (I think?) get an error when trying to finally merge into the main branch and have to do a rebase in order to fulfill this requirement (if their feature branch is sufficiently old). Is that correct?Decagram
Yes, it's correct. But developers aren't supposed to pull --merge to their local feature branches. They're supposed to use pull --rebase and then push --force to their remote. It'll keep the history of the feature branch legible.Supernaturalism
L
0

It is possible to:

  • Merge branch2 into HEAD (aka: current branch)
    • without interleaving/mangling the commit history (aka: merge onto instead of into)
    • without creating a merge-commit
    • without altering (specifically, without rebasing) branch2
    • without making interstitial changes to the [HEAD] working tree (aka: checking out another branch) which would required you to cleanup (or manually stash) your working tree and/or index (aka: staging area) changes.

Here is how it's done:

from="branch2" // You only have to change this line
to=HEAD
first_shared_commit="$(
  git merge-base "$to" "$from"
)"
list_of_commits_exclusively_in_from="$(
  git log --format=%H "$first_shared_commit".."$from"
)"

git cherry-pick "$list_of_commits_exclusively_in_from"

I have created an alias for this. I call it git merge-quiet. You can create this alias in your .git/config with the following oneliner:

git config --global --add alias.merge-quiet '!f(){ to=HEAD; from="$1"; git cherry-pick $(git log --format=%H $(git merge-base "$to" "$from").."$from"); }; f'

I believe this is the best solution because it doesn't have to create a [temporary] branch2-rebased branch to avoid altering the existing branch2. It does so by creating the branch (actually the list of commits that would make up the branch) in memory.

Lit answered 13/3, 2023 at 18:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.