jj icon indicating copy to clipboard operation
jj copied to clipboard

FR: add a revset function for the default branch of a remote

Open jyn514 opened this issue 1 year ago • 7 comments

Is your feature request related to a problem? Please describe. i have a git alias which shows me commits that are only on my branch, not yet merged to master:

[alias]
branch-log = !git log $(git merge-base HEAD origin/HEAD)..

i can almost replicate this with jj: jj log -r 'main@origin..@' only shows the commits between origin/main and HEAD. but it doesn't work if the branch on the remote is called something else. i can specify the common cases:

jj log -r '(remote_branches(main, origin) | remote_branches(master, origin))..@'

and in practice that will almost always work. but it would be nice to make that reliable.

Describe the solution you'd like add a new function to https://github.com/martinvonz/jj/blob/main/docs/revsets.md#functions that returns the default branch of the remote - perhaps remote_branches(@, origin), by analogy with origin/HEAD? that doesn't conflict with a named branch, and it means that @origin is a cute shortcut for it.

Describe alternatives you've considered

  • add a standalone command which prints the revlist a --revisions flag resolved to. that seems useful regardless; but without an equivalent of origin/HEAD it doesn't actually solve my problem.
  • do bash shenanigans to detect if this is backed by a git repo and run git rev-parse --symbolic-full-name origin/HEAD; this doesn't work because jj aliases (AFAICT?) don't support running arbitrary code, only jj subcommands.

Additional context jj 0.13.0-81b0e3bf3bf6f230740e93532b9a9767e8a4ef6e

jyn514 avatar Feb 04 '24 17:02 jyn514

Note that there is trunk() which refers to the HEAD of the default branch of the remote named origin. You can also override trunk() yourself to point to foobar@remote. So that's a convenient one; for instance, as an alternative to your branch-log I tend to use the following revset I call "open()" (that I plug all the time, sorry!), in order to look at all my open patch series: (mine() ~ ::trunk()) ~ heads(empty()); trunk() has been very convenient for when some repos use master and others use main...

Maybe trunk() should just be capable of taking an argument which is the remote to get the default HEAD/branch of? I'm not sure if we should overload it.

thoughtpolice avatar Feb 04 '24 17:02 thoughtpolice

i didn't know about trunk! that sounds like roughly what i want, but the exact behavior is not what i expect:

trunk(): Resolves to the head commit for the trunk branch of the remote named origin or upstream. The branches main, master, and trunk are tried. If more than one potential trunk commit exists, the newest one is chosen. If none of the branches exist, the revset evaluates to root().

i would expect it to use origin/HEAD if using a git backend, or fall back to the current behavior if the backend doesn't support getting the default branch for some reason. "guessing and checking" makes me a little uncomfortable; i've run into repos in the past that have main that is not the default branch.

jyn514 avatar Feb 04 '24 18:02 jyn514

I'd like to second this, I originally thought that trunk() was implemented by using origin/HEAD, instead of being part of a manual list of remotes/branches to check.

I think a new revset function would be really helpful, and I can help to work on implementing this if we decide on the correct syntax (eg. remote_default_branch([remote]) or maybe someway to extend remote_branches())? Or just overriding trunk() as suggested above might do the trick as well.

bnjmnt4n avatar Mar 04 '24 09:03 bnjmnt4n

jj's internal view doesn't know about <remote>/HEAD, so we'll need to think that about first. It's a bit special because <remote>/HEAD is a symbolic ref whereas jj's refs (including local HEAD@git) are all peeled.

It might be easier to set up trunk() to point to the remote head branch on jj git clone/jj git init.

yuja avatar Mar 04 '24 10:03 yuja

Ah, I see what you mean—looking through the code briefly, it seems like jj doesn't explicitly handle symbolic refs, it just depends on all refs being resolved to a commit ID (correct me if I'm wrong).

Will that be part of the scope for jj?

Alternatively I can do what you suggest: just configure a repository specific alias for trunk() to set the default branch for the remote on jj git clone/jj git init. (This doesn't handle adding of new remotes, but I'm thinking updating the configuration on each jj git fetch will not be ideal since it will override user-specific customizations, if any).

