June 20th, 2024

I kind of like rebasing

People debate Git workflows, favoring rebasing for a linear history. Redowan Delowar prefers rebasing to consolidate changes and maintain a clean commit history. They discuss interactive rebasing benefits, squashing commits, handling conflicts, and offer practical tips.

Read original articleLink Icon
I kind of like rebasing

People often debate the merits of Git workflows, with some favoring rebasing for its ability to create a linear history and tidy up messy commits. Redowan Delowar shares their preference for rebasing, highlighting how it helps them consolidate changes and maintain a clean commit history. They outline a workflow for rebasing a feature branch onto the main branch, emphasizing the importance of interactive rebasing to organize commits effectively before submitting a pull request. Delowar also discusses how to squash multiple messy commits into a single coherent one using interactive rebasing commands. They provide insights into handling merge conflicts during the rebase process and offer tips on running tests or commands during interactive rebasing sessions. Overall, the article serves as a practical guide for developers looking to streamline their Git workflows through rebasing techniques.

Related

A specification for adding human/machine readable meaning to commit messages

A specification for adding human/machine readable meaning to commit messages

The Conventional Commits specification simplifies commit messages for clarity and automation. It categorizes changes, aids in generating changelogs, and promotes organized development practices without strict case sensitivity requirements.

Avoiding Emacs Bankruptcy

Avoiding Emacs Bankruptcy

Avoid "Emacs bankruptcy" by choosing efficient packages, deleting unnecessary configurations, and focusing on Emacs's core benefits. Prioritize power-to-weight ratio to prevent slowdowns and maintenance issues. Regularly reassess for a streamlined setup.

Software Engineering Practices (2022)

Software Engineering Practices (2022)

Gergely Orosz sparked a Twitter discussion on software engineering practices. Simon Willison elaborated on key practices in a blog post, emphasizing documentation, test data creation, database migrations, templates, code formatting, environment setup automation, and preview environments. Willison highlights the productivity and quality benefits of investing in these practices and recommends tools like Docker, Gitpod, and Codespaces for implementation.

Copy-on-Write Performance and Debugging

Copy-on-Write Performance and Debugging

The article discusses Copy-on-Write (CoW) linking in Dev Drive for Windows systems, enhancing performance during repo builds. CoW benefits C# projects, with upcoming Windows updates enabling CoW by default for faster builds.

Microfeatures I love in blogs and personal websites

Microfeatures I love in blogs and personal websites

The article explores microfeatures for blogs and websites inspired by programming concepts. It highlights sidenotes, navigation tools, progress indicators, and interactive elements to improve user experience subtly. Examples demonstrate practical implementations.

Link Icon 54 comments
By @AceJohnny2 - 5 months
Ah! My people.

I, too, much prefer a rebase-heavy workflow. It allows me to both have a dirty "internal" history and clean up for publication.

As a side-effect, it also makes me comfortable having a mostly linear-history for what I publish, as opposed to a many-branched, merge-heavy one, which I dislike, and makes history confusing.

I reject the argument that a no-rebase, merge-only history "preserves the true history of how commits were created", because I believe that is irrelevant. What is relevant is what the tree looks like once the merge (or rebase) lands.

Should a merge conflict arise, in a rebase workflow the conflict resolution is folded into the rebased commit, so it looks like it was fine all along. In a merge workflow, the fix is in the separate merge commit. In both cases you still have to handle the merge conflict. And in my opinion it is not significant for the merge conflict resolution to be separate from the original commit itself because, again: what's important is the final state of the repo.

By @000ooo000 - 5 months
Everyone's got an opinion on "rebase", and IMO you can safely ignore those that conflate:

* the rebase command, as a means of adjusting commits

* the practice of squashing all commits into a single commit (typically, but not always, via rebase)

* the "rewriting of history", whether on public or private branches

* rebasing on merge vs. creating a merge commit

This article starts by saying they "like rebasing" only to then say

>Git rebase allows me to squash my disordered commits into a neat little one

