summaryrefslogtreecommitdiff
path: root/lisp/vc/vc-git.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/vc/vc-git.el')
-rw-r--r--lisp/vc/vc-git.el215
1 files changed, 161 insertions, 54 deletions
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index 7072b8e483b..8d8ea33f8b3 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -53,7 +53,8 @@
;; - responsible-p (file) OK
;; - receive-file (file rev) NOT NEEDED
;; - unregister (file) OK
-;; * checkin (files rev comment) OK
+;; * checkin (files comment rev) OK
+;; - checkin-patch (patch-string comment) OK
;; * find-revision (file rev buffer) OK
;; * checkout (file &optional rev) OK
;; * revert (file &optional contents-done) OK
@@ -81,7 +82,7 @@
;; - annotate-time () OK
;; - annotate-current-time () NOT NEEDED
;; - annotate-extract-revision-at-line () OK
-;; TAG SYSTEM
+;; TAG/BRANCH SYSTEM
;; - create-tag (dir name branchp) OK
;; - retrieve-tag (dir name update) OK
;; MISCELLANEOUS
@@ -118,6 +119,14 @@ If nil, use the value of `vc-diff-switches'. If t, use no switches."
(repeat :tag "Argument List" :value ("") string))
:version "23.1")
+;;;###autoload
+(defun vc-git-annotate-switches-safe-p (switches)
+ "Check if local value of `vc-git-annotate-switches' is safe.
+Currently only \"-w\" (ignore whitespace) is considered safe, but
+this list might be extended in the future."
+ ;; TODO: Probably most options are perfectly safe.
+ (equal switches "-w"))
+
(defcustom vc-git-annotate-switches nil
"String or list of strings specifying switches for Git blame under VC.
If nil, use the value of `vc-annotate-switches'. If t, use no switches."
@@ -126,6 +135,7 @@ If nil, use the value of `vc-annotate-switches'. If t, use no switches."
(string :tag "Argument String")
(repeat :tag "Argument List" :value ("") string))
:version "25.1")
+;;;###autoload(put 'vc-git-annotate-switches 'safe-local-variable #'vc-git-annotate-switches-safe-p)
(defcustom vc-git-log-switches nil
"String or list of strings specifying switches for Git log under VC."
@@ -290,12 +300,14 @@ Good example of file name that needs this: \"test[56].xx\".")
(vc-git--run-command-string nil "version")))
(setq vc-git--program-version
(if (and version-string
- ;; Git for Windows appends ".windows.N" to the
- ;; numerical version reported by Git.
- (string-match
- "git version \\([0-9.]+\\)\\(\\.windows\\.[0-9]+\\)?$"
- version-string))
- (match-string 1 version-string)
+ ;; Some Git versions append additional strings
+ ;; to the numerical version string. E.g., Git
+ ;; for Windows appends ".windows.N", while Git
+ ;; for Mac appends " (Apple Git-N)". Capture
+ ;; numerical version and ignore the rest.
+ (string-match "git version \\([0-9][0-9.]+\\)"
+ version-string))
+ (string-trim-right (match-string 1 version-string) "\\.")
"0")))))
(defun vc-git--git-status-to-vc-state (code-list)
@@ -662,32 +674,26 @@ or an empty string if none."
:files files
:update-function update-function)))
-(defvar vc-git-stash-shared-map
- (let ((map (make-sparse-keymap)))
- (define-key map "S" #'vc-git-stash-snapshot)
- (define-key map "C" #'vc-git-stash)
- map))
+(defvar-keymap vc-git-stash-shared-map
+ "S" #'vc-git-stash-snapshot
+ "C" #'vc-git-stash)
-(defvar vc-git-stash-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map vc-git-stash-shared-map)
- ;; Turn off vc-dir marking
- (define-key map [mouse-2] #'ignore)
-
- (define-key map [down-mouse-3] #'vc-git-stash-menu)
- (define-key map "\C-k" #'vc-git-stash-delete-at-point)
- (define-key map "=" #'vc-git-stash-show-at-point)
- (define-key map "\C-m" #'vc-git-stash-show-at-point)
- (define-key map "A" #'vc-git-stash-apply-at-point)
- (define-key map "P" #'vc-git-stash-pop-at-point)
- map))
+(defvar-keymap vc-git-stash-map
+ :parent vc-git-stash-shared-map
+ ;; Turn off vc-dir marking
+ "<mouse-2>" #'ignore
-(defvar vc-git-stash-button-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map vc-git-stash-shared-map)
- (define-key map [mouse-2] #'push-button)
- (define-key map "\C-m" #'push-button)
- map))
+ "<down-mouse-3>" #'vc-git-stash-menu
+ "C-k" #'vc-git-stash-delete-at-point
+ "=" #'vc-git-stash-show-at-point
+ "RET" #'vc-git-stash-show-at-point
+ "A" #'vc-git-stash-apply-at-point
+ "P" #'vc-git-stash-pop-at-point)
+
+(defvar-keymap vc-git-stash-button-map
+ :parent vc-git-stash-shared-map
+ "<mouse-2>" #'push-button
+ "RET" #'push-button)
(defconst vc-git-stash-shared-help
"\\<vc-git-stash-shared-map>\\[vc-git-stash]: Create named stash\n\\[vc-git-stash-snapshot]: Snapshot stash")
@@ -861,6 +867,47 @@ The car of the list is the current branch."
;;; STATE-CHANGING FUNCTIONS
+(defcustom vc-git-log-edit-summary-target-len nil
+ "Target length for Git commit summary lines.
+If a number, characters in Summary: lines beyond this length are
+displayed in the `vc-git-log-edit-summary-target-warning' face.
+A value of any other type means no highlighting.
+
+By setting this to an integer around 50, you can improve the
+compatibility of your commit messages with Git commands that
+print the summary line in width-constrained contexts. However,
+many commit summaries will need to exceed this length.
+
+See also `vc-git-log-edit-summary-max-len'."
+ :type '(choice (const :tag "No target" nil)
+ (natnum :tag "Target length"))
+ :safe (lambda (x) (or (not x) (natnump x))))
+
+(defface vc-git-log-edit-summary-target-warning
+ '((t :inherit warning))
+ "Face for Git commit summary lines beyond the target length.
+See `vc-git-log-edit-summary-target-len'.")
+
+(defcustom vc-git-log-edit-summary-max-len 68
+ "Maximum length for Git commit summary lines.
+If a number, characters in summary lines beyond this length are
+displayed in the `vc-git-log-edit-summary-max-warning' face.
+A value of any other type means no highlighting.
+
+It is good practice to avoid writing summary lines longer than
+this because otherwise the summary line will be truncated in many
+contexts in which Git commands display summary lines.
+
+See also `vc-git-log-edit-summary-target-len'."
+ :type '(choice (const :tag "No target" nil)
+ (natnum :tag "Target length"))
+ :safe (lambda (x) (or (not x) (natnump x))))
+
+(defface vc-git-log-edit-summary-max-warning
+ '((t :inherit error))
+ "Face for Git commit summary lines beyond the maximum length.
+See `vc-git-log-edit-summary-max-len'.")
+
(defun vc-git-create-repo ()
"Create a new Git repository."
(vc-git-command nil 0 nil "init"))
@@ -908,16 +955,44 @@ If toggling on, also insert its message into the buffer."
standard-output 1 nil
"log" "--max-count=1" "--pretty=format:%B" "HEAD")))))
-(defvar vc-git-log-edit-mode-map
- (let ((map (make-sparse-keymap "Git-Log-Edit")))
- (define-key map "\C-c\C-s" #'vc-git-log-edit-toggle-signoff)
- (define-key map "\C-c\C-n" #'vc-git-log-edit-toggle-no-verify)
- (define-key map "\C-c\C-e" #'vc-git-log-edit-toggle-amend)
- map))
+(defvar-keymap vc-git-log-edit-mode-map
+ :name "Git-Log-Edit"
+ "C-c C-s" #'vc-git-log-edit-toggle-signoff
+ "C-c C-n" #'vc-git-log-edit-toggle-no-verify
+ "C-c C-e" #'vc-git-log-edit-toggle-amend)
+
+(defun vc-git--log-edit-summary-check (limit)
+ (and (re-search-forward "^Summary: " limit t)
+ (when-let ((regex
+ (cond ((and (natnump vc-git-log-edit-summary-max-len)
+ (natnump vc-git-log-edit-summary-target-len))
+ (format ".\\{,%d\\}\\(.\\{,%d\\}\\)\\(.*\\)"
+ vc-git-log-edit-summary-target-len
+ (- vc-git-log-edit-summary-max-len
+ vc-git-log-edit-summary-target-len)))
+ ((natnump vc-git-log-edit-summary-max-len)
+ (format ".\\{,%d\\}\\(?2:.*\\)"
+ vc-git-log-edit-summary-max-len))
+ ((natnump vc-git-log-edit-summary-target-len)
+ (format ".\\{,%d\\}\\(.*\\)"
+ vc-git-log-edit-summary-target-len)))))
+ (re-search-forward regex limit t))))
(define-derived-mode vc-git-log-edit-mode log-edit-mode "Log-Edit/git"
"Major mode for editing Git log messages.
-It is based on `log-edit-mode', and has Git-specific extensions.")
+It is based on `log-edit-mode', and has Git-specific extensions."
+ (setq-local
+ log-edit-font-lock-keywords
+ (append log-edit-font-lock-keywords
+ '((vc-git--log-edit-summary-check
+ (1 'vc-git-log-edit-summary-target-warning prepend t)
+ (2 'vc-git-log-edit-summary-max-warning prepend t))))))
+
+(defvar vc-git-patch-string nil)
+
+(defun vc-git-checkin-patch (patch-string comment)
+ (let ((vc-git-patch-string patch-string))
+ (vc-git-checkin nil comment)))
(defun vc-git-checkin (files comment &optional _rev)
(let* ((file1 (or (car files) default-directory))
@@ -941,12 +1016,21 @@ It is based on `log-edit-mode', and has Git-specific extensions.")
(if (eq system-type 'windows-nt)
(let ((default-directory (file-name-directory file1)))
(make-nearby-temp-file "git-msg")))))
+ (when vc-git-patch-string
+ (unless (zerop (vc-git-command nil t nil "diff" "--cached" "--quiet"))
+ (user-error "Index not empty"))
+ (let ((patch-file (make-temp-file "git-patch")))
+ (with-temp-file patch-file
+ (insert vc-git-patch-string))
+ (unwind-protect
+ (vc-git-command nil 0 patch-file "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 vc-git-patch-string)) files)
(nconc (if msg-file (list "commit" "-F"
(file-local-name msg-file))
(list "commit" "-m"))
@@ -964,7 +1048,8 @@ It is based on `log-edit-mode', and has Git-specific extensions.")
(write-region (car args) nil msg-file))
(setq args (cdr args)))
args)
- (if only (list "--only" "--") '("-a")))))
+ (unless vc-git-patch-string
+ (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)
@@ -1487,13 +1572,25 @@ This requires git 1.8.4 or later, for the \"-L\" option of \"git log\"."
(expand-file-name fname (vc-git-root default-directory))))
revision)))))
-;;; TAG SYSTEM
+;;; TAG/BRANCH SYSTEM
+
+(declare-function vc-read-revision "vc"
+ (prompt &optional files backend default initial-input))
(defun vc-git-create-tag (dir name branchp)
- (let ((default-directory dir))
- (and (vc-git-command nil 0 nil "update-index" "--refresh")
+ (let ((default-directory dir)
+ (start-point (when branchp (vc-read-revision
+ (format-prompt "Start point"
+ (car (vc-git-branches)))
+ (list dir) 'Git))))
+ (and (or (zerop (vc-git-command nil t nil "update-index" "--refresh"))
+ (y-or-n-p "Modified files exist. Proceed? ")
+ (user-error (format "Can't create %s with modified files"
+ (if branchp "branch" "tag"))))
(if branchp
- (vc-git-command nil 0 nil "checkout" "-b" name)
+ (vc-git-command nil 0 nil "checkout" "-b" name
+ (when (and start-point (not (eq start-point "")))
+ start-point))
(vc-git-command nil 0 nil "tag" name)))))
(defun vc-git-retrieve-tag (dir name _update)
@@ -1597,7 +1694,7 @@ This requires git 1.8.4 or later, for the \"-L\" option of \"git log\"."
(declare-function grep-read-regexp "grep" ())
(declare-function grep-read-files "grep" (regexp))
(declare-function grep-expand-template "grep"
- (template &optional regexp files dir excl))
+ (template &optional regexp files dir excl more-opts))
(defvar compilation-environment)
;; Derived from `lgrep'.
@@ -1680,7 +1777,7 @@ This command shares argument histories with \\[rgrep] and \\[grep]."
(let ((stash (completing-read
prompt
(split-string
- (or (vc-git--run-command-string nil "stash" "list") "") "\n")
+ (or (vc-git--run-command-string nil "stash" "list") "") "\n" t)
nil :require-match nil 'vc-git-stash-read-history)))
(if (string-equal stash "")
(user-error "Not a stash")
@@ -1693,8 +1790,8 @@ This command shares argument histories with \\[rgrep] and \\[grep]."
(vc-setup-buffer "*vc-git-stash*")
(vc-git-command "*vc-git-stash*" 'async nil "stash" "show" "-p" name)
(set-buffer "*vc-git-stash*")
- (diff-mode)
(setq buffer-read-only t)
+ (diff-mode)
(pop-to-buffer (current-buffer)))
(defun vc-git-stash-apply (name)
@@ -1725,12 +1822,11 @@ This command shares argument histories with \\[rgrep] and \\[grep]."
(defun vc-git-stash-list ()
(when-let ((out (vc-git--run-command-string nil "stash" "list")))
- (delete
- ""
- (split-string
- (replace-regexp-in-string
- "^stash@" " " out)
- "\n"))))
+ (split-string
+ (replace-regexp-in-string
+ "^stash@" " " out)
+ "\n"
+ t)))
(defun vc-git-stash-get-at-point (point)
(save-excursion
@@ -1867,6 +1963,17 @@ Returns nil if not possible."
(1- (point-max)))))))
(and name (not (string= name "undefined")) name))))
+(defvar-keymap vc-dir-git-mode-map
+ "z c" #'vc-git-stash
+ "z s" #'vc-git-stash-snapshot
+ "z p" #'vc-git-stash-pop)
+
+(define-minor-mode vc-dir-git-mode
+ "A minor mode for git-specific commands in `vc-dir-mode' buffers.
+Also note that there are git stash commands available in the
+\"Stash\" section at the head of the buffer."
+ :lighter " Git")
+
(provide 'vc-git)
;;; vc-git.el ends here