From: Sean Whitton <spwhitton@spwhitton.name>
To: Dmitry Gutov <dgutov@yandex.ru>
Cc: 60126@debbugs.gnu.org, juri@linkov.net
Subject: bug#60126: 30.0.50; vc-git-checkin: Offer to unstage conflicting changes
Date: Thu, 22 Dec 2022 20:59:53 -0700 [thread overview]
Message-ID: <878riybwhy.fsf@melete.silentflame.com> (raw)
In-Reply-To: <87h6xnasgn.fsf@melete.silentflame.com> (Sean Whitton's message of "Thu, 22 Dec 2022 17:12:24 -0700")
[-- Attachment #1: Type: text/plain, Size: 752 bytes --]
Hello,
On Thu 22 Dec 2022 at 05:12PM -07, Sean Whitton wrote:
>> For completeness, though, here's a way to implement 'git push --staged' with
>> Git plumbing manually: https://stackoverflow.com/a/72582276/615245
>>
>> And as for a 'git pop --index' substitute, if the stash contains only the
>> index area stuff, it might be as easy as
>>
>> git diff stash@{0}^..stash@{0} > patch.diff
>> git apply --cached patch.diff
>> git stash drop
>
> These references are helpful. I'll investigate further.
Here is my patch.
It works, except that sometimes the let-binding of process-environment
fails, such that the commands affect the normal index rather than the
temporary index. Can you see what I'm doing wrong there?
Thanks.
--
Sean Whitton
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-vc-git-checkin-Stash-other-staged-changes.patch --]
[-- Type: text/x-patch, Size: 7741 bytes --]
From acea3dcd080c1cfacbddeb257683438de8a9b325 Mon Sep 17 00:00:00 2001
From: Sean Whitton <spwhitton@spwhitton.name>
Date: Thu, 22 Dec 2022 20:54:08 -0700
Subject: [PATCH] vc-git-checkin: Stash other staged changes
* lisp/vc/vc-git.el (vc-git--stash-staged-changes): New function.
(vc-git-checkin): Use new function to avoid needing to unstage changes
unrelated to the patch we want to commit (bug#60126).
---
lisp/vc/vc-git.el | 97 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 83 insertions(+), 14 deletions(-)
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index 0a4e9caa614..86e8f5894df 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -1020,22 +1020,36 @@ 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"))))
+ to-stash)
(when vc-git-patch-string
(unless (zerop (vc-git-command nil t nil "diff" "--cached" "--quiet"))
- ;; Check that all staged changes also exist in the patch.
- ;; This is needed to allow adding/removing files that are
- ;; currently staged to the index. So remove the whole file diff
- ;; from the patch because commit will take it from the index.
+ ;; Check that what's already staged is compatible with what
+ ;; we want to commit (bug#60126).
+ ;;
+ ;; 1. If the changes to a file in the index are identical to
+ ;; the changes to that file we want to commit, remove the
+ ;; changes from our patch, and let the commit take them
+ ;; from the index. This is necessary for adding and
+ ;; removing files to work.
+ ;;
+ ;; 2. If the changes to a file in the index are different to
+ ;; changes to that file we want to commit, then we have to
+ ;; unstage the changes or abort.
+ ;;
+ ;; 3. If there are changes to a file in the index but we don't
+ ;; want to commit any changes to that file, we need to
+ ;; stash those changes before committing.
(with-temp-buffer
(vc-git-command (current-buffer) t nil "diff" "--cached")
(goto-char (point-min))
- (let ((pos (point)) file-name file-diff file-beg)
+ (let ((pos (point)) file-name file-header file-diff file-beg)
(while (not (eobp))
(when (and (looking-at "^diff --git a/\\(.+\\) b/\\(.+\\)")
(string= (match-string 1) (match-string 2)))
(setq file-name (match-string 1)))
(forward-line 1) ; skip current "diff --git" line
+ (setq file-header (buffer-substring pos (point)))
(search-forward "diff --git" nil 'move)
(move-beginning-of-line 1)
(setq file-diff (buffer-substring pos (point)))
@@ -1049,12 +1063,15 @@ vc-git-checkin
(+ file-beg (length file-diff)))))
(setq vc-git-patch-string
(string-replace file-diff "" vc-git-patch-string)))
- ((and file-name
- (yes-or-no-p
- (format "Unstage already-staged changes to %s?"
- file-name)))
- (vc-git-command nil 0 file-name "reset" "-q" "--"))
- (t (user-error "Index not empty")))
+ ((string-match (format "^%s" (regexp-quote file-header))
+ vc-git-patch-string)
+ (if (and file-name
+ (yes-or-no-p
+ (format "Unstage already-staged changes to %s?"
+ file-name)))
+ (vc-git-command nil 0 file-name "reset" "-q" "--")
+ (user-error "Index not empty")))
+ (t (push file-name to-stash)))
(setq pos (point))))))
(unless (string-empty-p vc-git-patch-string)
(let ((patch-file (make-nearby-temp-file "git-patch")))
@@ -1062,7 +1079,8 @@ vc-git-checkin
(insert vc-git-patch-string))
(unwind-protect
(vc-git-command nil 0 patch-file "apply" "--cached")
- (delete-file patch-file)))))
+ (delete-file patch-file))))
+ (when to-stash (vc-git--stash-staged-changes files)))
(cl-flet ((boolean-arg-fn
(argument)
(lambda (value) (when (equal value "yes") (list argument)))))
@@ -1088,7 +1106,58 @@ vc-git-checkin
args)
(unless vc-git-patch-string
(if only (list "--only" "--") '("-a"))))))
- (if (and msg-file (file-exists-p msg-file)) (delete-file msg-file))))
+ (if (and msg-file (file-exists-p msg-file)) (delete-file msg-file))
+ (when to-stash
+ (let ((cached (make-nearby-temp-file "git-cached")))
+ (unwind-protect
+ (progn (with-temp-file cached
+ (vc-git-command t 0 nil "stash" "show" "-p"))
+ (vc-git-command nil 0 cached "apply" "--cached"))
+ (delete-file cached))
+ (vc-git-command nil 0 nil "stash" "drop")))))
+
+(defun vc-git--stash-staged-changes (files &optional message)
+ "Stash only the staged changes to FILES with description MESSAGE."
+ ;; This is necessary because even if you pass a list of file names
+ ;; to git-stash(1), it will stash any and all staged changes.
+ (unless (zerop
+ (vc-git-command nil t files "diff" "--cached" "--quiet"))
+ (unless message (setq message "Previously staged changes"))
+ (cl-flet
+ ((git-string (&rest args)
+ (string-trim-right
+ (with-output-to-string
+ (apply #'vc-git-command standard-output 0 nil args)))))
+ (let ((cached (make-nearby-temp-file "git-cached"))
+ tree)
+ ;; Use a temporary index to create a tree object corresponding
+ ;; to the staged changes to FILES.
+ (unwind-protect
+ (progn
+ (with-temp-file cached
+ (vc-git-command t 0 files "diff" "--cached" "--"))
+ (let* ((index (make-nearby-temp-file "git-index"))
+ (process-environment
+ (cons (format "GIT_INDEX_FILE=%s" index)
+ process-environment)))
+ (unwind-protect
+ (progn
+ (vc-git-command nil 0 nil "read-tree" "HEAD")
+ (vc-git-command nil 0 cached "apply" "--cached")
+ (setq tree (git-string "write-tree")))
+ (delete-file index))))
+ (delete-file cached))
+ ;; Prepare stash commit object, which has a special structure.
+ (let* ((tree-commit (git-string "commit-tree" "-m" message
+ "-p" "HEAD" tree))
+ (stash-commit (git-string "commit-tree" "-m" message
+ "-p" "HEAD" "-p" tree-commit
+ tree)))
+ ;; Push the new stash entry.
+ (vc-git-command nil 0 nil "update-ref" "--create-reflog"
+ "-m" message "refs/stash" stash-commit)
+ ;; Unstage the changes we've now stashed.
+ (vc-git-command nil 0 files "reset" "--"))))))
(defun vc-git-find-revision (file rev buffer)
(let* (process-file-side-effects
--
2.30.2
next prev parent reply other threads:[~2022-12-23 3:59 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-12-16 18:32 bug#60126: 30.0.50; vc-git-checkin: Offer to unstage conflicting changes Sean Whitton
2022-12-17 17:06 ` Juri Linkov
2022-12-18 0:20 ` Sean Whitton
2022-12-18 1:08 ` Dmitry Gutov
2022-12-19 22:30 ` Sean Whitton
2022-12-20 0:53 ` Dmitry Gutov
2022-12-20 6:43 ` Sean Whitton
2022-12-20 13:47 ` Dmitry Gutov
2022-12-20 16:47 ` Sean Whitton
2022-12-20 15:13 ` Dmitry Gutov
2022-12-20 17:04 ` Sean Whitton
2022-12-20 23:10 ` Sean Whitton
2022-12-20 23:41 ` Sean Whitton
2022-12-20 23:45 ` Dmitry Gutov
2022-12-23 0:12 ` Sean Whitton
2022-12-23 3:59 ` Sean Whitton [this message]
2022-12-23 8:16 ` Eli Zaretskii
2022-12-24 2:03 ` Sean Whitton
2022-12-23 23:18 ` Dmitry Gutov
2022-12-24 2:02 ` Sean Whitton
2022-12-24 14:50 ` Dmitry Gutov
2022-12-24 18:22 ` Sean Whitton
2022-12-24 19:26 ` Dmitry Gutov
2022-12-24 20:10 ` Sean Whitton
2022-12-23 22:55 ` Dmitry Gutov
2022-12-20 17:13 ` Juri Linkov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=878riybwhy.fsf@melete.silentflame.com \
--to=spwhitton@spwhitton.name \
--cc=60126@debbugs.gnu.org \
--cc=dgutov@yandex.ru \
--cc=juri@linkov.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.