Rebase allows this practice but you could just as easily `git reset @~` a few times and then `git add .; git commit -m "My big commit"`. Alternatively the author could just `git add .; git commit --amend --no-edit` throughout the task and end up with the same result. To be fair to the author, they later add that they don't always squash to a single commit, but here I'm talking about "rebasing" vs. commit practices. Rebase is just a tool which allows various practices and it's tiresome seeing the same arguments against this nebulous "rebase", when the arguments are actually directed at practices.

By @mrinterweb - 5 months
Atomic commits can be great. I really prefer to see intentional atomic commits with meaningful messages than 20+ files changed with a commit message: "Feature X".

I am 100% onboard with devs squashing their "lint fix", "test fix", "whatever minor fix to get CI working", "generally meaningless commit to cross the finish line". Also, if devs are working on something they check in a "WIP" commit. It is great if you smash your WIPs into a single meaningful commit. These manual squash strategies require some discipline to clean things up.

I think the "squash branch to a single commit" merge strategy defeats the purpose of atomic commits. Of course devs will be bad at atomic commits if the commits will inevitably smashed to a single commit. IMO squashing branches on merge is a bad version control strategy. I love it when commits are intentional.

One rule I have for any rebasing is, when there may be more than one person using the branch, no more rebasing that branch.

By @quibono - 5 months
Personally I am squarely in the rebase team although I'm not completely anti-merge. I find the interactive rebase to be so useful that I pretty much never rebase non-interactively. I wonder if part of the reason the merge flow is more popular (or at least it seems more popular) is because native Git tooling around rebasing isn't the best. In fact doing anything more than the simple reword/squash/reorder (like commit splitting or moving chunks between commits) is abysmal. Magit really shines here and makes it a breeze.
By @switchbak - 5 months
I had a colleague (smart guy, lots of impressive Ivy League creds after his name) who just _insisted_ that rebasing was evil. There was simply no way to communicate that:

- I can rebase all I want in my own private repo

- Rebasing is fine if no one else thinks they can depend on your branch's history

- You obviously don't rebase master (!) or a branch that you'll collaborate on (not without proper warning at least).

I've seen this attitude more than a few times, and I think it's fear born of ignorance. I just don't get the inability to consider a different perspective.

Edit: formatting.

By @fleekonpoint - 5 months
I also like rebasing. Sometimes my squashing cannot be completed without manual intervention, so I'll do a squash merge into a temporary branch instead:

   git checkout main
   git checkout -b squash-branch 
   git merge --squash [branch-to-rebase]
At this point I usually git diff the two branches as a sanity check before merging back into main:

    git diff [branch-to-rebase]
    git checkout main
    git merge squash-branch
I am normally able to squash rebase 99% of the time using git rebase -i main, but doing a git merge --squash into a temp branch has saved me a lot of hassle over the years.
By @krick - 5 months
That's pretty weak defense of rebase. It almost seems like an attempt to promote the opposite. To "squash my disordered commits into a neat little one" is like the only bad and questionable practice involving the word "rebase" which people can rightfully object to. And rejecting that they will usually (unjustly) reject a dozen of other practices involving "rebase". "rebase" does not equal "squash". Using "squash" checkbox in Gitlab is not synonymous to "rebasing".

If you actually care about organizing you disordered commits, you can do it in a sane way and use git rebase -i master. That's basically how I work all the time, I don't even try to write meaningful commits first, I even commit stuff I know I must drop later and I commit every little incremental change file-by-file (to make re-ordering commits easier). Then I'll rebase them interactively to review what I did and make several (most of the time) clean atomic commits.

Then, rebase is simply superior to making merge commits all over the place, because it... well, "does nothing" as opposed to "makes your commit tree into a complete mess". There can be reasons why you would rather not bother rebasing on a particular project, but if you don't have them (i.e. you just don't have a strong justifiable opinion on that matter), just set [pull] rebase = true, and use git merge --ff whenever possible. Most of the time it doesn't even feel any different and just works.

