Subject: bug#52349: 29.0.50; vc-git and diff-mode: stage hunks

From: Juri Linkov <[email-masked]>
Date: Tue, 08 Feb 2022 21:57:58 +0200

Hi Dmitry,

>> So if using ‘git apply --cached’ is the preferred solution,
>> then I could finish the patch with it.
>
> I don't have a particularly strong opinion, but the approach with 'git
> apply --cached' followed by 'git commit' seems easier to implement and
> avoids changing the file contents on disk, or risking any of the
> stashes. So I'd try implementing it first and then see if the remaining
> problems are worth the trouble of doing it in a more difficult way.

This is implemented now, and I'm already using it without problems.
Please review this patch:

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0bf7899246..84128d40b8 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -2860,6 +2860,17 @@ diff-syntax-fontify-props
     (nreverse props)))

+;;;###autoload
+(defun diff-vc-deduce-fileset ()
+  (let ((backend (vc-responsible-backend default-directory))
+        files)
+    (save-excursion
+      (goto-char (point-min))
+      (while (progn (diff-file-next) (not (eobp)))
+        (push (diff-find-file-name nil t) files)))
+    (list backend (nreverse files) nil nil 'patch)))
+
+
 (defun diff--filter-substring (str)
   (when diff-font-lock-prettify
     ;; Strip the `display' properties added by diff-font-lock-prettify,
diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el
index 5c664d58f1..5400352efe 100644
--- a/lisp/vc/vc-dispatcher.el
+++ b/lisp/vc/vc-dispatcher.el
@@ -753,7 +753,8 @@ vc-finish-logentry
 (defun vc-dispatcher-browsing ()
   "Are we in a directory browser buffer?"
   (or (derived-mode-p 'vc-dir-mode)
-      (derived-mode-p 'dired-mode)))
+      (derived-mode-p 'dired-mode)
+      (derived-mode-p 'diff-mode)))
 
 ;; These are unused.
 ;; (defun vc-dispatcher-in-fileset-p (fileset)
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index 208cbee0e6..ddadca2084 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -942,13 +942,23 @@ vc-git-checkin
           ;; message.  Handle also remote files.
           (if (eq system-type 'windows-nt)
               (let ((default-directory (file-name-directory file1)))
-                (make-nearby-temp-file "git-msg")))))
+                (make-nearby-temp-file "git-msg"))))
+         (patch-buffer (derived-mode-p 'diff-mode)))
+    (when patch-buffer
+      (let ((patch-string (buffer-string))
+            (patch-file (make-temp-file "git-patch")))
+        (with-temp-file patch-file
+          (insert patch-string))
+        (unwind-protect
+            (apply #'vc-git-command nil 0 patch-file
+                   (list "apply" "--cached"))
+          (delete-file patch-file))))
     (cl-flet ((boolean-arg-fn
                (argument)
                (lambda (value) (when (equal value "yes") (list argument)))))
       ;; When operating on the whole tree, better pass "-a" than ".", since "."
       ;; fails when we're committing a merge.
-      (apply #'vc-git-command nil 0 (if only files)
+      (apply #'vc-git-command nil 0 (if (and only (not patch-buffer)) files)
              (nconc (if msg-file (list "commit" "-F"
                                        (file-local-name msg-file))
                       (list "commit" "-m"))
@@ -966,7 +976,8 @@ vc-git-checkin
                           (write-region (car args) nil msg-file))
                         (setq args (cdr args)))
                       args)
-		    (if only (list "--only" "--") '("-a")))))
+		    (unless patch-buffer
+                      (if only (list "--only" "--") '("-a"))))))
     (if (and msg-file (file-exists-p msg-file)) (delete-file msg-file))))
 
 (defun vc-git-find-revision (file rev buffer)
diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el
index a6124acadd..d55cf64b3d 100644
--- a/lisp/vc/vc.el
+++ b/lisp/vc/vc.el
@@ -1098,6 +1098,8 @@ vc-deduce-fileset-1
                             state-model-only-files)
   (let (backend)
     (cond
+     ((derived-mode-p 'diff-mode)
+      (diff-vc-deduce-fileset))
      ((derived-mode-p 'vc-dir-mode)
       (vc-dir-deduce-fileset state-model-only-files))
      ((derived-mode-p 'dired-mode)
@@ -1114,7 +1116,8 @@ vc-deduce-fileset-1
            (or (buffer-file-name vc-parent-buffer)
 				(with-current-buffer vc-parent-buffer
 				  (or (derived-mode-p 'vc-dir-mode)
-				      (derived-mode-p 'dired-mode)))))
+				      (derived-mode-p 'dired-mode)
+				      (derived-mode-p 'diff-mode)))))
       (progn                  ;FIXME: Why not `with-current-buffer'? --Stef.
 	(set-buffer vc-parent-buffer)
 	(vc-deduce-fileset-1 not-state-changing allow-unregistered state-model-only-files)))
@@ -1230,6 +1233,7 @@ vc-next-action
       (error "Fileset files are missing, so cannot be operated on"))
      ((eq state 'ignored)
       (error "Fileset files are ignored by the version-control system"))
+     ((eq model 'patch) (vc-checkin files backend))
      ((or (null state) (eq state 'unregistered))
       (cond (verbose
              (let ((backend (vc-read-backend "Backend to register to: ")))
@@ -1800,7 +1804,11 @@ vc-diff-internal
          (orig-diff-buffer-clone
           (if revert-buffer-in-progress-p
               (clone-buffer
-               (generate-new-buffer-name " *vc-diff-clone*") nil))))
+               (generate-new-buffer-name " *vc-diff-clone*") nil)))
+         (patch-buffer (and (buffer-live-p vc-parent-buffer)
+                            (with-current-buffer vc-parent-buffer
+			      (derived-mode-p 'diff-mode))
+                            vc-parent-buffer)))
     ;; On MS-Windows and MS-DOS, Diff is likely to produce DOS-style
     ;; EOLs, which will look ugly if (car files) happens to have Unix
     ;; EOLs.
@@ -1837,8 +1845,11 @@ vc-diff-internal
                      (if async 'async 1) "diff" file
                      (append (vc-switches nil 'diff) `(,(null-device)))))))
         (setq files (nreverse filtered))))
-    (vc-call-backend (car vc-fileset) 'diff files rev1 rev2 buffer async)
+    (unless patch-buffer
+      (vc-call-backend (car vc-fileset) 'diff files rev1 rev2 buffer async))
     (set-buffer buffer)
+    (when patch-buffer
+      (insert (with-current-buffer patch-buffer (buffer-string))))
     ;; Make the *vc-diff* buffer read only, the diff-mode key
     ;; bindings are nicer for read only buffers. pcl-cvs does the
     ;; same thing.