I like rebase because it's prettier, but I also think there's an issue with the non-rebase case that the OP has missed: unless you rebase, you're setting yourself up for failure in the `git bisect` case: when you step back to find the source of an bug you're tracking down, the worst possible case is that you find it's introduced by a merge and not present in either of the parents. That's much more likely if your merge commits contain fix-ups of their own, which in turn is much more likely if you've got overlapping branches. If you rebase-and-merge then the merge won't have any extra changes, and if you rebase-and-fast-forward then you won't have any merge commits at all.
Also see comment [3] by TheCorey mentioning you can use `git bisect --skip`. However, in the mentioned case with Neovim, very few commits actually built, making the process non-deterministic somehow (it kept going in cycles).
[1]: https://github.com/neovim/neovim/issues/6431
It also separates changes you made to implement your functionality work relative to the branch point from the extra changes you need to have your functionality work relative to the merge point. With rebase, you get the opportunity to put all the code to implement a feature in one place.
Remember that the usual caveats about writing software apply: you're doing this for the future-you and future-team-mates who come along in a few months trying to make sense of it all. Make life easy for them.
But if you had to resolve conflicts in a merge, you might also have had to make changes, and now you've got to work out whether the breakage was because of the combination of features that hadn't been tested together before or because someone changed something while resolving conflicts. Speaking from experience, that's really difficult.
If you rebase instead, the commit that breaks the build contains the code change that breaks the build, and if it's because of a rebase error you've got the opportunity _as you're rebasing_ to detect that.
I like the separation between having full freedom in a feature branch without the extra hassle of having to consider timing (active repos usually suffers from this) or typo:ing in commit metadata.
Bug Fix branch, working on a shared branch, use rebase. The branching in those case is a side effect the exact time two colleague committed on the same branch. There is no information you gather from that, it is just a technical blip in the history.
Not quite sure the obsession people have in using only 50% of the tool they have because the other 50% could be misused.
Similarly, digging for a bug in several hundred commits is going to be shit. Hindsight is very deceptive with bug hunting - they always hide in plain sight, if you know where it is, it is easy to imagine "oh, I would have seen it immediately with a merge". Maybe, probably not.
You see that of that kind of overreaction in Management. There is a bug in production, we must introduce more testing, or more testing phase, or change the way we design the around that single experience. But sometimes, bugs just happen.
I'd also argue that merge and rebase represent fundamental differences in what commits mean. The former being commits are history, and the latter being commits are features.
Yes, that's how I thought it was meant to be used.
(At first I got confused reading this article because I assumed that's what they were talking about too.)
>> the feature branch is reset to master, after which the commits are re-applied on top of feature
Is that not rebasing 'feature' on top of 'master'?
But if you use GitHub pull requests and squash your commits, that seems almost the same? The history is there, just not in the repo.
Git already has merge commits, that can be used to label and describe bigger sets of changes in retrospect. There is no need to rewrite the commit history with the benefit of hindsight, it only erases the record of how changes were arrived at, thus losing the opportunity to revisit conclusions from debugging or experiments.
You can also use merge commits to describe sub-units of work in your feature branch. Just rename your branch to some subtask and merge that into your feature branch.
edit: towards the middle of the article, the author also opines "What motivates people to rebase branches? ... I’ve come to the conclusion that it’s about vanity. Rebasing is a purely aesthetic operation. The apparently clean history appeals to us as developers, but it can’t be justified, from a technical nor functional standpoint."
For me, those are often arbitrary - I can't get something to work at a certain point, so I make a WIP commit with the buggy work at that point, and will come back to it the next day.
Before I merge my branch back into master, though, I want my commit history to be useful. "This is the point where I went home or was disturbed that day" is not useful to future developers. "This is the work I did on this individual feature and everything that's needed to run it and to have the tests succeed is in this commit, and this was the reasoning behind what I did", however, is.
In other words, I rebase to divide my codebase into non-arbitrary units of code, not based on chronology, but on what is useful together.
There's no guarantee that any of these features will be built in order since they might have different priorities, difficulties or level of acceptance so it's hard to tell which one must or will be done in what order. Rebasing pretty much settles that while merging is much more sane to that workflow. It's harder to keep it functional yes, but it's enabling parallel development. Using rebasing and/or merge isn't a source control problem but a feature management problem.
This is also potentially a big deal if you're doing maintenance bugfix releases for a project - that's way easier if porting the bugfix to an older branch just requires cherry-picking a single commit.
The author is correct that a rebase may require resolving conflicts; but then those conflicts need resolving anyway if you choose to merge instead. It is also possible to miss a "semantic conflict" that doesn't make git complain but produces a commit that doesn't build -- you can put in tooling that checks that each commit builds individually to avoid the "oops, bisect on master isn't much use" issues.
Normally if you pull from the remote and have local commits not in the remote, you'll have an "extra" merge. Automatic rebase on pull takes care of this, to avoid those useless merges.
Have a look at https://github.com/git/git/blob/master/Documentation/merge-s... to get some idea what is going on behind the scenes in automatic merges.
So we rebase, and we merge with merge commits. This way, the developer are forced to resolve conflicts one by one on their own branch, instead of sorting out a huge pile in one go on a shared branch.
What I like about git, though, is that you can choose different approaches. Discuss in your team what you find important and use git to support that. If finding bugs with bisect is your main thing, use git in a way that makes that as easy as possible. If your have other needs, you'll have to find other ways of using the tool.
Learning and understanding the tool to use it in the best way to satisfy your needs.
This highlights a bigger issue, many people don't learn their tools well enough.
I've been teaching my fellow senior engineers how to use git the last six months because they've only been using git GUIs until then. They're terrified of merge conflicts and they often make mistakes fixing them.
I'd rather educate people and let them make their own decisions once they can understand and justify the tradeoffs.
My ideology is - prefer rebase first, resort to merge if it will let you do the gnarly merge fast. Of course this is because of the development environment I work in where we just don't use bisect and the workflow is different. I also keep in mind that if I work on a different repository with differing testing/development processes this ideology will change.
The first few days I work on any new project(due to a job/project transition), I figure out how I can iterate as fast as I can and see what my time saving points are. How to best use the git I know, on the current repo, is also a minor part of this poking process.
I'll add that another annoying thing about rebasing is that if you make multiple changes to the same piece of code that conflict with changes on master, when you rebase, you have to resolve each change independently instead of only the last snapshot. However, if the changes are in different places, I like the incremental conflict resolution process of rebasing instead of the one-shot merge commit.
It’s a builtin feature, see the documentation https://git-scm.com/docs/git-rerere and a description here: https://git-scm.com/blog/2010/03/08/rerere.html
Very handy if you use rebase with multiple commits to the same code.
I'll often have a patch that has a few things going on:
1. Refactor code in one file
2. Fix a bug in another module uncovered by the newly refactored code (maybe a code path that hasn't been hit until now)
3. Some quick fixes made while I was looking at that code
...if I made a mistake in step 1, it's likely that step 2, step 3, or both would be useful to keep. If it's squashed and merged, it may be difficult to undo just step 1. And it's good communication to keep each as their own commit because each is its own atomic change.
And I can always squash things myself as I feel the need.
Just tell me one. All git tools I know do such a terrible job at this.
Ever tried magit? I don't know if you'd find it terrible, I find it not so bad.
For example when I'm working on a feature I tend to do n things at once, but I then want to create logical commits which separate the work into logical unit which could be reverted etc. as necessary. If in this process I notice that I've forgotten something (or for that matter, realise later than I've introduced a bug / broken tests) I'll tend to create a patch commit for that logical unit and then `git rebase -i HEAD~n` and then fixup that patch into the original commit.
Also, whenever I'm done for the day instead of stashing I: `git commit -am "wip"; git push; git reset HEAD~1 --soft` (i.e. push my changes up to Origin to avoid any localized mishaps with my PC resulting in a loss of work). I then force push to my feature branch.
I know that both of these "change history", but as long as they're isolated to my own feature branches I see no reason to avoid doing so...
Easy, just fixup the commit that (re)introduced the dependency.
> You pretend that the commits were written today, when they were in fact written yesterday,
The author and committer dates will differ accordingly.
1. I can easily undo small mistakes this way,
2. It makes it easy for me to pick up what I've done the last time I worked with that codebase, and
3. It makes it easier for me to tell a comprehensive story when I'm ready to submit my PR.
That said, I do see the authors point about rebasing onto a branch with dependencies.
It's a pity the author doesn't mention some of those magnificent tools. Some tools I know/use:
CLI: tig
GUI: gitg, qgit (Linux)
Any others?GitUp is beautiful, incredibly fast and you operate directly on the (well laid-out) graph for almost everything, so you're always acutely aware of the commit history.
(I mostly use Windows + Linux at the moment and this is probably the piece of software I miss the most, even though I prefer shell for absolutely everything else)
say team a does 58 commits per day has 75 team-members and team b has 126 members that are doing 187 commits per day and there's team c, d , e .... n giving non conflicting input to your codebase.
the flow in this case would be to always rebase developer specific features on non-conflicting codebase before you are allowed to pullrequest your changes to central repo. Rebasing to master or to any feature branch developer needs to work will allow them to see any conflicting changes they have made directly in their repo (rebase conflicts and a way to solve those as they know what conflicts, they have introduced the conflicts) instead of merging it together and finding out that the repo they need to PR this stuff will utterly fail cause they are lagged behind or several developers have changed the same files.
in this case rebase flow brings the shit to developer to handle and gives them knowledge that it will break. This mainly happens cause they are not having the latest version of code whereas merge strategy will merge in the conflict and send it to others to discover that thy can't merge any more cause of other developers conflicts.
Merge strategy is not suitable in case where code is edited rapidly daily and some features take , weeks, years, are to be included. Whereas rebase strategy ensures that every developer will always send you changes that are not conflicting with up to date version of whatever is versioned.
Also, don't even get me started on `git revert`ing merge commits... Down that road lies sadness and disillusion.
It's also kind of a problem if underlying parts of the system that you depend on either are churning that much without that being communicated or if they have poorly-defined interfaces and are prone to accidentally breaking things that users depend on.
It's not entirely clear, but there may be some testing gaps too if bugs weren't found.
Maybe merge-based workflow helps a bit but it seems like the pain would still be there regardless.
I would encourage everyone to consider adopting trunk based development (https://trunkbaseddevelopment.com/). I expect this to solve many of the problems people have. Using short-lived feature branches that are merged every day, merges are trivial, and history easy to read. A rebase-based workflow wouldn't be that harmful if you use short-lived branches either, although the argument for preserving history still stands.
Thanks to all others who explained technically what upset me so much.
Or leave you with an inconclusive result if the guilty commit also happens to be one of the skipped commits
Is there a better way to keep feature branches updated with changes on master?