By @osigurdson - 5 months
The problem with rebase is it creates a lot of friction when sharing with others. Even if a single dev has two checkouts, it can get confusing ("can I just pull the changes or do I need to reset the branch? - dammit what did I do again? I guess I'll have to review both checkouts history in detail"). Merge based git push / git pull in the other hand, have none of this, thus conceptually much simpler.

Furthermore, since pushes must necessarily overwrite (--force), you actually risk loosing work.

This is all fine and doable but you have to be "on the ball" / paying close attention all the time thus introducing higher cognitive load.

The bottom line is getting work done and making history look good are competing objectives to some extent. There are all kinds of reasons to commit code (I got something working, I want to share something, I need to try this on another machine, etc.). Thus, the only way for commits to appear logical / atomic is to review the current state and refactor it to a logical looking but artificial series of commits. I can't imagine such efforts being useful enough to actually do in most cases. Perhaps even a task better suited to AI.

In reality, the PR is the only commit that anyone cares about. Everything else is mostly just noise. Therefore it would be best if the PR was a first class concept in git vs something only available outside of it (i.e. github).

By @elijahbenizzy - 5 months
100% team rebase -- I think the fear comes from "rewriting history", which sounds scary until you realize that git is entirely immutable data store (through the API at least). Blockchain for the win!

I find merging to be extremely tough to work with -- I don't think I actually got good at git until I learned to rebase entirely.

Also, cherry-picking (effectively an atomic rebase-type) is underrated.

By @peter_l_downs - 5 months
Enforce squash merging to main and move on with your life. Linear history on main, individual contributors can rebase and merge or format-patch or do whatever they want on their PR branches and it doesn’t matter. There are zero downsides to this approach.
By @NathanFlurry - 5 months
Our team switched over to Graphite [1] 8 months ago now. Graphite involves a lot of "restacking" (i.e., rebasing for every commit/branch in a "stack").

No one on our team had used rebasing extensively before this, but it's hard to go back. Similar to the article, the benefits we see are:

- Fast iteration with `gt m` (`git commit --amend`) and `gt s` (`git push --force`) - Using `gt restack` (`git rebase` on each parent commit) helps make merge conflicts more transparent in what happened - Commit histories are much more legible

I highly recommend giving it a go.

[1] https://graphite.dev/

By @Carrok - 5 months
> Some like to rebase, while others prefer to keep the disorganized records.

Keeping disorganized records is the only thing you can say in favor of merge commits, and I'm unconvinced that's a positive thing to begin with.

By @fastball - 5 months
The best solution is to squash messy commits into one atomic commit, and then use rebasing for a clean history on top of that. But I find that squash-merging all the commits from a PR or branch is generally not what you want.

And if you have atomic commits, then merge commits generally aren't helpful. As others have pointed out, what matters is when the code was added and what it does, which becomes very clear if you have a linear history without merge commits (but atomic commits as much as possible).

If during the development process your commits aren't atomic, that is fine, but then you should make them atomic with squashing before they actually get rebased into main.

So for example if I'm in a personal dev branch and I have "messy fix feat A", "fix feat A but better", "even better feat A", and "add feat B", I should just squash the first three into "Improve feat A" and leave the fourth alone as "Add feat B". Then after a PR review or tests or whatever you just rebase into main. Now there is still a clear delineation between my fixes to A and my addition of B, without forcing me to make separate PRs for highly related features. I end up with:

- clean history

- atomic commits

- limited mental overhead when doing quick and dirty commits

- not forcing atomic PRs which can be overwhelming

By @breatheoften - 5 months
Strongly pro rebase here.

I sometimes wish I could require squash merging to main so that history on main is fully linear -- however the fatal flaw with doing that is that it becomes impossible to know from git history whether a given branch has actually been merged to main or whether it was abandoned and left to dangle and rot forever. The inability to observe merge state for a given commit increases the effort required to know whether some given piece of work was merged (or at least requires using mechanisms that are not native to git to make such a determination) and complicates cleaning up old local branches after they are merged. If you use a graphical git client then seeing a brunch of old local branches that are already merged everywhere is noisy and distracting. When real merges are done then can easily write a script to remove all the branches that are already merged to main which helps to reduce noise and maintain better focus and faster navigation.

