jj
jj copied to clipboard
Add a `jj absorb` command
Description
As suggested by @unrelentingtech in #64, we should have a jj absorb command. There's git absorb, which was inspired by hg absorb. The two are implemented quite differently. Mercurial's is based on weaves, while Git's is based on "commuting" diff hunks. @durin42 tells me weaves are superior. I'd like to understand better what the difference is in practice (i.e. some concrete examples and how they behave with each method). Maybe @quark-zju (the original author of hg absorb) also has some thoughts.
I think the superior cases are covered by the tests. If you can port the tests then it's easier to see the differences. Basically weave should behave better when adjacent/context lines are changed.
I can confirm that. Here's a commit created by git absorb:
commit 15d9908f911a5eed7bd4b2a5880f52154eb3dc87 (HEAD -> master)
Author: Martin von Zweigbergk <[email protected]>
Date: Tue Mar 22 15:18:42 2022 -0700
fixup! fourth
diff --git a/file b/file
index 46876d0..48ffd66 100644
--- a/file
+++ b/file
@@ -1,7 +1,7 @@
-a
-b2
-c3
-d4
+a5
+b5
+c5
+d5
e
f
g3
The number on each line is the commit it was added in (e.g. "c3" was added in commit 3, replacing the previous "c*" line). The lines ending in "5" were added in the index. As you can see, all the fixup would move all the edits in the fourth commit instead of spreading the changes out over commits 1-4. It makes sense when you think about how it works, of course. Thanks for explaining.
While not automatic like hg/git absorb, I've found jj move flexible and easy to use, and even more so now that it accepts path arguments. For example, I had some changes in the working copy just now that should go into two different commits. So I ran this:
# Move most changes into the parent
$ jj squash CHANGELOG.md src/commands.rs tests/test_describe_command.rs
# Move remaining changes into the grandparent
$ jj move --to @--
I still think we should have a jj absorb, but I don't miss it as much as would miss hg absorb.
For anyone who sorely misses this feature, as a workaround you can install git-absorb and just run "git absorb", then continue using JJ for everything else. It doesn't play well when going branchless though unfortunately :(
Some Discord discussions:
Many people express their interest in absorb around https://discord.com/channels/968932220549103686/968932220549103689/1204156637930192927 and link to https://gregoryszorc.com/blog/2018/11/05/absorbing-commit-changes-in-mercurial-4.8/.
I had some vague ideas about absorb on Discord a while ago. Most of those thoughts are about absorbing conflicts or presenting absorbs to users, which may or may not work and can wait until some working version of absorb already exists.
I mentioned this on Discord before (TODO: check if it's the above links or more), or maybe in previous discussions, but not here.
I'm still a bit excited about using the conflict resolution machinery for this and/or (maybe) for blame (#2988).
Given
root -> B-> A-> X
we already have machinery that will tell you what becomes conflicted when you rebase X to B. So, I'm thinking that we could split the diff A->X into that part (it would not get absorbed) and the part that doesn't become conflicted (it would be absorbed into either B or an ancestor of A).
If X is already conflicted (or, more interestingly, if X is conflicted and A is not), we'd look at whether rebasing makes the conflict worse.
This might not work as well as weaves right now, but then there's a chance that we could improve absorb and conflict simplification at the same time.
I still need to think about whether this directly applies to having a blame layer.