The negativity in the comments here is unwarranted in my opinion. I've been using `git absorb` for years and it works amazingly well. I use it in addition to manual fixups. My most common uses of git-absorb, but definitely not the only, are when I submit a PR with multiple commits and it fails CI for whatever reason. If fixing CI requires changes across multiple commits (say, lint violations), then git-absorb will almost always find the right commit for each change automatically. It saves the tedium of finding the right commit for each change. False positives are virtually non-existent in my experience. False negatives do happen, but then you just fall back to the manual approach.
It seems like some would reply and say PRs should just be one commit. Or that they will be squashed anyway. And sure, that is sometimes the case. But not always. I tend to prefer logically small commits when possible, and it's not always practical to break them up across multiple PRs. Perhaps partially due to how GitHub works.
I use this workflow on all of my projects on GitHub.
If it only lets you select one, that's strictly less powerful. What if I want some parts of it into one commit and another parts into another? The `hg absorb` works for this case.
# make a fixup commit for the last time the file was modified
cff = "!f() { [ -n $@ ] && git add $@ && git commit --fixup $(git last-sha $@); }; f"
# Get latest sha for file(s)
last-sha = log -n1 --pretty=format:%h --grep 'fixup!' --invert-grep
Given a file like `git cff path/to/file.rb`, It'll find the last commit that touched that file, and make a fixup commit for it. Its great for the try-this-change-on-CI cycle: Change the Dockerfile, git cff Dockerfile, push, repeat, without it screwing up because you changed a different file while you were working on it.
I tried using this tool after seeing recommendations for it, but IME it got the parent commit wrong enough times that the work to undo the damage was more than if I had looked up the commit myself and used `--fixup` instead. So I moved back to this manual workflow pretty quickly.
I prefer having full control over my commit history, and this tool is too much magic for my taste. I'm sure that it could be improved so that mistakes are rare, but I'm not sure I would trust it enough to not have to review its work anyway.
I use lazygit for this. Keyboard driven TUI which lets you easily re-order/squash commits with minimum fuss. Being able to see them all laid out means no futzing with identifying the right ids.
Lazygit has its own built-in approach to this problem which is much more strict than what git-absorb does (it explicitly asks for confirmation if there's any ambiguity). There's an extensive writeup about it here: https://github.com/jesseduffield/lazygit/blob/master/docs/de...
The more worrying thing for me is how would you know whether it got it right or wrong? I suspect if you're using it the point is you're not going to go and inspect each commit manually. IMO something that looks right is far more dangerous than something that's merely wrong.
If it just automatically created the fix up commit and moved it to the proper place in the commit tree, I’d be happy with that. You can then run a one-liner to merge the fix up commits after reviewing.
Maybe I am being to much of a purist, but retroactively modifying commits and history? Why? Stuff happens, so do mistakes. Fix the mistakes, make another commit, and go on with your life.
git-absorb appears to take it one level further. However from the README I'm not clear on exactly what it will do in specific situations:
Does git-absorb automatically associate the most recent commit unique to a branch for a given file and apply the diff to said commit? Or what is the precise workflow and outcome in terms of which edits go into which commits?
I use magit. I still use git absorb. The issue for me is not the speed at which I can run the interactive rebase, it is the amount of looking I have to do to find the correct commit to fixup onto in a big stack of commits. git absorb figures that out for me.
TIL about `git commit --fixup` and `git rebase --autosquash`.
Interactive git rebase is by far my favorite Git tool to use, it scratches a particular itch to create perfect logically atomic commits.
That said, sometimes this kind of history editing tends to backfire spectacularly because these crafted perfect commits have actually never been compiled and tested.
Do people actually check commit history in detail so often that they absolutely find so much value in ultra clean commit history?
I never understood that obsession with 100% clean history.
When I'm hunting a mistake (even/especially my own) finding the commit that introduced the bad line via blame and then looking at that specific diff is extremely handy. Also being able to bounce back and forth between the first broken one and the one before running tests while I work out exactly what I messed up and how.
I wouldn't say I put the effort in to get to 100% clean but something like 95% clean makes future me significantly less enraged at past me's mistakes.
You don’t even have to do archaeology to benefit from clean commits, it helps tremendously during review too. How do you review nontrivial changes without splitting them in digestible chunks?
Yes, absolutely. Writing out a change log when you have clean commit history is a trivial task. Doing so when it's not clean, can be arduous and take hours, and then still end up being inaccurate.
I also frequently try to find when some bug or change was introduced in the get history, so that I can understand why it was done through the context. Context. When it is just thrown in some random commit that doesn't even have much of a commit message, it is utterly useless. When it is part of a commit that is atomic and targeted at one thing, it is trivial to see why it was done. Even when the comment isn't super helpful (which is common despite intentions, because predicting the future and what will be useful in the future is very difficult) it's still valuable because you can see the code in its full context and it's often clear what it's doing.
So yes, I am a big believer in clean commit history with atomic, single commits that represent the task someone was doing. For example, If it was a bug fix, then the bug fix will be in its own commit with a commit message describing the bug that is being fixed.
It's self-reinforcing. If commit history is reasonably clean, the barrier to doing code archeology is lower, so you reach for it more often. And if you do code archeology often, you develop a better sense of what's a clean commit history and what makes commit messages useful.
The need for code archeology depends on a project. When you writing a lot of new code it's probably less important than in a legacy thing where most changes are convoluted tweaks made for non-obvious reasons.
If I'm doing a `git blame` or digging through the annotate gutter in GitHub I prefer to find the appropriate commit context, not a commit with title "typo" or "oops" or "trying something" or "WIP".
In the latter case you can still do the `git log` archaeology to find the actual context but I'd rather not.
Some teams will squash all merges to avoid this problem, but why not have the best of both worlds by merging relatively clean branches?
'git rebase -i' meets this need and more. Everyone using git should learn to use it eventually. With it you can squash, fixup, reword, or delete commits interactively.
Glad I'm not the only one. Squashing branches pre-merge, or post-merge solves the problems of commit purity (main/develop/prod/etc always builds at every commit) while preserving the intent of any MR/PR. Squashing also makes rebases _A LOT_ easier; every bad day you've had executing a rebase is likely solved by squashing your feature/fix branch, first.
Going back in time to fix the past just to sort your changes into the "correct" bin or vicinity of changes just seems unnecessarily complex. I am also unaware of what use-cases this supports. Are teams out there forcing commits to have a stricter scope than just "anything in the codebase"? Perhaps it's a monorepo pattern? I have no idea.
Either way, you have to `git push --force` to modify your PR/MR. May as well use the stock workflow and learn how to clean up your branch.
I also endorse `git commit --amend` for anyone that identifies as a "micro-committer" but doesn't need to preserve intermediate changes.
Edit: re-reading comments - it sounds sort of like some teams prefer to bin changes like this in order to make a PR/MR browsable by commit, rather than inspect one big broad diff. Is that the case?
It sounds like this will automatically find and associate with each commit that last touched the modified lines, which is nice.
There've been plenty of times I haven't done a fixup because I couldn't be arsed to check exactly which of the three ancestor commits in my branch the tweak actually belongs with. I wouldn't even consider splitting into three fixups if there were three tweaks that belonged to three different ancestors. It sounds like this tool/workflow will do all that work with a single invocation.
git-absorb is a complementary tool to `git rebase -i`. git-absorb will create the fixup commits (from your staging area) for you and set them up for use with `git rebase -i --autosquash`.
This makes no sense to me, why would a conflict-free modifiable commit within the last 10 be the one I want to fixup any more often than roughly 1 in 10?
I fixup^ frequently, often often with conflict to resolve in the process, and I have never ever thought 'if only something would automatically choose the target commit for me', even if it was advanced AI why would I trust it to be what I wanted?
`git fzsha` being another alias of mine to choose the target commit with fzf if not given. I rarely use that though, because usually I know it's HEAD~5 or whatever from doing it a second ago, or I've already been looking at the log to work out what I want.
In our projects we only enable squash merge in GitHub and the PRs can have any commits you want. The squashed commit includes link to PR, and PR has detailed summary (which wouldn’t be practical in the commit message).
Small question to anyone with this workflow. Do you (re)run CI on every affected commit? If no, what is the point of small commits if you lose any guarantees? I much prefer the honest linear non-modified history.
Yes, I empirically found the hunk-based approach of git autofixup to work more reliably. I use it via magit.
Paired with rebase --update-refs, it's particularly helpful to automatically fixup stacked branches.
This sounds very useful, I frequently reset soft and recommit in batches of related changes before PR and this sounds like it streamlines integrating updates quite nicely
This seems neat in theory but I would be perpetually concerned that it is going to pull some change I don't mean to commit and put it into something for review. How well does it do at that, anecdotally?
It deals with things that have been staged, so if you don't want to commit then don't stage it. I believe a dirty working tree is fine (and gets ignored).
If I understand the logic it uses correctly this will nearly always attach the fixup to the right commit. It's not for me because I do this manually too fluidly but it seems like a good tool.
[+] [-] burntsushi|1 year ago|reply
It seems like some would reply and say PRs should just be one commit. Or that they will be squashed anyway. And sure, that is sometimes the case. But not always. I tend to prefer logically small commits when possible, and it's not always practical to break them up across multiple PRs. Perhaps partially due to how GitHub works.
I use this workflow on all of my projects on GitHub.
[+] [-] AndrewHampton|1 year ago|reply
> gfx='git commit --fixup $(git log $(git merge-base main HEAD)..HEAD --oneline| fzf| cut -d" " -f1)'
It shows you the commits on the current branch and lets you select one via fzf. It then creates the fixup commit based on the commit you selected.
[+] [-] nickspain|1 year ago|reply
[^1]: https://github.com/mystor/git-revise
[+] [-] kccqzy|1 year ago|reply
[+] [-] psadauskas|1 year ago|reply
[+] [-] jonS90|1 year ago|reply
[+] [-] johnnypangs|1 year ago|reply
alias gfixup="git commit -v --fixup HEAD && GIT_SEQUENCE_EDITOR=touch git rebase -i --stat --autosquash --autostash HEAD~2"
From what I understand it does the same thing as this crate for the most part. All I do after is:
git push —force-with-lease
Not sure what you get from the crate otherwise
[+] [-] emersion|1 year ago|reply
[+] [-] mckn1ght|1 year ago|reply
[+] [-] dangitman|1 year ago|reply
[deleted]
[+] [-] imiric|1 year ago|reply
I prefer having full control over my commit history, and this tool is too much magic for my taste. I'm sure that it could be improved so that mistakes are rare, but I'm not sure I would trust it enough to not have to review its work anyway.
[+] [-] 3eb7988a1663|1 year ago|reply
[+] [-] jesseduffield|1 year ago|reply
[+] [-] globular-toast|1 year ago|reply
[+] [-] adastra22|1 year ago|reply
[+] [-] dawg|1 year ago|reply
[+] [-] bradley13|1 year ago|reply
[+] [-] metadat|1 year ago|reply
https://jordanelver.co.uk/blog/2020/06/04/fixing-commits-wit...
git-absorb appears to take it one level further. However from the README I'm not clear on exactly what it will do in specific situations:
Does git-absorb automatically associate the most recent commit unique to a branch for a given file and apply the diff to said commit? Or what is the precise workflow and outcome in terms of which edits go into which commits?
[+] [-] tcoff91|1 year ago|reply
Edamagit for vscode users is not as good but it does this particular workflow great.
[+] [-] psanford|1 year ago|reply
[+] [-] accelbred|1 year ago|reply
[+] [-] bananapub|1 year ago|reply
fixup is "stage changes, users selects past commit to fixup, commit staged changes"
absorb is "stage changes, git-absorb figures out which past commit to fixup, commit staged changes"
[+] [-] parasti|1 year ago|reply
Interactive git rebase is by far my favorite Git tool to use, it scratches a particular itch to create perfect logically atomic commits.
That said, sometimes this kind of history editing tends to backfire spectacularly because these crafted perfect commits have actually never been compiled and tested.
[+] [-] ssijak|1 year ago|reply
[+] [-] mst|1 year ago|reply
I wouldn't say I put the effort in to get to 100% clean but something like 95% clean makes future me significantly less enraged at past me's mistakes.
[+] [-] asqueella|1 year ago|reply
[+] [-] freedomben|1 year ago|reply
I also frequently try to find when some bug or change was introduced in the get history, so that I can understand why it was done through the context. Context. When it is just thrown in some random commit that doesn't even have much of a commit message, it is utterly useless. When it is part of a commit that is atomic and targeted at one thing, it is trivial to see why it was done. Even when the comment isn't super helpful (which is common despite intentions, because predicting the future and what will be useful in the future is very difficult) it's still valuable because you can see the code in its full context and it's often clear what it's doing.
So yes, I am a big believer in clean commit history with atomic, single commits that represent the task someone was doing. For example, If it was a bug fix, then the bug fix will be in its own commit with a commit message describing the bug that is being fixed.
[+] [-] borrow|1 year ago|reply
The need for code archeology depends on a project. When you writing a lot of new code it's probably less important than in a legacy thing where most changes are convoluted tweaks made for non-obvious reasons.
[+] [-] playingalong|1 year ago|reply
The answer is: some do, some don't.
[+] [-] syncsynchalt|1 year ago|reply
If I'm doing a `git blame` or digging through the annotate gutter in GitHub I prefer to find the appropriate commit context, not a commit with title "typo" or "oops" or "trying something" or "WIP".
In the latter case you can still do the `git log` archaeology to find the actual context but I'd rather not.
Some teams will squash all merges to avoid this problem, but why not have the best of both worlds by merging relatively clean branches?
[+] [-] gorjusborg|1 year ago|reply
[+] [-] pragma_x|1 year ago|reply
Going back in time to fix the past just to sort your changes into the "correct" bin or vicinity of changes just seems unnecessarily complex. I am also unaware of what use-cases this supports. Are teams out there forcing commits to have a stricter scope than just "anything in the codebase"? Perhaps it's a monorepo pattern? I have no idea.
Either way, you have to `git push --force` to modify your PR/MR. May as well use the stock workflow and learn how to clean up your branch.
I also endorse `git commit --amend` for anyone that identifies as a "micro-committer" but doesn't need to preserve intermediate changes.
Edit: re-reading comments - it sounds sort of like some teams prefer to bin changes like this in order to make a PR/MR browsable by commit, rather than inspect one big broad diff. Is that the case?
[+] [-] syncsynchalt|1 year ago|reply
There've been plenty of times I haven't done a fixup because I couldn't be arsed to check exactly which of the three ancestor commits in my branch the tweak actually belongs with. I wouldn't even consider splitting into three fixups if there were three tweaks that belonged to three different ancestors. It sounds like this tool/workflow will do all that work with a single invocation.
[+] [-] burntsushi|1 year ago|reply
[+] [-] keybored|1 year ago|reply
[+] [-] MBlume|1 year ago|reply
[+] [-] OJFord|1 year ago|reply
I fixup^ frequently, often often with conflict to resolve in the process, and I have never ever thought 'if only something would automatically choose the target commit for me', even if it was advanced AI why would I trust it to be what I wanted?
^my alias is:
`git fzsha` being another alias of mine to choose the target commit with fzf if not given. I rarely use that though, because usually I know it's HEAD~5 or whatever from doing it a second ago, or I've already been looking at the log to work out what I want.[+] [-] enw|1 year ago|reply
In our projects we only enable squash merge in GitHub and the PRs can have any commits you want. The squashed commit includes link to PR, and PR has detailed summary (which wouldn’t be practical in the commit message).
[+] [-] Aissen|1 year ago|reply
[1] https://github.com/keis/git-fixup
[+] [-] cocoto|1 year ago|reply
[+] [-] globular-toast|1 year ago|reply
Has anyone compared the two?
[+] [-] dawg|1 year ago|reply
[+] [-] krobelus|1 year ago|reply
[+] [-] taberiand|1 year ago|reply
[+] [-] adastra22|1 year ago|reply
[+] [-] saagarjha|1 year ago|reply
[+] [-] mook|1 year ago|reply
[+] [-] juped|1 year ago|reply
[+] [-] thecopy|1 year ago|reply
[+] [-] jeltz|1 year ago|reply