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 articlePeople 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
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
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)
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
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
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.
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.
* 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.
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.
- 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.
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.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.
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).
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.
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.
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.
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
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.
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.
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.
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
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.
Then you run `git rebase -i --autosquash origin/main` instead and the commits are already in the right order.
Keeping things neat in the repo has gained me far more than I ever theoretically lost by some conceptual "loss" of history.
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.
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
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.
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.
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.
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?
Git workflows imposed by those that don't understand git well enough is one of the most annoying things.
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.
Squash merge to main always.
Problem solved.
Diffs are what matter--not commits.
# 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.
Related
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
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)
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
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
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.