By @stevebmark - 5 months
I find merges more confusing than rebasing. At the end of rebasing you just have commits. Merges are commits with multiple parents, which is a confusing concept.
By @Taylor_OD - 5 months
I love rebasing. I know a few people who have disliked it when they first started doing it and most of the time its because they would not rebase into their branches often while working. This made for one big rebase at the end which can be a nightmare.
By @anordal - 5 months
Why haven't more people heard of git revise? Unless you actually want to transplant your changes onto a new base, honestly, do yourself a favor and use this instead:

https://github.com/mystor/git-revise

Its inability to change your worktree (operates in memory instead) is a big speed and safety feature: It won't invalidate your build, and you can't screw up the end state.

It also has features that regular rebase lacks, like splitting a commit and editing all commits at once. I'm more than a big fan of it.

By @PUSH_AX - 5 months
I haven’t found meaningful use for history in years. Neither has anyone on any of the teams I’ve worked on. Nitpicking git practices really feels like a waste of time. Pick something that works and roll with it.
By @jauntywundrkind - 5 months
GitHub uses the committee date, and the committee date changes with rebase. So I almost always --committer-date-is-author-date. Most people don't and it kind of makes their rebases ugly in GitHub, appear to all have happened at time of rebase, which is obviously no good & useless.

Rebasing is excellent. It's a whole suite of tools, unlike merge, which is just merge, for managing history. Rebasing is how I build a sensible history.

I should see if there's some way to set --committer-date-is-author-date by default. Also haven't found a way to make git pull --rebase do c-d-i-a-d.

By @micimize - 5 months
Rebasing potentially allows for clean, clear documentation of changeset intent in a way that integrates easily with our existing tooling, at the expense of a potential footgun in highly collaborative scenarios. Ie, if someone forked off your branch or referenced a commit you might break a reference accidentally. So it can be nice, but really depends on the environment you're working in.

Gerrit solves the referencing issue by using git notes and maintaining its own ID for changesets across rebases. Without such a system, some seeming benefits of the rebase workflow, like atomically reviewable stacked commits, become fairly awkward. IE, if we want to review commits themselves, but their references get clobbered due to a rebase, that's no good (this was the case a few years ago with GH's rebasing support, maybe improved since).

IMO, that we have to make the "rebase or merge" tradeoff at all, or accept the deep limitations of pure commit-based history, is fairly sub-optimal. It'd be nice if more tooling/workflows were built more around notes. I envision someone/some bot going back and annotatinb a range of commits with a note that associates them into some coherent code documentation system, or amends some faulty assertion, links artifacts, etc. That way blame could take us to salient docs or surface behavior snapshot gifs or w/e. From directly within IDEs

By @jaredsohn - 5 months
I look forward to the day that I can run a local LLM (for confidentiality reasons) to automatically reorganize commits within my PR. Should be very safe compared to generating code or merging/rebasing other code since it is just changing the grouping of commits and the final code should be unchanged.

This would free up developers to spend more time solving problems for the business instead of tidying up code.

Could also delay doing this to when somebody is trying to understand the code (during review or later) or doing a bisect.

By @overgard - 5 months
I feel like squash-merge accomplishes what I want (easy revertibility and simpler history). I'll admit I need to learn it better, but every time I've tried to use rebase I feel like I'm playing with fire and I end up doing silly things like making a backup of the whole folder. I just think the process is unintuitive and somewhat scary, and I don't get the net gain. Like, outside of looking at the last couple days is anyone really spelunking into history that much anyway?
By @danjc - 5 months
Your commit history is part of your product, not scaffolding. Well designed commits make a codebase a pleasure to work on and reduce the accrual rate of technical debt.
By @jaza - 5 months
I just git commit --amend and then git push -f origin feat_branch all my incremental changes. Saves me having to think of even one word commit messages like "typo" or "renaming". And saves me having to do an interactive rebase later (which basically has the same end result as git commit --amend anyway). Nobody on my team cares about those incremental changes, including me.
By @jacobr - 5 months
Any article about rebase is incomplete without the mention of `git commit --fixup fb2f677`. When committing you usually already know which commit you’re “fixing” so you specify that.

