Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Allow absorbing existing commits #44

Closed
arkban opened this issue May 27, 2021 · 9 comments
Closed

[Feature] Allow absorbing existing commits #44

arkban opened this issue May 27, 2021 · 9 comments

Comments

@arkban
Copy link

arkban commented May 27, 2021

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:

$ git log --oneline
0000002 Oops, I need to fix "foo.txt"
0000001 Second let's add bar.txt
0000000 First let's add foo.txt

I'd like to do something like:

$ git absorb 0000002

And then have the log look like:

0000002 !fixup First let's add foo.txt
0000001 Second let's add bar.txt
0000000 First let's add foo.txt

So I can then use --autosquash:

# git rebase -i --autosquash
pick 0000001 Second let's add bar.txt
fixup 0000002 !fixup First let's add foo.txt
pick 0000000 First let's add foo.txt

Might something like that be possible?

@tummychow
Copy link
Owner

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.)

@arkban
Copy link
Author

arkban commented May 31, 2021

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.

@tummychow
Copy link
Owner

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?

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

@aspiers
Copy link

aspiers commented May 31, 2021

@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 ask git-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 :-)

@arkban
Copy link
Author

arkban commented Jun 1, 2021

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 ;)

@arkban
Copy link
Author

arkban commented Jun 2, 2021

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.

@joihn
Copy link

joihn commented Jan 23, 2023

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.

In this kind of scenario git autosquash will solve your issue

@rjmunro
Copy link

rjmunro commented May 16, 2023

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.

@arkban
Copy link
Author

arkban commented Jun 16, 2023

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.

@arkban arkban closed this as not planned Won't fix, can't repro, duplicate, stale Jun 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants