aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/magit-20220503.1245/magit-tag.el
blob: 95d2f9a8df5ff89e419f61b3dc81ea70e7b42d38 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
;;; magit-tag.el --- Tag functionality  -*- lexical-binding:t -*-

;; Copyright (C) 2008-2022 The Magit Project Contributors

;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>

;; SPDX-License-Identifier: GPL-3.0-or-later

;; Magit is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; Magit is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
;; License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Magit.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This library implements tag commands.

;;; Code:

(require 'magit)

;; For `magit-tag-delete'.
(defvar helm-comp-read-use-marked)

;;;###autoload (autoload 'magit-tag "magit" nil t)
(transient-define-prefix magit-tag ()
  "Create or delete a tag."
  :man-page "git-tag"
  ["Arguments"
   ("-f" "Force"    ("-f" "--force"))
   ("-a" "Annotate" ("-a" "--annotate"))
   ("-s" "Sign"     ("-s" "--sign"))
   (magit-tag:--local-user)]
  [["Create"
    ("t"  "tag"     magit-tag-create)
    ("r"  "release" magit-tag-release)]
   ["Do"
    ("k"  "delete"  magit-tag-delete)
    ("p"  "prune"   magit-tag-prune)]])

(defun magit-tag-arguments ()
  (transient-args 'magit-tag))

(transient-define-argument magit-tag:--local-user ()
  :description "Sign as"
  :class 'transient-option
  :shortarg "-u"
  :argument "--local-user="
  :reader #'magit-read-gpg-signing-key
  :history-key 'magit:--gpg-sign)

;;;###autoload
(defun magit-tag-create (name rev &optional args)
  "Create a new tag with the given NAME at REV.
With a prefix argument annotate the tag.
\n(git tag [--annotate] NAME REV)"
  (interactive (list (magit-read-tag "Tag name")
                     (magit-read-branch-or-commit "Place tag on")
                     (let ((args (magit-tag-arguments)))
                       (when current-prefix-arg
                         (cl-pushnew "--annotate" args))
                       args)))
  (magit-run-git-with-editor "tag" args name rev))

;;;###autoload
(defun magit-tag-delete (tags)
  "Delete one or more tags.
If the region marks multiple tags (and nothing else), then offer
to delete those, otherwise prompt for a single tag to be deleted,
defaulting to the tag at point.
\n(git tag -d TAGS)"
  (interactive (list (--if-let (magit-region-values 'tag)
                         (magit-confirm t nil "Delete %i tags" nil it)
                       (let ((helm-comp-read-use-marked t))
                         (magit-read-tag "Delete tag" t)))))
  (magit-run-git "tag" "-d" tags))

;;;###autoload
(defun magit-tag-prune (tags remote-tags remote)
  "Offer to delete tags missing locally from REMOTE, and vice versa."
  (interactive
   (let* ((remote (magit-read-remote "Prune tags using remote"))
          (tags   (magit-list-tags))
          (rtags  (prog2 (message "Determining remote tags...")
                      (magit-remote-list-tags remote)
                    (message "Determining remote tags...done")))
          (ltags  (-difference tags rtags))
          (rtags  (-difference rtags tags)))
     (unless (or ltags rtags)
       (message "Same tags exist locally and remotely"))
     (unless (magit-confirm t
               "Delete %s locally"
               "Delete %i tags locally"
               'noabort ltags)
       (setq ltags nil))
     (unless (magit-confirm t
               "Delete %s from remote"
               "Delete %i tags from remote"
               'noabort rtags)
       (setq rtags nil))
     (list ltags rtags remote)))
  (when tags
    (magit-call-git "tag" "-d" tags))
  (when remote-tags
    (magit-run-git-async "push" remote (--map (concat ":" it) remote-tags))))

(defvar magit-tag-version-regexp-alist
  '(("^[-._+ ]?snapshot\\.?$" . -4)
    ("^[-._+]$" . -4)
    ("^[-._+ ]?\\(cvs\\|git\\|bzr\\|svn\\|hg\\|darcs\\)\\.?$" . -4)
    ("^[-._+ ]?unknown\\.?$" . -4)
    ("^[-._+ ]?alpha\\.?$" . -3)
    ("^[-._+ ]?beta\\.?$" . -2)
    ("^[-._+ ]?\\(pre\\|rc\\)\\.?$" . -1))
  "Overrides `version-regexp-alist' for `magit-tag-release'.
See also `magit-release-tag-regexp'.")

(defvar magit-release-tag-regexp "\\`\
\\(?1:\\(?:v\\(?:ersion\\)?\\|r\\(?:elease\\)?\\)?[-_]?\\)?\
\\(?2:[0-9]+\\(?:\\.[0-9]+\\)*\
\\(?:-[a-zA-Z0-9-]+\\(?:\\.[a-zA-Z0-9-]+\\)*\\)?\\)\\'"
  "Regexp used by `magit-tag-release' to parse release tags.

The first submatch must match the prefix, if any.  The second
submatch must match the version string.

If this matches versions that are not dot separated numbers,
then `magit-tag-version-regexp-alist' has to contain entries
for the separators allowed here.")

(defvar magit-release-commit-regexp "\\`Release version \\(.+\\)\\'"
  "Regexp used by `magit-tag-release' to parse release commit messages.
The first submatch must match the version string.")

;;;###autoload
(defun magit-tag-release (tag msg &optional args)
  "Create a release tag for `HEAD'.

Assume that release tags match `magit-release-tag-regexp'.

If `HEAD's message matches `magit-release-commit-regexp', then
base the tag on the version string specified by that.  Otherwise
prompt for the name of the new tag using the highest existing
tag as initial input and leaving it to the user to increment the
desired part of the version string.

If `--annotate' is enabled, then prompt for the message of the
new tag.  Base the proposed tag message on the message of the
highest tag, provided that that contains the corresponding
version string and substituting the new version string for that.
Otherwise propose something like \"Foo-Bar 1.2.3\", given, for
example, a TAG \"v1.2.3\" and a repository located at something
like \"/path/to/foo-bar\"."
  (interactive
   (save-match-data
     (pcase-let*
         ((`(,pver ,ptag ,pmsg) (car (magit--list-releases)))
          (msg (magit-rev-format "%s"))
          (ver (and (string-match magit-release-commit-regexp msg)
                    (match-string 1 msg)))
          (_   (and (not ver)
                    (require (quote sisyphus) nil t)
                    (string-match magit-release-commit-regexp
                                  (magit-rev-format "%s" ptag))
                    (user-error "Use `sisyphus-create-release' first")))
          (tag (if ver
                   (concat (and (string-match magit-release-tag-regexp ptag)
                                (match-string 1 ptag))
                           ver)
                 (read-string
                  (format "Create release tag (previous was %s): " ptag)
                  ptag)))
          (ver (and (string-match magit-release-tag-regexp tag)
                    (match-string 2 tag)))
          (args (magit-tag-arguments)))
       (list tag
             (and (member "--annotate" args)
                  (read-string
                   (format "Message for %S: " tag)
                   (cond ((and pver (string-match (regexp-quote pver) pmsg))
                          (replace-match ver t t pmsg))
                         ((and ptag (string-match (regexp-quote ptag) pmsg))
                          (replace-match tag t t pmsg))
                         (t (format "%s %s"
                                    (capitalize
                                     (file-name-nondirectory
                                      (directory-file-name (magit-toplevel))))
                                    ver)))))
             args))))
  (magit-run-git-async "tag" args (and msg (list "-m" msg)) tag)
  (set-process-sentinel
   magit-this-process
   (lambda (process event)
     (when (memq (process-status process) '(exit signal))
       (magit-process-sentinel process event)
       (magit-refs-setup-buffer "HEAD" (magit-show-refs-arguments))))))

(defun magit--list-releases ()
  "Return a list of releases.
The list is ordered, beginning with the highest release.
Each release element has the form (VERSION TAG MESSAGE).
`magit-release-tag-regexp' is used to determine whether
a tag qualifies as a release tag."
  (save-match-data
    (mapcar
     #'cdr
     (nreverse
      (cl-sort (cl-mapcan
                (lambda (line)
                  (and (string-match " +" line)
                       (let ((tag (substring line 0 (match-beginning 0)))
                             (msg (substring line (match-end 0))))
                         (and (string-match magit-release-tag-regexp tag)
                              (let ((ver (match-string 2 tag))
                                    (version-regexp-alist
                                     magit-tag-version-regexp-alist))
                                (list (list (version-to-list ver)
                                            ver tag msg)))))))
                ;; Cannot rely on "--sort=-version:refname" because
                ;; that gets confused if the version prefix has changed.
                (magit-git-lines "tag" "-n"))
               ;; The inverse of this function does not exist.
               #'version-list-< :key #'car)))))

;;; _
(provide 'magit-tag)
;;; magit-tag.el ends here