Then you run `git rebase -i --autosquash origin/main` instead and the commits are already in the right order.

By @dham - 5 months
I don't know why anyone cares about this stuff. You can literally do whatever you want on your private branch. If you want to make your life harder, that's cool. Just squash into the main and call it a day. None of this stuff matters anymore, like it did when I started my career.
By @kutenai - 5 months
I've looked at the "arguments" for not rebasing and reject them. I've never once thought 'oh shit, I wish I had not rebased that'.

Keeping things neat in the repo has gained me far more than I ever theoretically lost by some conceptual "loss" of history.

By @pseudoramble - 5 months
Not too interested into diving into the debate itself, but one minor point I wanted to add to the article where they count the commits to squash and then do `git rebase -i HEAD~n` is that you can replace this strategy with using the branch you're targeting. So if you're working on a feature branch to merge into `main` you can update the local main branch first, then punch in `git rebase -i main` and it'll handle finding all the commits for you.

I'm sure there's even more clever ways to do this, as it always seems like there's more when it comes to git. This is just the most intuitive way I've seen so far, and so it sticks in my mind.

By @cocoto - 5 months
In my opinion squashing is all you need. You keep the organic history on the original branch and the clean and easy to follow history on the master branch. Ideal would be to have a “collapsing” commit instead of squashing for ergonomy.
By @swiftcoder - 5 months
One of the biggest barriers to rebasing these days is that GitHub's UX for pull requests doesn't keep track of comments across a rebase-and-force-push. Frustrating for those of us who prefer this style
By @xs83 - 5 months
I will die on the hill of squash-merge only. It doesnt make the history that confusing having branching visible with some run of the mill standardisation in process.

My biggest issue is that if you have to delay a feature then the sheer amount of conflicts you have to resolve (many of which you had nothing to do with) becomes prohibitive.

I have no problems with rebasing master or a published branch - thats the release managers problem to deal with everything on but I really dont know where this "rebase everything" comes from

By @IshKebab - 5 months
Rebasing is definitely the better option if you can do it. Sometimes it is just too much of a pain resolving conflicts if you are rebasing a branch with lots of commits and there are lots of conflicts. In that case I normally squash and then rebase. Branches where there's enough history to be worth maintaining multiple commits should - 99% of the time - be separate PRs.

The rare exception is when you have multiple people contributing to a branch before it is merged into master, but for obvious reasons you should avoid that when at all possible.

By @juancn - 5 months
The only issue with rebasing is if someone else has checked out the branch and now they have diverged. It's usually a simple fix, but it can get annoying.

I tend to checkout branches to do code reviews, when changes are complex a diff is not enough, I want an IDE to help me reason about the code (I sometimes even refactor and try different things just so I can understand the code better, so I can make good suggestions).

When I do this, and comment, and the other dev rebases, it's an extra step for me on the next CR cycle.

By @kkfx - 5 months
Rebasing is a way to cleanup when a project have a useless history because of too many small commits, badly described and so on. A dangerous way, but still a way.

The issue is another: git does next to nothing to handle forks. We can cherry pick some commits, but there is no easy way to state "we have a project that a certain point in time diverge, keeping some common base, that need to be kept in sync, ignoring the fork specific changes". This led to long and manual three-way merges. No extra help.

