Closed arkban closed 1 year ago
if the commit you want to absorb is HEAD, then you can git reset --soft
that commit to leave it in the index, then absorb it. same goes for any contiguous range of commits ending at HEAD. i don't think i would want to take on the complexity of this feature if it was only for that.
now, if you want to absorb a range of commits that doesn't end at HEAD, life gets a bit more complicated. eg suppose you have a sequence of commits like this:
5555555 modify bar
4444444 modify bar
3333333 modify foo
2222222 touch bar
1111111 touch foo
now, suppose you want to absorb 3333333 and 4444444 (into 1111111 and 2222222), but not 5555555. if 4444444 and 5555555 commute, then you can reorder them to the top (eg with an interactive rebase), reset --soft, and absorb, so that's still pretty easy. but if they don't commute, you would have to remove 5555555 altogether to run the absorb on 3333333 and 4444444 (eg with an interactive rebase edit 4444444
, reset --soft both commits, absorb, then resume the rebase).
all of this is possible with the existing algorithm - there's nothing fundamentally novel here - but it's mechanically complicated. and in addition, rewording commits is probably going to be an annoying amount of code. so i don't think i'm interested in implementing this myself, and if someone were to submit a pr for it, it would have to be pretty high quality (to compensate for how much new code it would be adding). if this is a problem you have often, the reset --soft approach is probably the most practical way forward. (you might also have an easier time writing a script around interactive rebases than you would trying to implement this feature directly in git absorb.)
Thank you for sharing this detailed reply! Your explanation makes it clear why you (or anyone) would be reluctant to add this complexity to git-absorb
.
On reflection I realized I didn't share my use case for why I would want such a feature: Sometimes I find myself making several changes and realizing afterwards that I should commit them separately. I then select some of those changed files, commit those, and repeat, ending up with a stack of tiny commits at the end that need to be !fixup
-ed into various commits.
I wrote this feature request because I didn't see flags on git-absorb
that would allow one to ask git-absorb
something like: what is the earliest commit before commit X where commit X does not commute?
I wish I could be the person to put up a PR but my desire to learn Rust keeps getting overshadowed by real-world necessities.
If you'd like I will close this ticket as to not clutter up your issue list.
I wrote this feature request because I didn't see flags on
git-absorb
that would allow one to askgit-absorb
something like: what is the earliest commit before commit X where commit X does not commute?
oh that's interesting... i think i'm open to that. it would be a pretty modest change ("find the first commit that any hunk in this commit does not commute with"), and it seems useful to have some plumbing flags that let you pull the algorithm out for scripting. let's leave this issue open
@arkban commented on May 31, 2021 1:44 AM:
On reflection I realized I didn't share my use case for why I would want such a feature: Sometimes I find myself making several changes and realizing afterwards that I should commit them separately. I then select some of those changed files, commit those, and repeat, ending up with a stack of tiny commits at the end that need to be
!fixup
-ed into various commits.I wrote this feature request because I didn't see flags on
git-absorb
that would allow one to askgit-absorb
something like: what is the earliest commit before commit X where commit X does not commute?
Don't you mean the latest commit? If the latest commit before X is called Y, then X will not commute with any ancestors of Y either.
In case this feature does not appear in git-absorb soon, I hope @tummychow will not mind me mentioning git-deps, a project I wrote which takes a very different approach to roughly the same problem (it uses git blame instead of testing commutativity, as mentioned in #8). It has a very simple example script called git-fixup
which does something similar to what you want, except it first commits uncommitted changes, finds which commit those changes depend on, and then changes HEAD
into the correct fixup
. It should be extremely easy to modify this script to do what you want.
Having said that, my intention of mentioning it here is not to "steal" users from git-absorb, which is an excellent project, and certainly has advantages over the blame-based heuristic which git-deps uses. I think both tools have benefits in different situations, and it's always good to have a choice :-)
Don't you mean the latest commit? If the latest commit before X is called Y, then X will not commute with any ancestors of Y either.
Good catch, you are correct, I meant "latest"; I wrote some of this response while being peppered with questions about Sailor Moon lore by my daughter who was supposed to be asleep ;)
I realized I also wanted this feature because sometimes I type git-absorb
when I really wanted to type git-absorb --and-rebase
in the first place.
I realized I also wanted this feature because sometimes I type
git-absorb
when I really wanted to typegit-absorb --and-rebase
in the first place.
In this kind of scenario git autosquash will solve your issue
You can do this with git rebase -i
. When you get the list of commits, add the following line after the ones you want to absorb, replacing [branchname] with the name of the branch you are rebasing onto:
x git reset HEAD~1 --soft && git absorb --force --base [branchname]
You will then get your commit replaced with a bunch of fixup commits in the history, you can then git rebase --autosquash
them as normal.
Yeah, after doing this a lot I wrote myself a ~/.gitconfig
alias called fixup
that does very much that (plus some other things that I wanted):
[alias]
# better `fixup` based on 'git-absorb' https://github.com/tummychow/git-absorb/
# Changes made by this mini script:
# - Stages all changes (because I always forget to do that)
# - sets the `--base` to the HEAD of the followed branch (so it does not fix older commits)
# - automatically invokes interactive rebase
# - Keeps empty commits to ensure a smooth squash (at least tries too...)
# - Rebases away any empty commits left by the autosquash
# See:
# - https://stackoverflow.com/a/50122618/11889
fixup = "!f() { \
trap 'echo >&2 ERROR: Operation failed; return' ERR; \
command -v git-absorb >/dev/null 2>&1 || { echo >&2 'git-absorb needs to be installed!'; exit 1; }; \
UPSTREAM=$( git branch $( git branch --show-current ) --list --format '%(upstream)' ); \
git add -A; \
git-absorb --verbose --force --base $UPSTREAM; \
git rebase --interactive --autosquash --allow-empty --keep-empty $UPSTREAM; \
git rebase --empty=drop --no-keep-empty --no-allow-empty; \
}; f"
I'm posting it here in case someone else wants to crib from this, and I think at this point the feature "exists" and this issue can be considered resolved.
It would be nice to allow "absorbing" existing commits, i.e. I already committed a minor change, I'd like to have
git-absorb
identify which previous commit it should be absorbed into.Example, assume I have already committed my small fixes into the last commit:
I'd like to do something like:
And then have the log look like:
So I can then use
--autosquash
:Might something like that be possible?