Package: emacs;
Reported by: Sean Whitton <spwhitton <at> spwhitton.name>
Date: Sun, 14 Dec 2025 15:29:02 UTC
Severity: normal
Found in version 31.0.50
To reply to this bug, email your comments to 80006 AT debbugs.gnu.org.
Toggle the display of automated, internal messages from the tracker.
View this report as an mbox folder, status mbox, maintainer mbox
sbaugh <at> janestreet.com, dmitry <at> gutov.dev, juri <at> linkov.net, bug-gnu-emacs <at> gnu.org:bug#80006; Package emacs.
(Sun, 14 Dec 2025 15:29:02 GMT) Full text and rfc822 format available.Sean Whitton <spwhitton <at> spwhitton.name>:sbaugh <at> janestreet.com, dmitry <at> gutov.dev, juri <at> linkov.net, bug-gnu-emacs <at> gnu.org.
(Sun, 14 Dec 2025 15:29:02 GMT) Full text and rfc822 format available.Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
From: Sean Whitton <spwhitton <at> spwhitton.name> To: bug-gnu-emacs <at> gnu.org Subject: 31.0.50; Automatically choosing the VC outgoing base Date: Sun, 14 Dec 2025 15:28:19 +0000
X-debbugs-cc: sbaugh <at> janestreet.com, dmitry <at> gutov.dev, juri <at> linkov.net
I previously annotated the code for 'C-x v B =' and 'C-x v B D' with:
> For the following two commands, the default meaning for
> UPSTREAM-LOCATION may become dependent on whether we are on a
> shorter-lived or longer-lived ("trunk") branch. If we are on the
> trunk then it will always be the place `vc-push' would push to. If we
> are on a shorter-lived branch, it may instead become the remote trunk
> branch from which the shorter-lived branch was branched. That way you
> can use these commands to get a summary of all unmerged work
> outstanding on the short-lived branch.
>
> The obstacle to doing this is that VC lacks any distinction between
> shorter-lived and trunk branches. But we all work with both of these,
> for almost any VCS workflow. E.g. modern workflows which eschew
> traditional feature branches still have a long-lived trunk plus
> shorter-lived local branches for merge requests or patch series.
I now think I have an adequate plan to address this.
BACKGROUND
==========
For the 'C-x v B' commands to be convenient, they need to automatically
choose an outgoing base, and the choice needs to be correct often enough
that people feel it's worth their time invoking the commands.
Primarily that means: if I'm on a feature branch, the 'C-x v B' commands
need to correctly guess what branch my work on this branch will
eventually be merged back into.
Secondarily, it means that if I'm on a trunk branch, it needs to be easy
to use 'C-x v B =' and 'C-x v B D' as versions of 'C-x v O =' and
'C-x v O D' that include uncommitted changes.
If we know for sure whether a branch is a trunk or a feature branch,
then it relatively easy to guess the correct outgoing base. For an
older VCS where there is one trunk and it's always called 'trunk', this
is easy. But for newer VCS, trunk branches are not privileged in this
way. It just depends on how they are used, which they are.
So, we have to make our guess without knowing for sure which kind of
branch this is. And, to be clear, it's only worth making a guess if it
is a good guess. Otherwise people won't bother using the 'C-x v B'
commands much. This is the unresolved problem.
STRATEGY
========
I propose to generally assume that we're on a feature branch when the
user invokes one of the 'C-x v B' commands, because these commands are
primarily useful on feature branches. So then instead of trying to
answer "is this a trunk or a feature branch?" we have to answer "*were*
this to be a feature branch, what would its outgoing base probably be?",
which is much easier.
To satisfy the use case of versions of the outgoing diff commands which
include uncommitted changes, I propose to use a double prefix argument.
So 'C-u C-u' requests inverting the general assumption: 'C-u C-u' means
to assume that we're on a trunk branch.
In addition, we can have some mechanisms that are able to say that a
branch is definitely a trunk branch or definitely a feature branch.
These would override the general assumption. The idea is that these
mechanisms would return either "def a trunk", "def a feature", "don't
know for sure". So either they know for sure and we follow that, or we
fall back to our general assumption and the 'C-u C-u' override.
MAKING THE CHOICE
=================
Here is a summary of the algorithm for choosing the UPSTREAM-LOCATION I
propose:
1. With a single C-u, we prompt for the upstream, as we do at present
for all these commands.
2. With a C-u C-u prefix argument, assume it's a trunk branch, and skip
to step (6) below.
3. Check the branch name against two defcustoms, which each specify a
list of regular expressions matching branches that are definitely
trunks and that are definitely feature branches.
If an entry in one of the defcustoms does not contain any regular
expression metacharacters, then it's a literal name of a branch
anchored at both ends.
The default value for the list of regular expressions matching
branches that are assumed to be trunks would be '("master" "main"
"trunk" "default") and the default value for the defcustom matching
branches that are assumed to be feature branches would be nil.
Although the usual names of trunks is VCS-specific, I propose making
these global defcustoms, for simplicity. I don't think there's much
benefit to only looking for "master" and "main" if it's a Git
repository, for example.
For repositories with a branch naming scheme, these defcustoms can be
set as directory-local variables. For example, for emacs.git we can
add "\\`emacs-[0-9]+\\'" to the defcustom for trunk branches and
"\\`\\(?:feature\\|scratch\\)/" to the defcustom for feature branches
(and some others possibly).
I think we would want some way to negate the whole value,
e.g. pushing `not' to the front of the list. I don't think we need
full and/or/not handling. Some way to specify "the negation of the
other defcustom" would be good too, so that you could specify "any
branch not a trunk branch, according to the other defcustom, is a
feature branch". (Though I do not think that should be the default.
The default should be that we don't know which type it is.)
If the branch name matches both defcustoms, I'd be in favour of
signalling an error, for now, to avoid locking in one priority order.
If we move on to the next step that means the defcustom mechanism is
saying "don't know for sure".
4. Ask the backend whether it thinks the branch is a trunk or feature
branch. This would be an optional backend function returning
`trunk', `feature' or nil (for "not sure"). For CVS I believe we can
return `trunk' if it's the branch named `trunk', and `feature'
otherwise. For Git, we can return `feature' for a branch that has an
upstream that has a different name and a pushRemote of the same name,
or some similar heuristic. A third party backend could hook in here,
too.
5. If (3) and (4) both yield "don't know", assume it's a feature branch,
because the user has invoked one of the 'C-x v B' commands, which
usually imply wanting to work with a feature branch.
6. We now have a determination of whether this is a trunk or feature
branch. We now need an appropriate incoming revision. If it's a
trunk branch, we can just proceed as all the existing commands
already do: use a nil UPSTREAM-LOCATION to get the incoming revision
for the place to which vc-push would push.
If it's a feature branch, we call into the backend again, using a
different API call, to request an incoming revision for this branch
considered as a feature branch. Generally this will be the incoming
revision for the branch from which this feature branch was branched
and to which it will later be merged back into. I think searching
backwards for a branch point is the general way to do this. An
alternative might be to look at what has been merged into us. We
might require the other branch to be one that exists as a local
branch, if the VCS distinguishes between local and remote branches.
Finally we obtain the outgoing base by finding the merge base of the
incoming revision we just obtained and the working revision.
ALTERNATIVE C-u C-u
===================
Instead of C-u C-u meaning to assume a trunk branch, it could mean to
invert the automatic determination. So, when we get to (5) above, if
the user had typed C-u C-u, we then invert our determination.
I think this introduces significant cognitive overhead and is not useful
enough. The general assumption is that we are on a feature branch, not
the other way around, so what's more important is being able to invert
that general assumption, not invert the whole algorithm.
NEW COMMANDS
============
The above will enhance the existing 'C-x v B =' and 'C-x v B D'.
I intend to fill out the command set by adding 'C-x v B l'
and 'C-x v B L', which will work as you would expect.
A more exciting command is 'C-x v B +'. Where 'C-x v +' always pulls
from the same place to which vc-push pushes, 'C-x v B +' means to pull
from the true upstream of a feature branch, i.e., the branch from which
this feature branch was originally started.
So for example if you were on feature/igc, 'C-x v B +' would fetch and
merge origin/master into feature/igc.
If the VCS is configured to do rebases instead of merges, 'C-x v B +'
would mean to fetch the trunk and rebase your feature branch onto it.
This would be a common operation in a typical PR/MR workflow with modern
web forges. I'll discuss this example in the docs.
(To make this work with Git may or may not require explicitly
configuring a pushRemote; I'll have to see about that.)
--
Sean Whitton
bug-gnu-emacs <at> gnu.org:bug#80006; Package emacs.
(Sun, 14 Dec 2025 23:07:01 GMT) Full text and rfc822 format available.Message #8 received at 80006 <at> debbugs.gnu.org (full text, mbox):
From: Spencer Baugh <sbaugh <at> janestreet.com> To: Sean Whitton <spwhitton <at> spwhitton.name> Cc: 80006 <at> debbugs.gnu.org, juri <at> linkov.net, dmitry <at> gutov.dev Subject: Re: bug#80006: 31.0.50; Automatically choosing the VC outgoing base Date: Sun, 14 Dec 2025 18:06:35 -0500
Sean Whitton <spwhitton <at> spwhitton.name> writes:
> X-debbugs-cc: sbaugh <at> janestreet.com, dmitry <at> gutov.dev, juri <at> linkov.net
>
> I previously annotated the code for 'C-x v B =' and 'C-x v B D' with:
>
>> For the following two commands, the default meaning for
>> UPSTREAM-LOCATION may become dependent on whether we are on a
>> shorter-lived or longer-lived ("trunk") branch. If we are on the
>> trunk then it will always be the place `vc-push' would push to. If we
>> are on a shorter-lived branch, it may instead become the remote trunk
>> branch from which the shorter-lived branch was branched. That way you
>> can use these commands to get a summary of all unmerged work
>> outstanding on the short-lived branch.
>>
>> The obstacle to doing this is that VC lacks any distinction between
>> shorter-lived and trunk branches. But we all work with both of these,
>> for almost any VCS workflow. E.g. modern workflows which eschew
>> traditional feature branches still have a long-lived trunk plus
>> shorter-lived local branches for merge requests or patch series.
>
> I now think I have an adequate plan to address this.
>
> BACKGROUND
> ==========
>
> For the 'C-x v B' commands to be convenient, they need to automatically
> choose an outgoing base, and the choice needs to be correct often enough
> that people feel it's worth their time invoking the commands.
>
> Primarily that means: if I'm on a feature branch, the 'C-x v B' commands
> need to correctly guess what branch my work on this branch will
> eventually be merged back into.
>
> Secondarily, it means that if I'm on a trunk branch, it needs to be easy
> to use 'C-x v B =' and 'C-x v B D' as versions of 'C-x v O =' and
> 'C-x v O D' that include uncommitted changes.
>
> If we know for sure whether a branch is a trunk or a feature branch,
> then it relatively easy to guess the correct outgoing base. For an
> older VCS where there is one trunk and it's always called 'trunk', this
> is easy. But for newer VCS, trunk branches are not privileged in this
> way. It just depends on how they are used, which they are.
>
> So, we have to make our guess without knowing for sure which kind of
> branch this is. And, to be clear, it's only worth making a guess if it
> is a good guess. Otherwise people won't bother using the 'C-x v B'
> commands much. This is the unresolved problem.
>
> STRATEGY
> ========
>
> I propose to generally assume that we're on a feature branch when the
> user invokes one of the 'C-x v B' commands, because these commands are
> primarily useful on feature branches. So then instead of trying to
> answer "is this a trunk or a feature branch?" we have to answer "*were*
> this to be a feature branch, what would its outgoing base probably be?",
> which is much easier.
>
> To satisfy the use case of versions of the outgoing diff commands which
> include uncommitted changes, I propose to use a double prefix argument.
> So 'C-u C-u' requests inverting the general assumption: 'C-u C-u' means
> to assume that we're on a trunk branch.
>
> In addition, we can have some mechanisms that are able to say that a
> branch is definitely a trunk branch or definitely a feature branch.
> These would override the general assumption. The idea is that these
> mechanisms would return either "def a trunk", "def a feature", "don't
> know for sure". So either they know for sure and we follow that, or we
> fall back to our general assumption and the 'C-u C-u' override.
>
> MAKING THE CHOICE
> =================
>
> Here is a summary of the algorithm for choosing the UPSTREAM-LOCATION I
> propose:
>
> 1. With a single C-u, we prompt for the upstream, as we do at present
> for all these commands.
>
> 2. With a C-u C-u prefix argument, assume it's a trunk branch, and skip
> to step (6) below.
>
> 3. Check the branch name against two defcustoms, which each specify a
> list of regular expressions matching branches that are definitely
> trunks and that are definitely feature branches.
>
> If an entry in one of the defcustoms does not contain any regular
> expression metacharacters, then it's a literal name of a branch
> anchored at both ends.
>
> The default value for the list of regular expressions matching
> branches that are assumed to be trunks would be '("master" "main"
> "trunk" "default") and the default value for the defcustom matching
> branches that are assumed to be feature branches would be nil.
>
> Although the usual names of trunks is VCS-specific, I propose making
> these global defcustoms, for simplicity. I don't think there's much
> benefit to only looking for "master" and "main" if it's a Git
> repository, for example.
>
> For repositories with a branch naming scheme, these defcustoms can be
> set as directory-local variables. For example, for emacs.git we can
> add "\\`emacs-[0-9]+\\'" to the defcustom for trunk branches and
> "\\`\\(?:feature\\|scratch\\)/" to the defcustom for feature branches
> (and some others possibly).
>
> I think we would want some way to negate the whole value,
> e.g. pushing `not' to the front of the list. I don't think we need
> full and/or/not handling. Some way to specify "the negation of the
> other defcustom" would be good too, so that you could specify "any
> branch not a trunk branch, according to the other defcustom, is a
> feature branch". (Though I do not think that should be the default.
> The default should be that we don't know which type it is.)
>
> If the branch name matches both defcustoms, I'd be in favour of
> signalling an error, for now, to avoid locking in one priority order.
>
> If we move on to the next step that means the defcustom mechanism is
> saying "don't know for sure".
>
> 4. Ask the backend whether it thinks the branch is a trunk or feature
> branch. This would be an optional backend function returning
> `trunk', `feature' or nil (for "not sure"). For CVS I believe we can
> return `trunk' if it's the branch named `trunk', and `feature'
> otherwise. For Git, we can return `feature' for a branch that has an
> upstream that has a different name and a pushRemote of the same name,
> or some similar heuristic. A third party backend could hook in here,
> too.
>
> 5. If (3) and (4) both yield "don't know", assume it's a feature branch,
> because the user has invoked one of the 'C-x v B' commands, which
> usually imply wanting to work with a feature branch.
>
> 6. We now have a determination of whether this is a trunk or feature
> branch. We now need an appropriate incoming revision. If it's a
> trunk branch, we can just proceed as all the existing commands
> already do: use a nil UPSTREAM-LOCATION to get the incoming revision
> for the place to which vc-push would push.
>
> If it's a feature branch, we call into the backend again, using a
> different API call, to request an incoming revision for this branch
> considered as a feature branch. Generally this will be the incoming
> revision for the branch from which this feature branch was branched
> and to which it will later be merged back into. I think searching
> backwards for a branch point is the general way to do this. An
> alternative might be to look at what has been merged into us. We
> might require the other branch to be one that exists as a local
> branch, if the VCS distinguishes between local and remote branches.
Why not simply always call this backend function? Delegate the logic of
"what is the base revision?" entirely to the backend.
This would mean we wouldn't be able to make C-u C-u do anything useful,
but that seems fine.
Basically, with this kind of backend function where we just call the
backend to tell us what the base revision is, I don't see why we would
bother distinguish feature vs trunk in vc itself. Just always call the
backend function.
>
> Finally we obtain the outgoing base by finding the merge base of the
> incoming revision we just obtained and the working revision.
>
> ALTERNATIVE C-u C-u
> ===================
>
> Instead of C-u C-u meaning to assume a trunk branch, it could mean to
> invert the automatic determination. So, when we get to (5) above, if
> the user had typed C-u C-u, we then invert our determination.
>
> I think this introduces significant cognitive overhead and is not useful
> enough. The general assumption is that we are on a feature branch, not
> the other way around, so what's more important is being able to invert
> that general assumption, not invert the whole algorithm.
I agree, I don't think that's very useful.
> NEW COMMANDS
> ============
>
> The above will enhance the existing 'C-x v B =' and 'C-x v B D'.
> I intend to fill out the command set by adding 'C-x v B l'
> and 'C-x v B L', which will work as you would expect.
>
> A more exciting command is 'C-x v B +'. Where 'C-x v +' always pulls
> from the same place to which vc-push pushes, 'C-x v B +' means to pull
> from the true upstream of a feature branch, i.e., the branch from which
> this feature branch was originally started.
>
> So for example if you were on feature/igc, 'C-x v B +' would fetch and
> merge origin/master into feature/igc.
>
> If the VCS is configured to do rebases instead of merges, 'C-x v B +'
> would mean to fetch the trunk and rebase your feature branch onto it.
> This would be a common operation in a typical PR/MR workflow with modern
> web forges. I'll discuss this example in the docs.
> (To make this work with Git may or may not require explicitly
> configuring a pushRemote; I'll have to see about that.)
All seems reasonable.
bug-gnu-emacs <at> gnu.org:bug#80006; Package emacs.
(Tue, 16 Dec 2025 16:57:01 GMT) Full text and rfc822 format available.Message #11 received at 80006 <at> debbugs.gnu.org (full text, mbox):
From: Sean Whitton <spwhitton <at> spwhitton.name> To: Spencer Baugh <sbaugh <at> janestreet.com> Cc: dmitry <at> gutov.dev, 80006 <at> debbugs.gnu.org, juri <at> linkov.net Subject: Re: bug#80006: 31.0.50; Automatically choosing the VC outgoing base Date: Tue, 16 Dec 2025 16:56:08 +0000
Hello, On Sun 14 Dec 2025 at 06:06pm -05, Spencer Baugh wrote: > Why not simply always call this backend function? Delegate the logic of > "what is the base revision?" entirely to the backend. > > This would mean we wouldn't be able to make C-u C-u do anything useful, > but that seems fine. > > Basically, with this kind of backend function where we just call the > backend to tell us what the base revision is, I don't see why we would > bother distinguish feature vs trunk in vc itself. Just always call the > backend function. Thank you very much for reviewing my long planning message, here. I considered using a single backend function which just returns the appropriate incoming revision or the outgoing base. It's certainly nicely simpler. However, there would be a couple of things that we then couldn't support: - as you say, 'C-u C-u C-x v B D' to get an outgoing diff including uncommitted changes - the backend-generic mechanism to say what kind of branch something is by matching its name against regular expressions (we could still support that, but each backend would have to take care to call some function to do the matching, instead of it happening transparently to the backends) ISTM that both of these features are important. The first one was something that was requested back in bug#62940 and it seems useful. The second one lets someone configure their repository such that they can use the outgoing base commands on any branch and get a sensible answer. Otherwise, if you know your backend can't unambiguously identify trunk branches (which will be by far the most common case), you can't use the outgoing base commands whenever you're on a trunk branch, which might be inconvenient. Finally, making the distinction between trunk and feature branches visible to generic VC code, when we are designing and documenting features in terms of that distinction, makes more sense to me than leaving it entirely to the backend. It also means that we might be able to do other useful things with the distinction in the future. -- Sean Whitton
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.