Take a look at `git rebase --preserve-merges`

There are three ways to rebase that you should learn, and I only have 5 minutes, but I'll try to sum them up quickly:

rebase -i: This takes a single upstream (parent) commit as an argument and assumes you want to change something, or everything after that. That was the most important part to understand... rebase assumes you have some stable base, and that is the commit that you want to keep. (You can still squash other commits into it, or rewrite the message, but if you want to remove it altogether, you should pick the commit before it as the stable base.)

Say you have a commit where you tried something, and then three commits that follow which shored that up into a working something. You may want to use rebase -i and squash those small commits, to make one good commit. (You might also use "fixup" to erase those commit messages about fixing something, because this time you're just going to deliver one commit that works. As a reviewer, I often tell my coworkers that while history is nice, I don't need to see every little thing you tried if it turned out that simply didn't work. If you want me to review your commit history, you should review it too, and feel free to clean it up first before sending it over to me!)

git rebase [original-base] --onto [new-base]

This is like cherry-pick for branches. This is where `--preserve-merges` really shines. Say you have a branch that you merged another branch into, and you merged that branch into your wip-release branch, and it still isn't finished, so you want to preserve all of that history when you rebase it ahead of master, where someone else has already merged their changes in before you. The tool for this is rebase --onto. Imagine you pruned the tree, leaving the node where your work started "original-base" intact, and grafted the branch containing your work that you trimmed onto "new-base". This almost has the argument semantics of a tool like "mv" -- mv [from-file] [to-file].

Then last but not least, git pull --rebase

Someone has merged into your upstream (say, master) ahead of you, and you don't have any interesting merge history that you want to preserve. You just want to pretend you started your work after they finished theirs, and any empty merge commits you might have accumulated since then can be discarded, because it's really just one feature branch you're working on. (When you get good at working this way, you might actually find that you merge more often simply because it's so easy to discard the merges when they are no longer needed.)

You may want to do this sooner than later, because you suspect their will be a merge conflict with their work, and you want to resolve it now, while their work and their merge is fresh in their head... so you can ask them what they were thinking, and this merge won't be looming over you for the duration of your feature branch. Depending on how you work, feature branches or wip-release branches could live for a duration of several weeks or longer. Pull rebase works like `git pull` in how it resolves merges, except it takes your commits and applies them one at a time, rewriting each commit to resolve the merge. This strategy won't generate any new merge commits.

OK, one more! This one isn't a rebase, but you'll probably want it if you are using all of the above... "merge --no-ff" – once you are an expert at rebasing, you are probably going to miss those clean merge commits, because by default all of your merges are going to be fast-forwards, and there's actually some merit to the idea of having a merge commit that shows "here, a branch was ended." Basically every Github or Bitbucket will do this for you when you merge a Pull Request without choosing the "squash" option. It's handy to have in your command-line toolbox as well. Your co-workers will know that they can base their work off of yours and they can trust that you're probably not going to squash your commits behind that point again, but you can get all of that without the overhead of opening a pull request, and putting it through a formal review.

This does not have the ugly semantics of a regular merge with two parents (that is the merge that everyone complains about, which you maybe cannot readily express as one "diff" against a single parent commit). As long as you use it together with rebase you can have a clear merge history while always maintaining the capability of tracing each commit's lineage to one parent with a single, clear diff. You just get a nice empty merge commit to show that "a merge happened here."

There are many ways to use rebase, and not all of them remove history. Hope this helps!