magit-tag.el (8445B)
1 ;;; magit-tag.el --- tag functionality -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2010-2021 The Magit Project Contributors 4 ;; 5 ;; You should have received a copy of the AUTHORS.md file which 6 ;; lists all contributors. If not, see http://magit.vc/authors. 7 8 ;; Author: Jonas Bernoulli <jonas@bernoul.li> 9 ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> 10 11 ;; SPDX-License-Identifier: GPL-3.0-or-later 12 13 ;; Magit is free software; you can redistribute it and/or modify it 14 ;; under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation; either version 3, or (at your option) 16 ;; any later version. 17 ;; 18 ;; Magit is distributed in the hope that it will be useful, but WITHOUT 19 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 20 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 21 ;; License for more details. 22 ;; 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with Magit. If not, see http://www.gnu.org/licenses. 25 26 ;;; Commentary: 27 28 ;; This library implements tag commands. 29 30 ;;; Code: 31 32 (require 'magit) 33 34 ;; For `magit-tag-delete'. 35 (defvar helm-comp-read-use-marked) 36 37 ;;;###autoload (autoload 'magit-tag "magit" nil t) 38 (transient-define-prefix magit-tag () 39 "Create or delete a tag." 40 :man-page "git-tag" 41 ["Arguments" 42 ("-f" "Force" ("-f" "--force")) 43 ("-a" "Annotate" ("-a" "--annotate")) 44 ("-s" "Sign" ("-s" "--sign")) 45 (magit-tag:--local-user)] 46 [["Create" 47 ("t" "tag" magit-tag-create) 48 ("r" "release" magit-tag-release)] 49 ["Do" 50 ("k" "delete" magit-tag-delete) 51 ("p" "prune" magit-tag-prune)]]) 52 53 (defun magit-tag-arguments () 54 (transient-args 'magit-tag)) 55 56 (transient-define-argument magit-tag:--local-user () 57 :description "Sign as" 58 :class 'transient-option 59 :shortarg "-u" 60 :argument "--local-user=" 61 :reader 'magit-read-gpg-signing-key 62 :history-key 'magit:--gpg-sign) 63 64 ;;;###autoload 65 (defun magit-tag-create (name rev &optional args) 66 "Create a new tag with the given NAME at REV. 67 With a prefix argument annotate the tag. 68 \n(git tag [--annotate] NAME REV)" 69 (interactive (list (magit-read-tag "Tag name") 70 (magit-read-branch-or-commit "Place tag on") 71 (let ((args (magit-tag-arguments))) 72 (when current-prefix-arg 73 (cl-pushnew "--annotate" args)) 74 args))) 75 (magit-run-git-with-editor "tag" args name rev)) 76 77 ;;;###autoload 78 (defun magit-tag-delete (tags) 79 "Delete one or more tags. 80 If the region marks multiple tags (and nothing else), then offer 81 to delete those, otherwise prompt for a single tag to be deleted, 82 defaulting to the tag at point. 83 \n(git tag -d TAGS)" 84 (interactive (list (--if-let (magit-region-values 'tag) 85 (magit-confirm t nil "Delete %i tags" nil it) 86 (let ((helm-comp-read-use-marked t)) 87 (magit-read-tag "Delete tag" t))))) 88 (magit-run-git "tag" "-d" tags)) 89 90 ;;;###autoload 91 (defun magit-tag-prune (tags remote-tags remote) 92 "Offer to delete tags missing locally from REMOTE, and vice versa." 93 (interactive 94 (let* ((remote (magit-read-remote "Prune tags using remote")) 95 (tags (magit-list-tags)) 96 (rtags (prog2 (message "Determining remote tags...") 97 (magit-remote-list-tags remote) 98 (message "Determining remote tags...done"))) 99 (ltags (-difference tags rtags)) 100 (rtags (-difference rtags tags))) 101 (unless (or ltags rtags) 102 (message "Same tags exist locally and remotely")) 103 (unless (magit-confirm t 104 "Delete %s locally" 105 "Delete %i tags locally" 106 'noabort ltags) 107 (setq ltags nil)) 108 (unless (magit-confirm t 109 "Delete %s from remote" 110 "Delete %i tags from remote" 111 'noabort rtags) 112 (setq rtags nil)) 113 (list ltags rtags remote))) 114 (when tags 115 (magit-call-git "tag" "-d" tags)) 116 (when remote-tags 117 (magit-run-git-async "push" remote (--map (concat ":" it) remote-tags)))) 118 119 (defvar magit-tag-version-regexp-alist 120 '(("^[-._+ ]?snapshot\\.?$" . -4) 121 ("^[-._+]$" . -4) 122 ("^[-._+ ]?\\(cvs\\|git\\|bzr\\|svn\\|hg\\|darcs\\)\\.?$" . -4) 123 ("^[-._+ ]?unknown\\.?$" . -4) 124 ("^[-._+ ]?alpha\\.?$" . -3) 125 ("^[-._+ ]?beta\\.?$" . -2) 126 ("^[-._+ ]?\\(pre\\|rc\\)\\.?$" . -1)) 127 "Overrides `version-regexp-alist' for `magit-tag-release'. 128 See also `magit-release-tag-regexp'.") 129 130 (defvar magit-release-tag-regexp "\\`\ 131 \\(?1:\\(?:v\\(?:ersion\\)?\\|r\\(?:elease\\)?\\)?[-_]?\\)?\ 132 \\(?2:[0-9]+\\(?:\\.[0-9]+\\)*\ 133 \\(?:-[a-zA-Z0-9-]+\\(?:\\.[a-zA-Z0-9-]+\\)*\\)?\\)\\'" 134 "Regexp used by `magit-tag-release' to parse release tags. 135 136 The first submatch must match the prefix, if any. The second 137 submatch must match the version string. 138 139 If this matches versions that are not dot separated numbers, 140 then `magit-tag-version-regexp-alist' has to contain entries 141 for the separators allowed here.") 142 143 ;;;###autoload 144 (defun magit-tag-release (tag msg &optional args) 145 "Create a release tag. 146 147 Assume that release tags match `magit-release-tag-regexp'. 148 149 First prompt for the name of the new tag using the highest 150 existing tag as initial input and leaving it to the user to 151 increment the desired part of the version string. 152 153 If `--annotate' is enabled, then prompt for the message of the 154 new tag. Base the proposed tag message on the message of the 155 highest tag, provided that that contains the corresponding 156 version string and substituting the new version string for that. 157 Otherwise propose something like \"Foo-Bar 1.2.3\", given, for 158 example, a TAG \"v1.2.3\" and a repository located at something 159 like \"/path/to/foo-bar\"." 160 (interactive 161 (save-match-data 162 (pcase-let* 163 ((`(,pver ,ptag ,pmsg) (car (magit--list-releases))) 164 (tag (read-string "Create release tag: " ptag)) 165 (ver (and (string-match magit-release-tag-regexp tag) 166 (match-string 2 tag))) 167 (args (magit-tag-arguments))) 168 (list tag 169 (and (member "--annotate" args) 170 (read-string 171 (format "Message for %S: " tag) 172 (cond ((and pver (string-match (regexp-quote pver) pmsg)) 173 (replace-match ver t t pmsg)) 174 ((and ptag (string-match (regexp-quote ptag) pmsg)) 175 (replace-match tag t t pmsg)) 176 (t (format "%s %s" 177 (capitalize 178 (file-name-nondirectory 179 (directory-file-name (magit-toplevel)))) 180 ver))))) 181 args)))) 182 (magit-run-git-async "tag" args (and msg (list "-m" msg)) tag) 183 (set-process-sentinel 184 magit-this-process 185 (lambda (process event) 186 (when (memq (process-status process) '(exit signal)) 187 (magit-process-sentinel process event) 188 (magit-refs-setup-buffer "HEAD" (magit-show-refs-arguments)))))) 189 190 (defun magit--list-releases () 191 "Return a list of releases. 192 The list is ordered, beginning with the highest release. 193 Each release element has the form (VERSION TAG MESSAGE). 194 `magit-release-tag-regexp' is used to determine whether 195 a tag qualifies as a release tag." 196 (save-match-data 197 (mapcar 198 #'cdr 199 (nreverse 200 (cl-sort (cl-mapcan 201 (lambda (line) 202 (and (string-match " +" line) 203 (let ((tag (substring line 0 (match-beginning 0))) 204 (msg (substring line (match-end 0)))) 205 (and (string-match magit-release-tag-regexp tag) 206 (let ((ver (match-string 2 tag)) 207 (version-regexp-alist 208 magit-tag-version-regexp-alist)) 209 (list (list (version-to-list ver) 210 ver tag msg))))))) 211 ;; Cannot rely on "--sort=-version:refname" because 212 ;; that gets confused if the version prefix has changed. 213 (magit-git-lines "tag" "-n")) 214 ;; The inverse of this function does not exist. 215 #'version-list-< :key #'car))))) 216 217 ;;; _ 218 (provide 'magit-tag) 219 ;;; magit-tag.el ends here