1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-05-10 15:25:04 -07:00

New VC commands for remote unintegrated changes

* lisp/vc/vc.el (vc--outgoing-base, vc--outgoing-base-mergebase):
New FORCE-TOPIC parameter.
(vc--maybe-read-outgoing-base): New NO-DOUBLE parameter.
(vc-root-diff-remote-unintegrated, vc-diff-remote-unintegrated)
(vc-log-remote-unintegrated, vc-root-log-remote-unintegrated):
New commands (bug#80434).
* lisp/vc/vc-dir.el (vc-dir-mode-map):
* lisp/vc/vc-hooks.el (vc-prefix-map): Bind them.
* doc/emacs/vc1-xtra.texi (Unintegrated Changes):
* etc/NEWS: Document them.
This commit is contained in:
Sean Whitton 2026-02-18 11:37:51 +00:00
parent ae40c3a438
commit 7d9dad4241
5 changed files with 181 additions and 13 deletions

View file

@ -296,6 +296,7 @@ yet merged into the target branch.
@node Unintegrated Changes
@subsubsection Commands to see all unintegrated changes
@cindex unintegrated changes
@cindex remote unintegrated changes
@table @kbd
@item C-x v T =
@ -314,6 +315,26 @@ of this branch and its upstream counterpart
@item C-x v T L
Display log messages for all changes since the merge base of this branch
and its upstream counterpart (@code{vc-root-log-unintegrated}).
@item C-x v T R =
Display diffs of remote changes to the VC fileset since the merge base
of this topic branch and its upstream counterpart
(@code{vc-diff-remote-unintegrated}).
@item C-x v T R D
Display a diff of all remote changes since the merge base of this topic
branch and its upstream counterpart
(@code{vc-root-diff-remote-unintegrated}).
@item C-x v T R l
Display log messages for remote changes to the VC fileset since the
merge base of this topic branch and its upstream counterpart
(@code{vc-log-remote-unintegrated}).
@item C-x v T R L
Display log messages for all remote changes since the merge base of this
topic branch and its upstream counterpart
(@code{vc-root-log-remote-unintegrated}).
@end table
For decentralized version control systems (@pxref{VCS Repositories}),
@ -388,7 +409,24 @@ changes to the current VC fileset. @kbd{C-x v T L} and @kbd{C-x v T l}
show the corresponding revision logs, excluding uncommitted changes as
above.
This functionality relies on Emacs correctly detecting whether the
@findex vc-diff-remote-unintegrated
@findex vc-root-diff-remote-unintegrated
@findex vc-log-remote-unintegrated
@findex vc-root-log-remote-unintegrated
On topic branches, it is sometimes also useful to see the same
information but for the remote repository's version of the topic branch.
This is the outstanding work on the topic branch that has been pushed,
but not yet merged. It is visible to your collaborators but is
otherwise still unintegrated. We call such work @dfn{remote
unintegrated changes}. There is a command corresponding to each of the
four commands discussed so far but which operates on the remote
repository's version of the current topic branch: @w{@kbd{C-x v R T =}}
(@code{vc-diff-remote-unintegrated}), @w{@kbd{C-x v R T D}}
(@code{vc-root-diff-remote-unintegrated}), @w{@kbd{C-x v R T l}}
(@code{vc-log-remote-unintegrated}), and @w{@kbd{C-x v R T L}}
(@code{vc-root-log-remote-unintegrated}).
All this functionality relies on Emacs correctly detecting whether the
current branch is a trunk or a topic branch, and in the latter case,
correctly determining the branch to which the topic branch will
eventually be merged. If the autodetection doesn't produce the right

View file

@ -3008,6 +3008,12 @@ These are useful to view all outstanding (unmerged, unpushed) changes on
the current branch. They are also available as 'T =', 'T D', 'T l' and
'T L' in VC Directory buffers.
'C-x v T R =' ('vc-diff-remote-unintegrated'), 'C-x v T R D'
('vc-root-diff-remote-unintegrated'), 'C-x v T R l'
('vc-log-remote-unintegrated') and 'C-x v T R L'
('vc-root-log-remote-unintegrated') are corresponding commands which
report information about the remote versions of a topic branch.
+++
*** New commands to report combined diffs of all local changes.
'C-x v E =' ('vc-diff-outgoing-and-edited') and 'C-x v E D'

View file

@ -412,6 +412,10 @@ an \\+`up-to-date' or \\+`ignored' file."
(define-key map "TL" #'vc-root-log-unintegrated)
(define-key map "T=" #'vc-diff-unintegrated)
(define-key map "TD" #'vc-root-diff-unintegrated)
(define-key map "TRl" #'vc-log-remote-unintegrated)
(define-key map "TRL" #'vc-root-log-remote-unintegrated)
(define-key map "TR=" #'vc-diff-remote-unintegrated)
(define-key map "TRD" #'vc-root-diff-remote-unintegrated)
(define-key map "EL" #'vc-root-log-outgoing)
(define-key map "E=" #'vc-diff-outgoing-and-edited)
(define-key map "ED" #'vc-root-diff-outgoing-and-edited)

View file

@ -1040,6 +1040,10 @@ In the latter case, VC mode is deactivated for this buffer."
"T L" #'vc-root-log-unintegrated
"T =" #'vc-diff-unintegrated
"T D" #'vc-root-diff-unintegrated
"T R l" #'vc-log-remote-unintegrated
"T R L" #'vc-root-log-remote-unintegrated
"T R =" #'vc-diff-remote-unintegrated
"T R D" #'vc-root-diff-remote-unintegrated
;; There are no -log-outgoing-and-edited commands because by
;; definition these are the same as -log-outgoing.
;; Additionally bind the -log-outgoing commands under C-x v E l/L as

View file

@ -3357,27 +3357,30 @@ BACKEND is the VC backend."
;; branch has no name.
(vc-call-backend backend 'trunk-or-topic-p branch))))
(defun vc--outgoing-base (backend)
(defun vc--outgoing-base (backend force-topic)
"Return an outgoing base for the current branch under VC backend BACKEND.
The outgoing base is the upstream location for which unintegrated
changes on this branch are destined once they are integrated.
There are two stages to determining the outgoing base.
First we decide whether we think this is a shorter-lived or a
longer-lived (\"trunk\") branch by calling `vc-trunk-or-topic-p'.
If that function returns nil, assume this is a shorter-lived branch.
This is based on how it's commands primarily intended for working with
shorter-lived branches that call this function.
longer-lived (\"trunk\") branch. If FORCE-TOPIC is non-nil, assume this
is a shorter-lived branch. Otherwise call `vc-trunk-or-topic-p'.
If that function returns nil, also assume this is a shorter-lived
branch. This is based on how it's commands primarily intended for
working with shorter-lived branches that call this function.
Second, if we have determined that this is a trunk, return nil, meaning
that the outgoing base is the place to which `vc-push' would push.
Otherwise, we have determined that this is a shorter-lived branch, and
we return the value of calling BACKEND's `topic-outgoing-base' VC API
function."
;; For further discussion see bug#80006.
(and (memq (vc-trunk-or-topic-p nil backend) '(topic nil))
(and (or force-topic
(memq (vc-trunk-or-topic-p nil backend) '(topic nil)))
(vc-call-backend backend 'topic-outgoing-base)))
(defun vc--outgoing-base-mergebase (backend &optional upstream-location refresh)
(defun vc--outgoing-base-mergebase
(backend &optional upstream-location refresh force-topic)
"Return, under VC backend BACKEND, the merge base with UPSTREAM-LOCATION.
Normally UPSTREAM-LOCATION, if non-nil, is a string.
If UPSTREAM-LOCATION is nil, it means to call `vc--outgoing-base' and
@ -3387,12 +3390,15 @@ If UPSTREAM-LOCATION is the special value t, it means to use the place
to which `vc-push' would push as UPSTREAM-LOCATION, unconditionally.
(This is passed when the user invokes an outgoing base command with a
\\`C-u C-u' prefix argument; see `vc--maybe-read-outgoing-base'.)
REFRESH is passed on to `vc--incoming-revision'."
REFRESH is passed on to `vc--incoming-revision'.
FORCE-TOPIC is passed on to `vc--outgoing-base'."
(vc-call-backend backend 'mergebase
(vc--incoming-revision backend
(pcase upstream-location
('t nil)
('nil (vc--outgoing-base backend))
('nil
(vc--outgoing-base backend
force-topic))
(_ upstream-location))
refresh)))
@ -3552,6 +3558,115 @@ topic branch."
(vc--with-backend-in-rootdir "VC revision log"
(vc-log-unintegrated upstream-location `(,backend (,rootdir)))))
;;;###autoload
(defun vc-root-diff-remote-unintegrated (&optional upstream-location)
"Report diff of remote changes since merge base with UPSTREAM-LOCATION.
Remote changes are changes in the incoming revision (instead of the
working revision), and the merge base with UPSTREAM-LOCATION is the
common ancestor of the incoming revision and UPSTREAM-LOCATION.
This command only makes sense for decentralized VCS, because otherwise
there is no distinction between locally committed changes and upstream
changes.
When unspecified, UPSTREAM-LOCATION is the outgoing base when this
branch is considered as a topic branch (whether or not it actually is).
This command with unspecified UPSTREAM-LOCATION only makes sense on
topic branches. See `vc-trunk-or-topic-p'.
When called interactively with a prefix argument, prompt for
UPSTREAM-LOCATION, which should be a remote branch name."
(interactive (list (vc--maybe-read-outgoing-base nil 'no-double)))
(vc--with-backend-in-rootdir "VC root-diff"
(vc-diff-remote-unintegrated upstream-location
`(,backend (,rootdir)))))
;;;###autoload
(defun vc-diff-remote-unintegrated (&optional upstream-location fileset)
"Show remote fileset changes since merge base with UPSTREAM-LOCATION.
Remote changes are changes in the incoming revision (instead of the
working revision), and the merge base with UPSTREAM-LOCATION is the
common ancestor of the incoming revision and UPSTREAM-LOCATION.
This command only makes sense for decentralized VCS, because otherwise
there is no distinction between locally committed changes and remote
changes.
When unspecified, UPSTREAM-LOCATION is the outgoing base when this
branch is considered as a topic branch (whether or not it actually is).
This command with unspecified UPSTREAM-LOCATION only makes sense on
topic branches. See `vc-trunk-or-topic-p'.
When called interactively with a prefix argument, prompt for
UPSTREAM-LOCATION, which should be a remote branch name.
When called from Lisp, optional argument FILESET overrides the fileset."
(interactive (let ((fileset (vc-deduce-fileset t)))
(list (vc--maybe-read-outgoing-base (car fileset)
'no-double)
fileset)))
(let* ((fileset (or fileset (vc-deduce-fileset t)))
(backend (car fileset)))
(vc-diff-internal vc-allow-async-diff fileset
(vc--outgoing-base-mergebase backend
upstream-location
'refresh 'force-topic)
;; REFRESH nil here because we just refreshed.
(vc--incoming-revision backend)
(called-interactively-p 'interactive))))
;;;###autoload
(defun vc-log-remote-unintegrated (&optional upstream-location fileset)
"Show remote log for VC fileset since merge base with UPSTREAM-LOCATION.
Remote changes are changes in the incoming revision (instead of the
working revision), and the merge base with UPSTREAM-LOCATION is the
common ancestor of the incoming revision and UPSTREAM-LOCATION.
This command only makes sense for decentralized VCS, because otherwise
there is no distinction between locally committed changes and remote
changes.
When unspecified, UPSTREAM-LOCATION is the outgoing base when this
branch is considered as a topic branch (whether or not it actually is).
This command with unspecified UPSTREAM-LOCATION only makes sense on
topic branches. See `vc-trunk-or-topic-p'.
When called interactively with a prefix argument, prompt for
UPSTREAM-LOCATION, which should be a remote branch name.
When called from Lisp, optional argument FILESET overrides the fileset."
(interactive (let ((fileset (vc-deduce-fileset t)))
(list (vc--maybe-read-outgoing-base (car fileset))
fileset)))
(let* ((fileset (or fileset (vc-deduce-fileset t)))
(backend (car fileset)))
(vc-print-log-internal backend (cadr fileset)
(vc--incoming-revision backend nil 'refresh)
'is-start-revision
;; REFRESH nil here because we just refreshed.
(vc--outgoing-base-mergebase backend
upstream-location
nil 'force-topic))))
;;;###autoload
(defun vc-root-log-remote-unintegrated (&optional upstream-location)
"Show log of remote revisions since merge base with UPSTREAM-LOCATION.
Remote changes are changes in the incoming revision (instead of the
working revision), and the merge base with UPSTREAM-LOCATION is the
common ancestor of the incoming revision and UPSTREAM-LOCATION.
This command only makes sense for decentralized VCS, because otherwise
there is no distinction between locally committed changes and remote
changes.
When unspecified, UPSTREAM-LOCATION is the outgoing base when this
branch is considered as a topic branch (whether or not it actually is).
This command with unspecified UPSTREAM-LOCATION only makes sense on
topic branches. See `vc-trunk-or-topic-p'.
When called interactively with a prefix argument, prompt for
UPSTREAM-LOCATION, which should be a remote branch name."
(interactive (list (vc--maybe-read-outgoing-base nil 'no-double)))
(vc--with-backend-in-rootdir "VC revision log"
(vc-log-remote-unintegrated upstream-location
`(,backend (,rootdir)))))
(declare-function ediff-load-version-control "ediff" (&optional silent))
(declare-function ediff-vc-internal "ediff-vers"
(rev1 rev2 &optional startup-hooks))
@ -4481,14 +4596,15 @@ starting at that revision. Tags and remote references also work."
nil 'vc-remote-location-history)))
(and (not (string-empty-p res)) res))))
(defun vc--maybe-read-outgoing-base (&optional backend)
(defun vc--maybe-read-outgoing-base (&optional backend no-double)
"Return upstream location for interactive uses of outgoing base commands.
If there is no prefix argument, return nil.
If the current prefix argument is \\`C-u C-u', return t.
If the current prefix argument is \\`C-u C-u' and NO-DOUBLE is nil,
return t.
Otherwise prompt for an upstream location.
BACKEND is the VC backend."
(cond
((equal current-prefix-arg '(16)) t)
((and (not no-double) (equal current-prefix-arg '(16))) t)
(current-prefix-arg
(let* ((outgoing-base (vc-call-backend (or backend
(vc-deduce-backend))