By @mnahkies - 5 months
This may be rose-tinted glasses, but I was fine with merges with Mercurial / https://tortoisehg.bitbucket.io/ but subsequently using git (with gitlab/GitHub ) I've been rebase + `--force-with-lease` every time. I'm happy with my current git workflow, but I can't help but feel I'm missing a trick through finding git merge commits so difficult to understand
By @oftenwrong - 5 months
The debate about whether to preserve the true history or clean it up is based on the limitations of git log. There is no reason we need to choose. Imagine you could `git summarise` a range of commits. Anyone who wanted a clean, straightforward history could read through the highest level of summarisation. If one wanted to dig deeper, they could peel away the summary and look at the log items below it.
By @captainbland - 5 months
The only rule about rebasing I care about is the golden rule of rebasing: "Never do it on a public branch". In general I'm not going to waste my team's time by messing about with it otherwise one way or the other, and personally use either rebase or amend depending on context to tidy things up locally, and use merge when interacting with public branches.
By @knallfrosch - 5 months
It’s interesting how much devs - including me! - care about git history, yet rarely do we use it in a way that makes the rebase-merge-squash differences meaningful.

Usually I just look at the file history, find the work item from the commit message and then understand _why_ the code is the way it is. That is the functionality I need to have in mind when changing the file. Who cares how it came to be?

By @kovac - 5 months
The man who designed git advises against rebasing as a default merge strategy, but the comment section is filled with comments promoting rebasing and coming up with wildly elaborate schemes to deal with its problems without addressing any of the points raised by Torvalds.

Git workflows imposed by those that don't understand git well enough is one of the most annoying things.

By @account42 - 5 months
> git log @ ^main

You can also use `git log main..` to show the commits in HEAD but not in main. I prefer the order of the arguments in the OLD..NEW syntax for the commit range (OLD, NEW] over the reversed order with separate arguments.

By @notJim - 5 months
Step 3 can be simplified slightly be doing git fetch && git rebase origin/main. No need to check out the local main.
By @ivolimmen - 5 months
I have been working with git for only 10 years. I know how to fix things when stuff goes wrong. I have no strong opinion on merging strategy. Git is now what my clients are concerned with. I am not getting paid for solving git issues (well not directly, I was never hired for it)
By @kwakubiney - 5 months
This is exactly what I do at work everytime. The only issue with this sometimes for me is when i rebase interactively and i have my commit message with all squashed commits. So I do a `git commit —-amend` on the squashed commit and tidy up the mess in the message.
By @xyzzy4747 - 5 months
If you're using complicated git commands, you're probably doing the wrong thing.
By @joshuanapoli - 5 months
I like rebasing too. When I have any significant change, I usually end up making a mix of clean-up and preparation around my new feature. I like to move the separable parts into separate branches, so that there is less to review per request.
By @phendrenad2 - 5 months
Great, just don't force it on me, or grumble when not everyone on the team does it.
By @plasticeagle - 5 months
Develop on a branch. Do whatever you like on that branch, no-one cares.

Squash merge to main always.

Problem solved.

By @Leszek - 5 months
Use `git log --first-parent` and if you merge, write useful merge commit messages. There, I've resolved the difference between merge and rebase forever.
By @dudeinjapan - 5 months
Do not like rebasing. For me, it's squash-merge all the way, and I compare the actual diff of the code on my new branch.

Diffs are what matter--not commits.

By @iamhamm - 5 months
I read that as “I kind of like freebasing” at first and thought it was a bit racy.
By @nailer - 5 months
Interactive rebase is indeed great, but:

    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    # e, edit <commit> = use commit, but stop for amending
    # s, squash <commit> = use commit, but meld into previous commit
    # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
...the CLI git text-document rebase UI is awful. Better command-line rebase UIs are available, or in Fork, I just select a bunch of tweets, right-click, and squash into parent (or use the https://git-fork.com/images/interactiveRebase.jpg where necessary).

If someone complains that CLI git is somehow more pure than using a decent GUI, ask them to rename a stash and time them.

By @oopsallmagic - 5 months
People need to get comfortable with git being a DAG, and not the degenerate case of just a straight line. Merge commits aren't scary, folks.