jj icon indicating copy to clipboard operation
jj copied to clipboard

FR: revset functions for traversing the obslog

Open eopb opened this issue 1 year ago • 8 comments

Problem

Revsets are very cool. They provide a way to select a set of changes, without having to run jj log and manually copy the change IDs. Unfortunately, they only provide functions for moving between the most recent commits that changes point to. To select a previous version of a change, the user must first open the obslog, and then copy the commit ID for the version they're after.

Solution

Introduce a set of functions for traversing the obslog. Much like parents(x) gives the parent change(s) of change x that can be found with jj log -r ..x, previously(x) would give the parent commit(s) of the change x that could be found with jj obslog -r x.

I'm proposing this set of functions where previously is the most useful and simple.

Obslog function suggestion Analogous changelog function Alias of
past(x) ancestors(x)
future(x) x..
previously(x) parents(x) oldest(past(x, 2))[^1]

Operators would also be really nice to have, especially in cases like previously(previously(x)) but I'm not sure what symbol would be best.

Use cases

Verbatim "ours" rebase/merge https://github.com/martinvonz/jj/issues/1027

I initially wanted this feature to carry out verbatim rebases. This is based on an idea from @ilyagr.

jj rebase -r a -d b
jj restore --to a --from 'previously(a)'

Unlike adding a --verbatim flag to rebase, this also allows restoring into a child of a so that the conflict resolution can be viewed before squashing into a.

Note that this is only equivalent to one type of git "ours" merge strategy. There are two subtly different types.

Restoring from a previous snapshot

Take this example

touch important # create an important file
jj debug snapshot # snapshot @
rm important # unintentionally remove an important file
jj restore --from 'previously(@)' important # bring the file back from the last snapshot

[^1]: oldest doesn't exist but should be too hard to add since we have latest

eopb avatar Jul 20 '24 20:07 eopb

I think I prefer previous()to previously(), but I suspect there’s lots of bikeshedding that could be done here.

There’s also the subtle difference between “this change ID at the previous op log version” and “this change ID at the previous obslog version” to consider.

emilazy avatar Jul 20 '24 20:07 emilazy

I think I prefer previous()to previously(), but I suspect there’s lots of bikeshedding that could be done here.

That's fair. I considered previous but went for previously because I felt it conveyed better that it only goes a single layer deep, that being said, my reasoning may not work for others.

There’s also the subtle difference between “this change ID at the previous op log version” and “this change ID at the previous obslog version” to consider.

This is also related to https://github.com/martinvonz/jj/issues/1283. Maybe we'll want to support both in the future. The obslog seems simpler and more useful to me, but both are interesting

eopb avatar Jul 20 '24 21:07 eopb

Mercurial calls obslog-based resolution as predecessors/allpredecessors/successors/allsuccessors (analogous to parents/ancestors/children/descendants respectively.) I'll be nice if we can find better names for all* variants.

yuja avatar Jul 21 '24 01:07 yuja

Copying from #4097, I think obsparents, obschildren, obsancestors, and obsdescendants are relatively clear. This also allows for obsroots and obsheads. We could also style them as obs_parents. Of course, these are not the prettiest names nor the shortest.

A related option would be to have parents(x, changes) for parents in the change-graph (AKA the normal graph, not sure if there is a better name) and parents(x, obs) for parents in the obsgraph. I'm not sure if this is a good idea as-is, but perhaps it'll spark other ideas.

ilyagr avatar Jul 21 '24 05:07 ilyagr

Maybe evolution is better as a prefix? I think obsolete implies the direction towards predecessors (or ancestors in rewrites log.)

A related option would be to have parents(x, changes) for parents in the change-graph (AKA the normal graph, not sure if there is a better name) and parents(x, obs) for parents in the obsgraph.

Or obs(parents(x)), obs(::x), etc. Too cryptic? It's probably similar to how "branch set" would be expressed if we had such thing.

yuja avatar Jul 21 '24 06:07 yuja

I would personally like something fairly short for the UX of the restore use case. In that sense obs is nice, but on the other hand the obslog name itself always stuck out to me as awkward and out of place.

emilazy avatar Jul 21 '24 07:07 emilazy

Maybe the new names should consider #3592 whenever we get around that.

Maybe evolution is better as a prefix? I think obsolete implies the direction towards predecessors (or ancestors in rewrites log.)

A related option would be to have parents(x, changes) for parents in the change-graph (AKA the normal graph, not sure if there is a better name) and parents(x, obs) for parents in the obsgraph.

I do like this idea though.

I would personally like something fairly short for the UX of the restore use case.

I don't think this should matter for the name bikeshed, as you always can add aliases.

PhilipMetzger avatar Jul 21 '24 12:07 PhilipMetzger

A related option would be to have parents(x, changes) for parents in the change-graph (AKA the normal graph, not sure if there is a better name) and parents(x, obs) for parents in the obsgraph.

Or obs(parents(x)), obs(::x), etc. Too cryptic?

It'll be nice if we can filter obslog by diffs from the predecessor.

jj obslog -r@- --obs '~empty()'

In revset, it might be expressed as obs(empty()), obs_empty(), or empty(obs).

yuja avatar Aug 06 '24 08:08 yuja

It would also be really nice to be able to filter based on time stamp, so I can find e.g. yesterday's version of this change.

anka-213 avatar May 16 '25 12:05 anka-213

Presumably filtering based on date would work the same as other commands, i.e. previous(x) & comitter_date(after:"yesterday")

bryceberger avatar Aug 04 '25 04:08 bryceberger

For the allpredecessors naming, we could implement the Kleene star operator.

  • previous*(x) - all previous incarnations of x
  • parents*(x) - equivalent to ancestors(x)
  • ancestors*(x) - redundant, still means ancestors(x)
  • description*(x) - an error (as is any other function that is not a relation between graph nodes.)

Pushing it further, I suppose parents*2(x) could be ancestors(x, 2), but that starts getting weird fast.

As an alternate syntax, it could be repeat(previous(x)) or even repeat(previous)(x) (higher order functions in your revsets, yo.) Then repeat(parents(x), 2) == ancestors(x, 2) would actually make sense. (Or repeat(parents, 2)(x) for the HOF version.)

And now I'm tempted to overcomplicate the overcomplicated: until(previous(x), author_date(after:"10 days ago")) gives all previous commits up to and including the first one matching the author_date condition...

Or now that all: is gone, repeat could even be all, as in all(previous(x)) or all(previous)(x) -- or all:previous(x). Which comes almost full circle, but I'll claim all:previous(x) reads better and is more general than allprevious(x). And it can be paired with last:previous(x) to get the first commit ever for the x change, or first:previous(x) if you just like being redundant. (j/k, first: would be an error.)

hotsphink avatar Sep 25 '25 01:09 hotsphink