The jj default revset filter, present(@) | ancestors(immutable_heads().., 2) | present(trunk()) had been mysterious for me for multiples hours.
And frankly, I don't like things to be mysterious, at least in computer
sciences (if you are a book, be misterious, that's great. If you are a date,
well, don't force it, that's annoying. If you are a computer science date, you
should not be mysterious at all. I see you, USA YY-DD-MM !)
First, this function is kinda magic because it only shows what I'm working on
present(@)andpresent(trunk())are obvious, they just mean that@ANDtrunk()will always appear in the log. See final note as to why they may NOT be part ofancestors(immutable_heads().., 2)and hence are included explicitly.- Now, what is the meaning of
ancestors(immutable_heads().., 2).ancestors(x, 2)was simple, if we have a set of revisionx, we get 2 parents, this adds a bit of context.
However, immutable_heads().. was confusing for me.
First, immutable_heads() is defined as present(trunk()) | tags() | untracked_remote_bookmarks().
It list a few derivations which are considered as immutable, including tags,
the trunk and untracked_remote_bookmarks. The logic about
untracked_remote_bookmarks is that if you do not track a remote "branch" or
"bookmark", well, you do not really care about them and do not want to change
them by mistake.
Most of my confusion comes from the properties of .. operator.
x.. means all the descendant of x which are not x.
edit: this is wrong actually. x..y means all the ancestors of y which are not ancestors of x. However x.. is a shortcut for x..visible_heads(), so that's all the ancestor of visible heads (I guess it is everything?) which are not ancestors of x.
So actually:
A-B-C
\-D
B.. is {C, D} and it includes D because D is a visible_head() and not an ancestor of B.
I've added this edit after the fact, so maybe the rest of the document is wrong / confusing with respect to this point.
edit: actually I discuss that later in the document, but a few days after, my brain had forgot this information. Either I'm stupid, or that's confusing (or both).
When applied on a unique revset, there is no confusion. For example:
A - B - C - D
\ \ - K
\- E - F - G
Here, A.. is actually {B, C, D, K, E, F, G}. Similarly, C.. is {D, K}.
However, what is happening when it is applied on multiples revisions.
My understanding was wrong. Actually, I thought that x.. would distribute on
all the revision in x. Said otherwise, I thought that, which is WRONG, (a | b).. = a.. | b...
Let's assume the following tree:
A* - B - C* - D
\ \ - K
\- E - F - G*
|- K* - L
We have a few commit which are considered as immutable_heads(), {A, C, G, K}. See the * besides their name.
Remember the semantic of .. with a unique commit:
G.. = {}K.. = {L}A.. = {B, C, D, K, E, F,G}C.. = {D, K}
Because I thought that .. was distributed on the sub revisions, ake, WRONG
(G | K | A | C).. = (G.. | K.. | A.. | C..), my understanding had been that
immutable_heads().. would be {D, B, C, K, L, E, F, G}, which is actually
the complete tree.
Then I saw the following commit:
https://github.com/jj-vcs/jj/commit/ea3a574e36ece4a3772765ef6fcc81094915a068
which adds untracked_remote_bookmarks() into immutable_heads(). And the changelog was including:
- The default
immutable_heads()set now includesuntracked_remote_branches()with the assumption that untracked branches aren't managed by you. Therefore, untracked branches are no longer displayed injj logby default.
And here I was super confused. How can adding MORE revision in
immutable_heads() would actually remove revision from jj log output?
What does the documentation for .. says?
x..: Revisions that are not ancestors ofx. Shorthand forx..visible_heads().
Note that this piece of documentation is not precise enough from my point of
view, because the revisions are NOT ancestors of x, but x is an ancestors
of these revisions. This is way more clear with the documentation of ..:
x..y: Ancestors ofythat are not also ancestors ofx. Equivalent to::y ~ ::x. This is whatgit logcallsx..y(i.e. the same as we call it).
This is still confusing for me. Imagine the following repo:
A - B - X - Z
\ - C - Y
X..Y would be the ancestors of Y, including Y but excluding X, hence {A, C, Y}. And A being an ancestor
of X, it is removed, So X..Y should actually gives {C, Y}, which is
completely confusing for me, and actually, that's how it works.
Revelation: And actually, that's great. Because imagine now that X is main@origin (or
whatever name you gave to trunk) and Y is another branch, well, if Y was
not rebased on X, still, [email protected] will give you all the commit until
the fork point, so e.g. everything you want to include in your rebase.
So the revisions included in x..y are not ancestors of x and are ancestors
of y (including itself). That's the only thing you need to know. The .. is
misleading by giving the idea of a path between both, but actually, there is no
path.
So picking the previous example:
A* - B - C* - D
\ \ - K
\- E - F - G*
|- K* - L
If we pick (A | C | G | K).., let's analyse a bit more. First, it is a
shorthand for (A | C | G | K)..visible_heads().
What are our visible_heads(), well, {D, K, G, L}
Now what are the ancestors of {D, K, G, L} which are NOT ancestors of (A | C | G | K). Well, it is {D, K, L}. For example, B is not included, because
that's an ancestor of C.
My initial confusion was that adding revset inside x in x.. would lead to
more childrens being evaluated. However the logic is in the reverse direction,
each revision added to x excludes itself and its ancestors from the listing.
So immutable_heads().. includes all the commit which are after what is
considered an immutable head (so tag, remote untracked revision, and trunk) at
the condition that they are not themself ancestors of one such immutable head.
So in summary, that's all your change, including bookmarked ones, which are NOT
considered as immutable. Now you may ask, why not in that case using mutable() and I actually don't know.
mutable() is defined as ~immutable() and immutable is ::(immutable_heads() | root()). If you remove the root(), that's technically ::immutable_heads().
So mutable() = ~(::immutable_heads()) which smells like immutable_heads()..
and I will admit that I'm just unsure about the precise semantic here, or what
does the additionnal root() change into the equation, this would be for a future, already too long, discussion.
@ and trunk() may not be part of your mutable changsets, however:
- it can be convenient to have
trunk()in your log, just to know how it is compared to your current commits. - it is not recommended, and
jjwill certainly complain, but hey, after all, do what you want, tojj editan immutable commit. (jjwill complain and request--ignore-immutable). However, if you do that, you'll be happy to have the log showing where@is.
I'm not sure what to write here. Let's ask an AI.
This was a great read