GNU bug report logs - #80006
31.0.50; Automatically choosing the VC outgoing base

Previous Next

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


Report forwarded to 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.

Acknowledgement sent to Sean Whitton <spwhitton <at> spwhitton.name>:
New bug report received and forwarded. Copy sent to 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




Information forwarded to 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.




Information forwarded to 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




This bug report was last modified today.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.