Correct Git commits with git-autofixup
symflower.com
symflower.com
[alias]
# Revise into the commit that last changed File
rf = "!f() { if [ $# -eq 0 ]; then REV=\"$(git status --porcelain --untracked-files=no | sed '/^ /d;s/^.. //' | xargs -n1 git rev-list -1 HEAD --)\"; NUM_REVS=\"$(echo \"$REV\" | wc -l)\"; if [ $NUM_REVS -ne 1 ]; then >&2 echo Files in the index were not all last modified in the same commit; exit 1; fi; else REV=\"$(git rev-list -1 HEAD -- \"$1\")\"; shift; fi; git revise \"$REV\" \"$@\"; }; f"
(This alias is three times as long as my next longest aliases, which are ports of Mercurial’s id, tip, incoming and outgoing commands.)This is more coarse-grained than the technique in this article, as it only goes down to the file level—because that was sufficient for me when I wrote the alias, and probably easier to implement. I’ve been vaguely contemplating trying git-autofixup and git-absorb for a while too, which include what are essentially more polished and powerful versions of my alias.
When I am in git, I use this version from https://github.com/sympy/sympy/wiki/Git-hg-rosetta-stone#set...:
[alias]
outgoing = !git fetch && git log FETCH_HEAD..
incoming = !git fetch && git log ..FETCH_HEAD
Feature-wise, the mercurial command is way more powerful: https://www.mercurial-scm.org/doc/hg.1.html#incoming # I almost always use glog rather than log.
glog = log --graph
# “Short log”
slog = log --graph --oneline
# `git id` = `git rev-list --max-count=1` with default refspec HEAD.
id = "!f() { case \"x$1\" in x-*|x) refspec=HEAD;; *) refspec=\"$1\"; shift;; esac; git rev-list --max-count=1 \"$refspec\" \"$@\"; }; f"
# `git tip` = `git log --max-count=1` with default refspec HEAD.
tip = "!f() { case \"x$1\" in x-*|x) refspec=HEAD;; *) refspec=\"$1\"; shift;; esac; git log --max-count=1 \"$refspec\" \"$@\"; }; f"
# `git out` = glog commits that exist locally but not on the upstream branch. No way to refer to different origins, sorry.
out = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git glog \"$branch@{upstream}..$branch\" \"$@\"; }; f"
sout = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git slog \"$branch@{upstream}..$branch\" \"$@\"; }; f"
# `git in` = glog commits that exist on the upstream branch (remember to fetch them first) but not locally.
in = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git glog \"$branch..$branch@{upstream}\" \"$@\"; }; f"
sin = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git slog \"$branch..$branch@{upstream}\" \"$@\"; }; f"What you’re looking for could be spelled $(git merge-base HEAD master) (that’s a subshell invocation, outputting a single commit ID). In some circumstances dotted range notation might do: master.. includes all the commits after master and HEAD diverge. That’s good for logging, for example, but not so useful for the sort of rebasing you’re describing, where you may wish to use the merge base instead.
I'm painfully familiar with the problem they solve and am delighted to discover they exist.
I'd love some input about how they differ.
- git-autofixup has a changelog
- git-autofixup includes sober, technical documentation that actually explains how it works. OTOH, git-absorb has a great elevator pitch for users that are not so familiar with Git.
- git-autofixup does not have unresolved bugs, unlike git-absorb
- git-absorb creates fixup commits for the last 10 commits, which seems really odd. It's better to use symbolic references like @{upstream}, but it looks like they dont' support this yet?
- git-autofixup is stable and mostly done software, while git-absorb has a sizeable list of todos
- git-autofixup is written in Perl (like some tools in Git itself), making it easier to install than git-absorb which uses Rust.
Then again, as author of this article, I'm obviously biased ;)
Not really; you can pass any commit ref to git-autofixup, and it will create commits only for commits since that ref. So it's for the user to decide :)
I am struggling to understand the use case for autofixup and absorb. The descriptions talk about post-code-review changes, which means the branch has been pushed. So are these tools only for a flow that uses force-pushing regularly?
I don't think that would be a feature for my teams. We think of code review as an event in time, and changes post-code review should be clearly differentiated from changes pre-code review.
Am I missing the point?
(Before I started using autosquash for this, I'd do the same, except I had to figure out at the time of merging onto which commit every new commit had to be fixed up, rather than at the time of writing it.)
However in my original comment I was actually just referring to the scenario where you only want to rewrite history onto the same base, in which case I stand by my suggestion... In fact I find this can be a good strategy if your history has grown and you also need to rebase onto a new upstream - i.e first clean up your history onto the same base with a `git rebase -i current-base` and squash it all down and tidy it up, then do the `git rebase upstream` so you can do conflict resolution with more holistic commit diffs.
If there are any staged changes, git-autofixup only fixes those up and ignores any unstaged ones; otherwise it tries to autofixup all unstaged changes.
[1]: https://github.com/torbiak/git-autofixup/blob/master/git-aut...
As in, I'm very comfortable doing an interactive rebase, but have to figure out the correct commit to fixup on is tedious, so it sounds very useful to me if a tool can help me with that.
I'd switch away from Emacs but magit keeps me hooked!
Having said that, if autofixup works well it might be worth integrating it into magit via a plugin as it would certainly reduce the repetition.
I’m a casual Magit user, so learning from other users would be very beneficial.
That's what --autosquash is for. If you do fixup commits correctly (using --fixup), it will put them in the correct place and mark them as fixups for you.
If I understand you correctly, this requires that I think about which commit I want to fixup. Once identified, I can mark my fixup with what commit it should be combined with automatically.
My understanding of the article is that it attempts to automatically figure out which commit to combine with. That’s the feature that I’m interested in.
That’s what I’m looking for: a Magit-native way to make my little fixups without having to manually track which fixup should be applied where in history.
When you press either of those, magit pops up a commit picker which shows the current git log. Selecting a commit will then instantaneously apply your staged changes to the selected commit. It's much simpler than any of the other workflows I've seen in response to your question.
The gif in this repo (for a tool I made that simulates this behavior as a cli tool for some jealous coworkers) tries to show the workflow: https://github.com/quodlibetor/git-fixup
That said, this _doesn't_ support the "automatically figure out which commits to apply hunks to" workflow. I personally find that I use both workflows depending on the nature of my changes.
I've been meaning to write an article on a fixup workflow for a while now. Just need to find somewhere to host it.
- each work item has its own branch. When pushed, gerrit turns this into a review.
- a git alias, "alias.fixlast=commit -a --amend --no-edit" which amends the last commit to match the working directory
Plus these configurations:
branch.autosetuprebase=local
branch.autosetupmerge=always
pull.rebase=true
The effect is that every branch I create off the main "develop" branch is automatically set up to rebase from that branch when I do "git pull". So when I get some review comments for review "xyz", I just do checkout - pull - make changes - fixup - push.
Gerrit's workflow is slighly unusual in that each review must be a single git change, but it keeps a history of its own of previous changes you made to that review.
https://github.com/alblue/scripts/blob/master/git-fixup
I like the idea of having the editor definition return “true” instead of showing it; I’ll have to add that later.
``` autofixup = !"autofixup origin/master --exit-code; test $? -lt 2 && GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash $(git merge-base HEAD origin/master)" ```
Works almost every time perfectly, and when not i always get a comment during a code review.
Why during CR? Is the error not obvious to you? You should be able to check the results of your changes before pushing, right?
I usually do not look through all commits again when a basic review already happend and i "just" integrate the review comments. That is where for me personally these problems happen that git-autofixup is sometimes wrong but in over a few thousand commits in the last months this happend only thrice. Which for me just speaks for the tool IMHO. Ow the original author big time :-)
Do you generally use the default --strict setting?
It's also completely missing the point of the feature under discussion here which is to automatically decide which commit to squash the fix into.