#10268

FR: `jj hide` / `jj unhide`: take changes out of view without abandoning them

Openalycdaopened 1mo ago

Is your feature request related to a problem? Please describe.

I keep a wide fan-out of exploratory changes: ideas and imported tasks that I later curate into history. Most of them aren't ready to discard, but while I work on one line they make the graph hard to walk. I want to take whole chains out of view and bring them back later, unchanged.

What exists today doesn't quite cover it:

  • revsets.log and aliases filter jj log, and #4180 describes tagging descriptions with #jj-archive and excluding them. But the filter only applies where the tool reads that setting. An IDE extension I use has no revset setting at all, so it still shows everything. Tagging also rewrites the changes it hides.
  • jj abandon removes them from view. Hidden commits stay addressable by commit ID, but there's no inverse. Bringing a chain back means finding commit IDs in the op log, re-pointing bookmarks by hand, and rebasing if a parent was rewritten in the meantime. #7389 mapped Sapling's hide to abandon, but Sapling pairs it with unhide.

Describe the solution you'd like

A reversible hide, kept at the view level so every jj-lib client respects it, not just jj log:

  • jj hide <revset> takes the changes out of the visible set without rewriting them. Like abandon, it refuses when it would leave a visible child without its parent, but it never rebases anything.
  • jj unhide <revset> makes them visible again with the same change and commit IDs. If a parent was rewritten while they were hidden, the chain is rebased onto the parent's current version.
  • A revset for what is hidden this way, so jj log -r 'hidden_by_user()' (name open) can review it. #2623 asks for a related hidden().
  • Hidden changes are kept alive against jj util gc, which is the difference from abandoning them.

Describe alternatives you've considered

I prototyped this as a shell script over jj and a colocated git repo: https://github.com/alycda/jj/tree/jj-stash/contrib/jj-stash (the README covers the design and test results). It records each hidden chain's roots, heads and bookmarks under refs/jj-stash/*, which jj doesn't import but git never prunes, writes a readable summary, then abandons the chain. Restoring revives it through a temporary bookmark. Because jj writes a change-id header into every git commit, the restored commits are identical, not replayed.

Building it turned up cases a native version would need to decide:

  • A hidden lane that is one parent of a merge. The prototype detaches the lane from the merge, which stays visible, and reattaches it on restore.
  • Ordering between hidden sets. Hide a leaf, then the chain it sits on, and restoring the leaf first drags part of the other set back into view. The prototype tracks these dependencies and refuses the wrong order.
  • Ignore rules for anything written into the working copy. jj reads them from disk, so checking out an older commit can change what is ignored.

A native version wouldn't need git refs, so it would work on any backend.

Additional context

  • Related: #4180 (archiving changes), #3402 (topics), #2623 (hidden() revset), #7389 (Sapling aliases), #4544 (confusing behaviour around hidden commits).
  • Prior art: Sapling's sl hide / sl unhide with smartlog, and git-branchless's git hide / git unhide.

Activity

1
josephlou51mo ago

IMO, #2623 does not feel related; that's asking for something else which happens to be called "hidden" as well. Since jj already has a "hidden revision" concept, which is distinct from what you're asking for here, I think we might need a different name. People would probably commonly refer to this as "archiving", so jj archive maybe? And then a revset function archived()?

I think this may also be something that could (maybe trivially) be done with per-revision metadata: #8166. It would probably be better if this was the kind of metadata that was attached to the revision separately, such that it doesn't change the hash. If you don't mind having the hash change, then you can do workarounds such as rebasing everything on top of a #jj-archive: ignore all descendants commit or something, but that isn't always ideal.

An IDE extension I use has no revset setting at all, so it still shows everything.

This sounds like the extension's responsibility though, not necessarily the project's. I don't think this request is a bad idea, but I don't think "an extension I use doesn't respect my settings" is a reason supporting this feature. It would be best to file a feature request against the extension to have some sort of customizable revset to show in their GUI. For example https://github.com/brychanrobot/jj-view reads directly from your revsets.log setting, and it exposes an environment variable when running, so you could use a conditional config to specifically target the extension's graph.