Skip to content

Comments

Imitate rebase to get similar reflog#163

Closed
sh-at-cs wants to merge 3 commits intotummychow:masterfrom
sh-at-cs:fix-gh-157-imitate-rebase
Closed

Imitate rebase to get similar reflog#163
sh-at-cs wants to merge 3 commits intotummychow:masterfrom
sh-at-cs:fix-gh-157-imitate-rebase

Conversation

@sh-at-cs
Copy link
Contributor

@sh-at-cs sh-at-cs commented Mar 11, 2025

What

This implements the idea from #162 (comment), i.e. we try to make reflog entries created by git absorb look similar to those created by git rebase.

Why

The original intent of this was to make "undoing" an absorb invocation easier (#157). This change would fulfill that by ensuring the starting branch's reflog only gets a single entry resulting from the git absorb invocation, so that git reset $(git branch --show-current)@{1} would be enough to "undo" it.

But having reflog entries that are similarly clean as those of git rebase may also be a good idea beyond that (if nothing else, it certainly looks fancy).

How

The easiest way to get reflog messages similar to rebase is to imitate its behavior as well, so that's what we're doing here:

In a similar vein to rebase's initial checkout of the base commit, we start out by detaching HEAD (if it wasn't detached already). We then perform all our operations in a way that keeps updating HEAD (in our case: create fixup commits, then rebase if user chose --and-rebase). Finally, at the end, we point the branch we started from (if any) to our final commit and reattach HEAD to it.

At the points where we update references ourselves (namely: detach HEAD, point branch to final commit, reattach HEAD), we provide custom reflog messages that follow the format of git rebase's.

Result

After a successul git absorb --and-rebase, we have a single reflog entry for our original branch:

$ git reflog mybranch
e2f01e3 (HEAD -> mybranch) mybranch@{0}: absorb (finish): updating branch ref to final result
3849aee mybranch@{1}: [ previous reflog entry, doesn't matter ]

And the full log of operations bookended by absorb (...):-prefixed messages in HEAD's reflog:

$ git reflog
e2f01e3 (HEAD -> mybranch) HEAD@{0}: absorb (finish): returning to refs/heads/mybranch
e2f01e3 (HEAD -> mybranch) HEAD@{1}: rebase (fixup): Baz
a9c219e HEAD@{2}: rebase (pick): Baz
2ccd01c HEAD@{3}: rebase (pick): Bar
c648109 HEAD@{4}: rebase (fixup): Foo
daffe57 HEAD@{5}: rebase: fast-forward
b1fff8f HEAD@{6}: rebase (start): checkout b1fff8ff73c4f79e3e624750382f8af077597efd
a9dadfa HEAD@{7}: commit: fixup! Foo
c24e9ab HEAD@{8}: commit: fixup! Baz
3849aee HEAD@{9}: absorb (start): detaching HEAD
3849aee HEAD@{10}: [ previous reflog entry, doesn't matter ]

Comparison to git rebase

Just to expand on what was already said in #162 (comment) and make it easier to compare, after an ordinary git rebase (not in the context of git absorb), we have reflogs like:

Typical rebase reflogs
$ git reflog mybranch
[...]
0a7d4fd mybranch@{38}: rebase (finish): refs/heads/mybranch onto 1a11b604e4856bd32382baac11f6cdfb9ca366c4
fcdcbfe mybranch@{39}: [ previous reflog entry, doesn't matter ]

and

$ git reflog
[...]
0a7d4fd HEAD@{117}: rebase (finish): returning to refs/heads/mybranch
0a7d4fd HEAD@{118}: rebase (fixup): Baz
cb0bb22 HEAD@{119}: rebase (pick): Baz
ddf6ea9 HEAD@{120}: rebase (pick): Bar
70d3142 HEAD@{121}: rebase (fixup): Foo
daffe57 HEAD@{122}: rebase: fast-forward
1a11b60 HEAD@{123}: rebase (start): checkout 1a11b604e4856bd32382baac11f6cdfb9ca366c4
fcdcbfe HEAD@{124}: [ previous reflog entry, doesn't matter ]

Drawbacks & other approaches

Personally, I don't really like that if an error or crash happens in the middle, the user ends up in a detached HEAD state with no instructions about what to do now. If something like that happens during git rebase, Git will at least print some extra information about it in git status etc., but I don't think we can imitate that too (?).

I'm wondering if it wouldn't be better to keep the same logic as before (i.e. no HEAD detachment etc.) and only manipulate the reflog after the fact to remove all the extra entries from the branch's reflog 🤔 But that seems like a hack, too...

Misc

Fixes #157.

)?;
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO Add test(s)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and documentation? If

git reset $(git branch --show-current)@{1} would be enough to "undo" it.

(assuming we had an attached head to begin with, and under Unix; the syntax would be different on Windows) then a big advantage would be that simple instructions can be provided to the users in the README.md and/or git-absorb.adoc.

(and this still might be a candidate for an info log message; I'm not sure if that concept has completely been replaced)

// If we started out on a branch, update it to point to the result and re-attach HEAD:
if let Some(branch_name) = branch_name {
let final_commit = repo.head()?.peel_to_commit()?;
let branch_name = branch_name.to_string(); // TODO Probably a better way?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

@sh-at-cs sh-at-cs force-pushed the fix-gh-157-imitate-rebase branch from 14e6c3a to 536d381 Compare March 11, 2025 08:10
@blairconrad
Copy link
Contributor

That's a real nice PR description, @sh-at-cs!

@sh-at-cs sh-at-cs force-pushed the fix-gh-157-imitate-rebase branch from 009b6ad to e14fcd5 Compare March 11, 2025 18:33
Comment on lines +274 to +289
// To avoid creating entries in our starting branch's reflog when we create commits,
// we detach HEAD if it isn't already:
let mut initial_head_ref = repo.head()?;
let branch_name = if initial_head_ref.is_branch() {
initial_head_ref.name()
} else {
None
};
if branch_name != None && !config.dry_run {
repo.reference(
"HEAD",
head_commit.id(),
true,
"absorb (start): detaching HEAD",
)?;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There more I think about this, the more I dislike it:

Unlike rebase, which has to detach HEAD to work, there is absolutely no reason for us to do it other than the reflog.

But the alternative to get to the same result would be to manipulate the reflog, which would probably be even worse, because if something goes wrong, we've destroyed the user's ability to make sense of what happened.

@sh-at-cs
Copy link
Contributor Author

Closing because I no longer think this is a good idea: #163 (comment)

@sh-at-cs sh-at-cs closed this Mar 11, 2025
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

Successfully merging this pull request may close these issues.

Feature request: "Undo" subcommand / command-line flag

2 participants