bnjmnt4n avatar Mar 04 '24 11:03 bnjmnt4n

I think there are two main questions to consider:

  • Should HEAD@remote be created as remote branches?
    • If yes, then trunk() can be set automatically to remote_branches(exact:"HEAD") for example.
    • Otherwise, perhaps the custom revset-alias would be the best solution.
  • Does jj want to support Git symbolic refs?
    • As mentioned this might be more complicated to handle. Since symbolic refs aren't commonly used apart from .git/HEAD and .git/refs/remotes/[remote]/head, we could just treat HEAD@remote as a typical branch and depend on the underlying Git tools (gix/git2) to resolve HEAD@remote to a peeled ref upon every fetch.

bnjmnt4n avatar Mar 04 '24 12:03 bnjmnt4n

jj doesn't explicitly handle symbolic refs, it just depends on all refs being resolved to a commit ID

Correct.

jj doesn't have a current/active branch (which is represented as a symbolic HEAD in Git), too. There's lengthy discussion related to that in #2338. If the discussion concluded we would need a distinction between symbolic and peeled HEAD, it might make sense to apply a similar data model to remote HEADs. I have no idea right now.

just configure a repository specific alias for trunk() to set the default branch for the remote on jj git clone/jj git init. (This doesn't handle adding of new remotes, but I'm thinking updating the configuration on each jj git fetch will not be ideal since it will override user-specific customizations, if any).

Agreed. It should be one-time action.

I think there are two main questions to consider:

* Should `HEAD@remote` be created as remote branches?

  * If yes, then `trunk()` can be set automatically to `remote_branches(exact:"HEAD")` for example.
  * Otherwise, perhaps the custom revset-alias would be the best solution.

Maybe no (at least in the underlying data model)? <remote>/HEAD isn't an independent branch, but is a state of the branch referred to by the <remote>/HEAD, I think. If <remote>/HEAD appeared as a usual branch, user could set up tracking local branch in jj.

* Does jj want to support Git symbolic refs?

I don't think it will become a general concept. HEADs might be special cased, but I honestly don't know.

yuja avatar Mar 04 '24 12:03 yuja

I started working on #3205, which updates the repo's trunk() alias on initial clone to the remote's default branch, but coming back to it I do think it might be valuable to have a revset function for the remote's default branch. Adding an alias feels like a sub-optimal workaround since users can add new remotes and want to find out what the latest revision on that remote's default branch is as well.

bnjmnt4n avatar Mar 16 '24 10:03 bnjmnt4n

I do think it might be valuable to have a revset function for the remote's default branch.

Perhaps, the revset syntax can be HEAD@<remote> just like HEAD@git. The underlying data model will need some adjustment. Maybe git_head will be moved to RemoteView (where remote_views["git"].head is HEAD@git)? I'm not sure whether the git_head should be a symbolic ref.

https://github.com/martinvonz/jj/blob/8600750fceafbf489d42a99b36b1f48bbc1e416b/lib/src/op_store.rs#L266-L285 https://github.com/martinvonz/jj/blob/8600750fceafbf489d42a99b36b1f48bbc1e416b/docs/design/tracking-branches.md#proposed-data-model

yuja avatar Mar 16 '24 14:03 yuja

Circling back to this after doing some more research, it seems like refs/remotes/origin/HEAD is actually a special symbolic ref which is only added when running git clone. Adding new remotes via git remote add X and git fetch X doesn't automatically populate refs/remotes/X/HEAD. In light of discovering this, I think going back to the original suggestion of just setting repo-level trunk() on clone would be the best option, since this is also effectively what git clone does when populating ref/remotes/origin/HEAD. We can definitely come back to revisit this in the future if this has greater demand, or eg. we start supporting symbolic refs.

(Sorry for the wishy-washiness, I will re-open my above PR once it's completed.)

bnjmnt4n avatar Mar 17 '24 18:03 bnjmnt4n