Commit auto-commits to separate branch...

...before merging the auto-commit back into the main branch.

This protects against the following scenario:

1. You've loaded a file at commit A.
2. While your file is unsaved, the repo underneath is updated to some commit B
   that is a descendant of A.
3. You save your file. Emacs prompts you that the file underneath has changed.
   You decide to overwrite, but you've lost the differences between A and B.

Now, we first switch to a temp branch and save the file as a commit on top of
commit A, and then switch back to the branch with commit B and merge the temp
branch (with our new change) back in.
This commit is contained in:
Robert Irelan 2019-04-09 11:46:57 -07:00
parent b9d5f8c2d4
commit 666ee4350a

View file

@ -56,6 +56,39 @@ If non-nil a git push will be executed after each commit."
:group 'git-auto-commit-mode :group 'git-auto-commit-mode
:type 'boolean) :type 'boolean)
(defcustom gac-loaded-commit nil
"Git commit in the buffer when it was loaded.
DO NOT MODIFY - used to ensure that the saved file doesn't conflict with
changes made since the current file was loaded."
:tag "Current commit"
:group 'git-auto-commit-mode
:type 'string
:risky t)
(make-variable-buffer-local 'gac-loaded-commit)
(defcustom gac-before-save-branch nil
"Git branch active immediately before a file is saved.
DO NOT MODIFY - used to ensure that the saved file doesn't conflict with
changes made since the current file was loaded."
:tag "Current commit"
:group 'git-auto-commit-mode
:type 'string
:risky t)
(make-variable-buffer-local 'gac-before-save-branch)
(defcustom gac-merge-branch nil
"Merge branch used when saving.
DO NOT MODIFY - used to ensure that the saved file doesn't conflict with
changes made since the current file was loaded."
:tag "Current commit"
:group 'git-auto-commit-mode
:type 'string
:risky t)
(make-variable-buffer-local 'gac-merge-branch)
(defcustom gac-ask-for-summary-p nil (defcustom gac-ask-for-summary-p nil
"Ask the user for a short summary each time a file is committed?" "Ask the user for a short summary each time a file is committed?"
:tag "Ask for a summary on each commit" :tag "Ask for a summary on each commit"
@ -166,6 +199,9 @@ Default to FILENAME."
(replace-regexp-in-string "\n\\'" "" (replace-regexp-in-string "\n\\'" ""
(gac--shell-command-to-string-throw "git rev-parse --verify HEAD")))) (gac--shell-command-to-string-throw "git rev-parse --verify HEAD"))))
(defun gac--load-current-commit ()
(setq-local gac-loaded-commit (gac--current-commit)))
(defun gac--current-branch () (defun gac--current-branch ()
"Return the current Git branch." "Return the current Git branch."
(let* ((buffer-file (buffer-file-name)) (let* ((buffer-file (buffer-file-name))
@ -208,6 +244,17 @@ Standard error is inserted into a temp buffer if it's generated."
rv command))) rv command)))
(delete-file err-file))))) (delete-file err-file)))))
(defun gac-checkout-merge-branch ()
"Create and check out a merge branch."
(setq-local gac-before-save-branch (gac--current-branch))
(setq-local gac-merge-branch (format "gac-merge-%d" (float-time)))
(let* ((buffer-file (buffer-file-name))
(default-directory (file-name-directory buffer-file)))
(gac--shell-command-throw
(format "git checkout -b %s %s"
(shell-quote-argument gac-merge-branch)
(shell-quote-argument gac-before-save-branch)))))
(defun gac-commit (buffer) (defun gac-commit (buffer)
"Commit the current buffer's file to git." "Commit the current buffer's file to git."
(let* ((buffer-file (buffer-file-name buffer)) (let* ((buffer-file (buffer-file-name buffer))
@ -220,6 +267,27 @@ Standard error is inserted into a temp buffer if it's generated."
gac-shell-and gac-shell-and
"git commit -m " (shell-quote-argument commit-msg))))) "git commit -m " (shell-quote-argument commit-msg)))))
(defun gac-merge (buffer)
"Merge gac-merge-branch back into gac-before-save-branch."
(let* ((buffer-file (buffer-file-name))
(default-directory (file-name-directory buffer-file)))
(gac--shell-command-throw
(concat (format "git checkout %s"
(shell-quote-argument gac-before-save-branch))
gac-shell-and
(format "git merge -m %s %s"
(shell-quote-argument
(format "Merge branch '%s'"
gac-merge-branch))
(shell-quote-argument gac-merge-branch))
gac-shell-and
(format "git branch -d %s"
(shell-quote-argument gac-merge-branch))))
;; Reset variables
(setq-local gac-before-save-branch nil)
(setq-local gac-merge-branch nil)
(gac--load-current-commit)))
(defun gac-push (buffer) (defun gac-push (buffer)
"Push commits to the current upstream. "Push commits to the current upstream.
@ -271,6 +339,7 @@ should already have been set up."
(not (gac--buffer-is-tracked buffer))) (not (gac--buffer-is-tracked buffer)))
(gac--buffer-has-changes buffer))) (gac--buffer-has-changes buffer)))
(gac-commit buffer) (gac-commit buffer)
(gac-merge buffer)
(with-current-buffer buffer (with-current-buffer buffer
;; with-current-buffer required here because gac-automatically-push-p ;; with-current-buffer required here because gac-automatically-push-p
;; is buffer-local ;; is buffer-local
@ -286,6 +355,14 @@ should already have been set up."
(add-hook 'kill-buffer-hook #'gac-kill-buffer-hook) (add-hook 'kill-buffer-hook #'gac-kill-buffer-hook)
(defun gac-find-file-func ()
"Store the current commit."
(gac--load-current-commit))
(defun gac-before-save-func ()
"Create and check out a merge branch."
(gac-checkout-merge-branch))
(defun gac-after-save-func () (defun gac-after-save-func ()
"Commit the current file. "Commit the current file.
@ -299,9 +376,15 @@ When `gac-automatically-push-p' is non-nil also push."
"Automatically commit any changes made when saving with this "Automatically commit any changes made when saving with this
mode turned on and optionally push them too." mode turned on and optionally push them too."
:lighter " ga" :lighter " ga"
(if git-auto-commit-mode (cond (git-auto-commit-mode
(add-hook 'after-save-hook 'gac-after-save-func t t) (gac--load-current-commit)
(remove-hook 'after-save-hook 'gac-after-save-func t))) (add-hook 'find-file-hook 'gac-find-file-func t t)
(add-hook 'before-save-hook 'gac-before-save-func t t)
(add-hook 'after-save-hook 'gac-after-save-func t t))
(t
(remove-hook 'find-file-hook 'gac-find-file-func t)
(remove-hook 'before-save-hook 'gac-before-save-func t)
(remove-hook 'after-save-hook 'gac-after-save-func t))))
(provide 'git-auto-commit-mode) (provide 'git-auto-commit-mode)