Subject: bug#62940: 29.0.60; vc: no easy way to get diff of all outgoing changes

From: Dmitry Gutov <[email-masked]>
Date: Sat, 12 Oct 2024 01:38:38 +0300

On 11/10/2024 23:28, Dmitry Gutov wrote:
> 
> Anyway, it seems instead of vc-root-diff we can use vc-diff-mergebase, 
> it has the same completion for revisions. With REV1=@{upstream} and 
> REV2="", we'll get #1, with reverse - #2. The full key sequence:
> 
>    C-u C-x v M D @{upstream} RET C-j
> 
> Note that vc-*-mergebase is only implemented for Git. But vc-hg-log- 
> incoming is defined -- that seems to indicate that 'mergebase' would be 
> useful to support for it too.

Here's my current patch.

diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi
index 99219b7f5d7..55803de6274 100644
--- a/doc/emacs/maintaining.texi
+++ b/doc/emacs/maintaining.texi
@@ -881,13 +881,16 @@ Old Revisions
 
 @kindex C-u C-x v =
   To compare two arbitrary revisions of the current VC fileset, call
-@code{vc-diff} with a prefix argument: @kbd{C-u C-x v =}.  This
-prompts for two revision IDs (@pxref{VCS Concepts}), and displays a
-diff between those versions of the fileset.  This will not work
-reliably for multi-file VC filesets, if the version control system is
-file-based rather than changeset-based (e.g., CVS), since then
-revision IDs for different files would not be related in any
-meaningful way.
+@code{vc-diff} with a prefix argument: @kbd{C-u C-x v =}.  This prompts
+for two revision IDs (@pxref{VCS Concepts}), and displays a diff between
+those versions of the fileset.  The first one has several default
+values: the revision before the last one when the fileset changed, and
+the last revision of the current branch's upstream.
+The second revision ID defaults to nil, which means the contents of
+the work tree.  This will not work reliably for multi-file VC filesets,
+if the version control system is file-based rather than changeset-based
+(e.g., CVS), since then revision IDs for different files would not be
+related in any meaningful way.
 
   Instead of the revision ID, some version control systems let you
 specify revisions in other formats.  For instance, under Bazaar you
@@ -921,6 +924,10 @@ Old Revisions
 prompts for two revision IDs (@pxref{VCS Concepts}), and displays a
 diff between those versions of the entire version-controlled directory
 trees (RCS, SCCS, CVS, and SRC do not support this feature).
+The first one has several default values: the revision before the last
+one when the fileset changed, and the last revision of the current
+branch's upstream.  The second revision ID defaults to nil, which means
+the contents of the work tree.
 
 @vindex vc-diff-switches
   You can customize the @command{diff} options that @kbd{C-x v =} and
diff --git a/etc/NEWS b/etc/NEWS
index 2cd3b5893a1..4e8081f870a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -441,6 +441,7 @@ overlap.
 They suggest the previous revision as the default for REV1, not the last
 one as before.  This makes them different from 'vc-diff' and
 'vc-root-diff' when those are called without a prefix argument.
+This change also affects 'vc-diff-mergebase'.
 
 ** php-ts-mode
 
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index 5a7ffeffc9d..8727accf0ec 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -1522,6 +1522,12 @@ vc-git-print-log
                   (list "-p"))
 		'("--")))))))
 
+(defun vc-git-upstream-revision (_file &optional remote-location)
+  "Return the ref for REMOTE-LOCATION."
+  (if (and remote-location (not (string-empty-p remote-location)))
+      remote-location
+    "@{upstream}"))
+
 (defun vc-git-log-outgoing (buffer remote-location)
   (vc-setup-buffer buffer)
   (apply #'vc-git-command buffer 'async nil
@@ -1530,9 +1536,7 @@ vc-git-log-outgoing
            ,(format "--pretty=tformat:%s" (car vc-git-root-log-format))
            "--abbrev-commit"
            ,@(ensure-list vc-git-shortlog-switches)
-           ,(concat (if (string= remote-location "")
-	                "@{upstream}"
-	              remote-location)
+           ,(concat (vc-git-upstream-revision nil remote-location)
 	            "..HEAD"))))
 
 (defun vc-git-log-incoming (buffer remote-location)
diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el
index 876d86dc24f..ff09c260f60 100644
--- a/lisp/vc/vc-hg.el
+++ b/lisp/vc/vc-hg.el
@@ -1422,6 +1422,12 @@ vc-hg-log-incoming
   (vc-hg-command buffer 1 nil "incoming" "-n" (unless (string= remote-location "")
 						remote-location)))
 
+(defun vc-hg-upstream-revision (_file &optional remote-location)
+  "Return a revspec for the last commit not outgoing to REMOTE-LOCATION."
+  (unless remote-location
+    (setq remote-location ""))
+  (format "last(. and not outgoing(%s))" remote-location))
+
 (defun vc-hg-log-outgoing (buffer remote-location)
   (vc-setup-buffer buffer)
   (vc-hg-command buffer 1 nil "outgoing" "-n" (unless (string= remote-location "")
diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el
index a30ba06aec3..f716f652f6e 100644
--- a/lisp/vc/vc.el
+++ b/lisp/vc/vc.el
@@ -2061,6 +2061,11 @@ vc-read-multiple-revisions
 INITIAL-INPUT are passed on to `vc-read-revision' directly."
   (vc-read-revision prompt files backend default initial-input t))
 
+(defun vc-default-upstream-revision (_file &optional _remote-location)
+  "Return the last upstream revision of FILE.
+The default is to return nil always."
+  nil)
+
 (defun vc-diff-build-argument-list-internal (&optional fileset)
   "Build argument list for calling internal diff functions."
   (let* ((vc-fileset (or fileset (vc-deduce-fileset t))) ;FIXME: why t?  --Stef
@@ -2069,6 +2074,8 @@ vc-diff-build-argument-list-internal
          (first (car files))
          (rev1-default nil)
          ) ;; (rev2-default nil)
+    (when-let ((upstream-revision (vc-call-backend backend 'upstream-revision first)))
+      (push upstream-revision rev1-default))
     (cond
      ;; someday we may be able to do revision completion on non-singleton
      ;; filesets, but not yet.
@@ -2169,9 +2176,8 @@ vc-diff-mergebase
 The merge base is a common ancestor between REV1 and REV2 revisions."
   (interactive
    (vc-diff-build-argument-list-internal
-    (or (ignore-errors (vc-deduce-fileset t))
-        (let ((backend (or (vc-deduce-backend) (vc-responsible-backend default-directory))))
-          (list backend (list (vc-call-backend backend 'root default-directory)))))))
+    (let ((backend (or (vc-deduce-backend) (vc-responsible-backend default-directory))))
+      (list backend (list (vc-call-backend backend 'root default-directory))))))
   (when (and (not rev1) rev2)
     (error "Not a valid revision range"))
   (let ((backend (vc-deduce-backend))