Skip to content

Add a "squash" action to jj arrange#9037

Open
martinvonz wants to merge 3 commits intomainfrom
mz/splwzttzvowt
Open

Add a "squash" action to jj arrange#9037
martinvonz wants to merge 3 commits intomainfrom
mz/splwzttzvowt

Conversation

@martinvonz
Copy link
Member

@martinvonz martinvonz commented Mar 6, 2026

Checklist

If applicable:

  • I have updated CHANGELOG.md
  • I have updated the documentation (README.md, docs/, demos/)
  • I have updated the config schema (cli/src/config-schema.json)
  • I have added/updated tests to cover my changes
  • I fully understand the code that I am submitting (what it does,
    how it works, how it's organized), including any code drafted by AI
  • For any prose generated by AI, I have proof-read and copy-edited with an
    eye towards deleting anything that is irrelevant, clarifying anything that
    is confusing, and adding details that are relevant. This includes, for
    example, commit descriptions, PR descriptions, and code comments.

@martinvonz martinvonz requested a review from a team as a code owner March 6, 2026 07:49
@josephlou5
Copy link
Contributor

Not to be the naming guy again, but would "squash" make more sense for this action? I have no idea what "fixup" means without reading a description, and "squash" already exists in jj concepts.

@martinvonz
Copy link
Member Author

Not to be the naming guy again, but would "squash" make more sense for this action?

Maybe. jj squash does not discard the description by default. This new "fixup" action is like jj squash --use-destination-message. I hope we'll soon have a version of this action that doesn't discard the source's description. If we call this one "squash", then what do we call the one that squashes and lets you edit the combined message?

@joyously
Copy link

joyously commented Mar 6, 2026

If we call this one "squash", then what do we call the one that squashes and lets you edit the combined message?

Name the action for the action it does, not the flags you give it.

@martinvonz
Copy link
Member Author

Name the action for the action it does, not the flags you give it.

I agree and I've tried. I thought "squash, discarding message" was too long. It should ideally not use a lot of horizontal space. Any suggestions?

@joyously
Copy link

joyously commented Mar 6, 2026

I thought "squash, discarding message" was too long. It should ideally not use a lot of horizontal space. Any suggestions?

Perhaps this is an indication to adjust the UI, by putting the "flags" as toggles, separate from the action name, instead of having separate actions. Or prompt for what to do with the description.

@josephlou5
Copy link
Contributor

I hope we'll soon have a version of this action that doesn't discard the source's description.

That makes sense. Are you imagining something similar to jj squash with no flags that brings up a description editor? Or would it either be "use destination message" or "use source message"?

If we call this one "squash", then what do we call the one that squashes and lets you edit the combined message?

Throwing this out there, but I'm not sure it's a good idea either: "squash changes only" vs. "squash" (which would resolve the description somehow)?

by putting the "flags" as toggles, separate from the action name, instead of having separate actions

This is a good point. I'm not sure if you had any thoughts on what this would look like in the future, i.e., if there would be a way to pass flags to equivalent jj commands, for instance. Or maybe that's too complicated.

@martinvonz
Copy link
Member Author

Are you imagining something similar to jj squash with no flags that brings up a description editor?

Yes.

(I don't have much opinion about the other questions yet.)

@nolith
Copy link

nolith commented Mar 7, 2026

As a non native speaker it took me some time to figure out that "fixup" is a git jargon and not an English word.

If I didn't knew git, I would probably express the fixup concept with one of those words: fold, or absorb.

I like fold the most, because absorb is already a jj concept and may cause confusion.

@aaronmallen
Copy link

Squish?

@joyously
Copy link

joyously commented Mar 7, 2026

As a non native speaker it took me some time to figure out that "fixup" is a git jargon and not an English word.

I had the opposite problem.... I am a native English speaker, but don't use Git much, so "fixup" is simply a vague non-word that tells me nothing.
Your idea of "fold" is good, but I still maintain that the flags should be modifiers of known actions instead of new actions. Adverbs, not verbs.

@barries
Copy link

barries commented Mar 7, 2026

Food for thought: assuming that the user is thinking first/primarily about the change they're squashing and the destination change is second/secondary in their mind, use option names that implicity refer to the src unless the dest is required.

jj squash ...                  # msg editor
jj squash ... --edit           # msg editor
jj squash ... --discard-msg    # src's msg is discarded, add `--edit` to revise.
jj squash ... --overwrite-msg  # src's msg replaces dest's, add `--edit` to revise.
jj squash ... --merge-msgs     # Use an external tool to merge msgs (thinking an LLM, but user defined), then msg editor. add `--no-edit` (spelling?) to _not_ edit & YOLO the result.

@PhilipMetzger
Copy link
Contributor

I currently really like the fold suggestion. If we had an option for squash for preserving the squashed commits message, we'd ideally mirror it here.

@martinvonz
Copy link
Member Author

IIUC, we have some people who prefer calling the current "squash and discard description" just "squash" for now and then deciding what to do with "squash and combine descriptions" later (maybe a different name, maybe some other indication in the UI). So I'll go ahead and just replace "fixup" by "squash" now so this hopefully gets unblocked.

@martinvonz
Copy link
Member Author

I have renamed it to "squash". This is now ready for review

Copy link
Contributor

@josephlou5 josephlou5 left a comment

Choose a reason for hiding this comment

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

Also update the PR title.

I'm about to add support for a "squash" action (name might change). It
will be allowed to mark a chain of commits as squashed, but it won'tx
be allowed to mark merge commits as fixups. It would be easy to make
pressing 'f' a no-op while on a merge commit, but then the user would
still mark it as a fixup and later swap the commit with a merge
commit. It gets complicated to check all interactions for each action,
so this patch instead applies the state update to a clone of the state
object, then validates the new state, and discards the new state if
it's invalid.

For now, the validation always passes.
This will reduce the risk of updating the old state instead of the new
state.
It's easier to support squashing while discarding the message then
asking the user for the combined message, so that's why I'm adding
that first.

For reference, `git rebase -i` calls this "fixup" while `hg histedit`
calls it "roll". We'll have to decide later how to handle squashing
while combining the message.
Copy link
Member Author

@martinvonz martinvonz left a comment

Choose a reason for hiding this comment

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

Thanks for reviewing

@josephlou5
Copy link
Contributor

LGTM, but also update the PR title.

@martinvonz martinvonz changed the title Add a "fixup" action to jj arrange Add a "squash" action to jj arrange Mar 9, 2026
@martinvonz
Copy link
Member Author

LGTM, but also update the PR title.

Done. Sorry, I misread your previous request a "Also updated the PR title.". I thought you had already updated it and I didn't even check :)

if let Some(new_tree) = new_tree {
let new_commit = rewriter.reparent().set_tree(new_tree).write().await?;
rewritten_commits.insert(id, new_commit);
} else if rewriter.parents_changed() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps, we need to set predecessors somewhere in this function.

Comment on lines +492 to +520
if new_parent_ids != rewrite.new_parents {
let store = mut_repo.store().clone();
let new_parents = try_join_all(
new_parent_ids
.iter()
.map(async |id| store.get_commit_async(id).await),
)
.await?;
let new_base_tree = merge_commit_trees_no_resolve_without_repo(
mut_repo.store(),
mut_repo.index(),
&new_parents,
)
.await?;
tree_terms.push((
new_base_tree,
format!(
"{} (new parents of squashed commits)",
conflict_label_for_commits(&new_parents)
),
));
tree_terms.push((
rewrite.old_commit.parent_tree(mut_repo).await?,
format!(
"{} (old parents of squashed commit)",
conflict_label_for_commits(&rewrite.old_commit.parents().await?)
),
));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add test that would fail without these rebasing terms? It would be nicer if we can extract/reuse jj-lib function.


fn is_valid(&self) -> bool {
for (id, commit_state) in &self.commits {
if self.external_children.contains(id) || self.external_parents.contains(id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since external children are included in the rewrite plan, I think we should also validate them.

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.

8 participants