-
Notifications
You must be signed in to change notification settings - Fork 252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FR: Warn users of empty revsets when using multiple child/parent operators. (EDITED) #3821
Comments
Maybe you know, but just to be clear, an empty expression (such as Anyway, the easiest workaround is to let We can also add a revset function that asserts the size (e.g. Alternatively, we can make some expressions require non-empty set by default, and add an explicit opt-out. For example, |
I'm not advocating for a warning if a revset's empty, just a warning if it's invalid (for some agreed-on definition of invalid). To me, But if |
Alternatively, an "optimistic" child/parent operator might be nice, one that attempts to extend if possible, but simply stops expanding when it runs out of revs. That's what I really want when I type something like I could also reliably do something like |
I think it's hard to classify what is invalid in this case.
If you're willing to accept that an empty set has no descendants, then the empty set should also have no parents/ancestors either.
I think this is probably the easiest workaround. Perhaps the warning wouldn't be too annoying. |
Perhaps "invalid" is the wrong word to use here. In set theory terms you're correct, but I think the intents of how people use various operators matters, too. Maybe I'm the only one, but I suspect many newcomers have similar mental models. I'll update the title, if that helps. |
"runs out" state is a bit fuzzy to define. |
fwiw, maybe you want to do |
I'm not sure I see the issue. Can you give me an example?
I'm not seeing this. By definition, the dash right before it becomes empty will always be For the children operator, it's different, since branches will be of different lengths. But that could be useful, too. You could say "show me up to 20 revs or less for each branch from this point". (I admit I don't need that, but someone might.)
That would work for Maybe clamping warrants new operators, but I think they'd be useful. (They'd would break symmetry, but I don't think anyone who uses clamping variants will be surprised. E.g., with clamping, |
I think I'd be OK with considering The warning could say something like:
This is not crucial, if people are really opposed to case-by-case solutions, but I think it'd make Having a log print something if the resulting set is empty seems orthogonal. It'd still be surprising why it's empty. |
I was wondering why
If each parent is tracked individually and individual emptiness is special cased (or
If empty set is special cased,
Maybe it's the job of |
I didn't follow your conversation entirely, but degree >1 parents do not break the equivalence of You refer to something similar when you mention the possibility of From this perspective, the ideal model would be to have infinitely many root commits, all empty:
(but actually implementing that seems like overkill to me. Maybe we can do it for some April Fools day.) |
In range expression, clamped version might be useful. However, I would be surprised if Implementation-wise, adding a "checked" version of range operation (that rejects empty operands) is probably easier than checking if each sub-expression is empty to emit warning. |
Only in range expression, right?
Correct. It's a better concept to model clamped children/parents. |
I didn't quite understand what you mean, could you elaborate a bit?
The idea of a "checked range operation" is interesting. Just to make the parallel clear, what I suggested is essentially a "checked - operation" that checks if the operand is
I don't quite follow the "checking if each sub-expression is empty to emit warning" idea, since empty expressions seem to make a lot of sense in some cases. I think you don't like it either, but let me know if there is something important I missed. |
Here I mean
I just mean your explanation is better than "hidden loop edge".
We'll need to inspect sub expression |
There's an intuitive interpretation that doesn't involve special "clamping" behavior:
Whereas it currently behaves like this:
Observe that both
I believe it's some kind of homomorphism. The structure is not violated, which is good, but the operator is Whereas only
This is a monotonic property, indicating that not only was the structure "preserved", but no information was "lost". (There must be a duality between homomorphic and monotonic operations that I'm not aware of?) Monotonic operators are quite common in declarative systems like these. I believe
But I think the question is: do people think that present-day At the very least, the decrement operator on the integers is certainly monotonic 😉. I'm guessing users might be surprised because they expect this operation to be monotonic in a more overt fashion. It's also worth considering why
In my experience, many similar declarative systems operate in terms of fixed points and monotonicity, so "more" monotonic is generally better. ( |
FWIW, I can confirm that my mental model of |
I still think it's context dependent whether the "minus" or the current "-" behavior is preferred. It makes some sense that |
Regardless of what's ultimately decided on, I think the docs should clarify the exact behavior at the heads and root. |
This is an important point; it makes sense that implementing a warning is more difficult than a hard error or the current behavior. IMO, in the ideal world, a warning would strike the right balance, but it's probably not worth the implementation difficulty (we already have a prost-based DSL for parsing; this would be a noticeable change to it). Based on my previous comments, I'd be OK with I don't have a clear conclusion in mind about what, if anything, we should do at this moment. |
@arxanas 's minus operation is curious. I'll illustrate it, since it took me a bit to understand:
would have What @arxanas called For better or worse, there seems to be no way to refer to
I might be corrected, but TBH feel like it was a theoretically imperfect engineering decision. Things are easier to reason about if the working copy commit always has a parent. Then, commands I'd say that having |
I just added "can be empty" since it seems obvious when it becomes empty if it can be empty. AFAICT, the confusion in martinvonz#3821 is whether or not "no parents" falls back to root().
The reason for the root commit is so there's always a common ancestor. I copied the idea from mercurial (cut there it's called the null revision). It removes special cases. See e.g. |
In those cases, is the working-copy commit the root commit, or a new empty commit on top of it?
This is correct (and I actually typically mentally model I guess one potentially unexpected behavior is when
That's true, but there's also no way to refer to just |
I just added "can be empty" since it seems obvious when it becomes empty if it can be empty. AFAICT, the confusion in #3821 is whether or not "no parents" falls back to root().
Well, one invariant this breaks is that I should say, I do find In theory, I'd argue that |
I don't, but maybe I should? When do you tend to use them? It's quite easy to make aliases like |
It's worth pointing out for the first case that it's not just an invariant: the typical definition of the "ancestors" function is the fixed point of the "parents" function, so it's more so true by definition rather than being an incidentally-nice property.
I'll have to make a note when I do it next. Usually, I think it's because I want to reconnect non-adjacent components somehow. |
I agree, "fixed point" is confusing here. I don't think the operation of taking parents will ever have meaningful fixpoints, those would be loops in the graph.
On second thought, if you define Update: I should have said "
Interesting! |
I had to jog my memory on what exactly the theory behind "ancestors being the least fixed point of parents" was, and I regret to say that
Relations themselves can be ordered and can be fixed points. What I had meant was "the The There is also a notion of revsets being "fixed points" for revset functions, rather than revset functions themselves somehow inherently being fixed points without respect to any particular value. This is the intuitive notion discussed already in this thread, which works for many other domains too (like normal functions of numbers). But it's getting complicated, so I'll have to expand in different thread. |
In this sense, But I'm just guessing now. I'm not used to talking of things in terms of fixed points. |
Is your feature request related to a problem? Please describe.
I ran into a surprising issue when I tried to log more revisions than existed in a repo. I ran something like
jj log -r '@-------------------::@'
, and jj silently outputted nothing. Conceptually, I was thinking of this as "go back a dozen ancestors from @", and not "locate the dozenth ancestor of @, and give me its descendants down to @". I was expecting it to display "root():@" if I went too far.Regardless, a silent failure is not ideal.
Describe the solution you'd like
jj should issue a warning if the revset is invalid. Even better, if the revset can be properly adjusted to the nearest root/head, that would be great, though I haven't thought through all the cases to know if that's feasible.
Describe alternatives you've considered
I mean, "root()::@" worked, but I didn't do that initially, because I assumed the repo history would be too long.
The text was updated successfully, but these errors were encountered: