-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
core-release-management.el
343 lines (320 loc) · 14.9 KB
/
core-release-management.el
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
;;; core-spacemacs.el --- Spacemacs Core File
;;
;; Copyright (c) 2012-2016 Sylvain Benner & Contributors
;;
;; Author: Sylvain Benner <sylvain.benner@gmail.com>
;; URL: https://github.com/syl20bnr/spacemacs
;;
;; This file is not part of GNU Emacs.
;;
;;; License: GPLv3
(defconst spacemacs-repository "spacemacs"
"Name of the Spacemacs remote repository.")
(defconst spacemacs-repository-owner "syl20bnr"
"Name of the Spacemacs remote repository owner.")
(defconst spacemacs-checkversion-remote "checkversion"
"Name of the remote repository used to check for new version.")
(defconst spacemacs-checkversion-branch "master"
"Name of the branch used to check for new version.")
(defvar dotspacemacs-check-for-update)
(defvar spacemacs-version)
;; new version variables
(defvar spacemacs-new-version nil
"If non-nil a new Spacemacs version is available.")
;; used to be "6 hours" but we now check for new versions only at startup
(defvar spacemacs-version-check-interval nil
"Time between two version checks.")
(defvar spacemacs-version-check-lighter "[+]"
"Text displayed in the mode-line when a new version is available.")
(defvar spacemacs-version--check-timer nil
"The current timer for new version check.")
(defvar spacemacs-version--last-startup-check-file
(expand-file-name (concat spacemacs-cache-directory "last-version-check"))
"File where the last startup version check time is stored.")
(defvar spacemacs-version--last-startup-check-time nil
"Time of last version check.")
(defvar spacemacs-version--startup-check-interval (* 3600 24)
"Minimum number of seconds between two version checks at startup.")
(defun spacemacs/switch-to-version (&optional version)
"Switch spacemacs to VERSION.
VERSION is a string with the format `x.x.x'.
IMPORTANT: The switch is performed by hard resetting the current branch.
If VERSION is nil then a prompt will ask for a version number.
If the current version is not `master' and not `develop' then
a prompt will ask for confirmation before actually switching to the
specified version.
It is not possible to switch version when you are on `develop' branch,
users on `develop' branch must manually pull last commits instead."
(interactive)
(let ((branch (spacemacs//git-get-current-branch))
(dirty (spacemacs//git-working-directory-dirty)))
(unless version
(message "Getting version information...")
(let ((last-version (spacemacs/get-last-version)))
(setq version (read-string
(format "Version (default %s [latest]): " last-version)
nil nil last-version))))
(cond ((string-equal "develop" branch)
(message (concat "Cannot switch version because you are on "
"develop.\n"
"You have to manually `pull --rebase' the latest "
"commits.")))
(dirty
(message (concat "Your Emacs directory is not clean.\n"
"git status:\n%s") dirty))
((string-equal version spacemacs-version)
(message "You are already on the latest version."))
((or (string-equal "master" branch)
(yes-or-no-p
(format (concat "You are not on master. "
"This command will switch branches.\n"
"Are you sure that you want to switch "
"to version %s ? ")
version)))
(let ((tag (concat "v" version)))
(if (spacemacs//git-hard-reset-to-tag tag)
(progn
(setq spacemacs-version version)
(message "Successfully switched to version %s" version))
(message "An error occurred while switching to version %s"
version))))
(t (message "Update aborted.")))))
(defun spacemacs/check-for-new-version (force &optional interval)
"Periodicly check for new for new Spacemacs version.
Update `spacemacs-new-version' variable if any new version has been
found."
(interactive "P")
(cond
((and (not force)
(not dotspacemacs-check-for-update))
(message "Skipping check for new version (reason: dotfile)"))
((and (not force)
(string-equal "develop" (spacemacs//git-get-current-branch)))
(message "Skipping check for new version (reason: develop branch)"))
((and (not force)
(not (spacemacs//can-check-for-new-version-at-startup)))
(message (concat "Skipping check for new version "
"(reason: last check is too recent)")))
((require 'async nil t)
(message "Start checking for new version...")
(async-start
`(lambda ()
,(async-inject-variables "\\`spacemacs-start-directory\\'")
(load-file (concat spacemacs-start-directory
"core/core-load-paths.el"))
(require 'core-spacemacs)
(spacemacs/get-last-version))
(lambda (result)
(if result
(if (or (version< result spacemacs-version)
(string= result spacemacs-version)
(if spacemacs-new-version
(string= result spacemacs-new-version)))
(message "Spacemacs is up to date.")
(message "New version of Spacemacs available: %s" result)
(setq spacemacs-new-version result))
(message "Unable to check for new version."))))
(when interval
(setq spacemacs-version--check-timer
(run-at-time t (timer-duration interval)
'spacemacs/check-for-new-version))))
(t (message "Skipping check for new version (reason: async not loaded)"))))
(defun spacemacs/git-get-current-branch-rev ()
"Returns the hash of the head commit on the current branch.
Returns nil if an error occurred."
(let ((proc-buffer "git-get-current-branch-head-hash")
(default-directory (file-truename spacemacs-start-directory)))
(when (eq 0 (process-file "git" nil proc-buffer nil
"rev-parse" "--short" "HEAD"))
(with-current-buffer proc-buffer
(prog1
(when (buffer-string)
(goto-char (point-min))
(replace-regexp-in-string
"\n$" ""
(buffer-substring (line-beginning-position)
(line-end-position))))
(kill-buffer proc-buffer))))))
(defun spacemacs/get-new-version-lighter-face (current new)
"Return the new version lighter face given the difference between the CURRENT
version and the NEW version."
(let* ((lcur (version-to-list current))
(lnew (version-to-list new))
(scur (spacemacs//compute-version-score lcur))
(snew (spacemacs//compute-version-score lnew))
(diff (- snew scur)))
(cond
((< diff 3000) 'spacemacs-mode-line-new-version-lighter-success-face)
((< diff 5000) 'spacemacs-mode-line-new-version-lighter-warning-face)
(t 'spacemacs-mode-line-new-version-lighter-error-face))))
(defun spacemacs/get-last-version ()
"Return the last tagged version."
(interactive)
(spacemacs//get-last-version spacemacs-repository
spacemacs-repository-owner
spacemacs-checkversion-remote
spacemacs-checkversion-branch))
(defun spacemacs//can-check-for-new-version-at-startup ()
"Return non-nil if the version check at startup can be performed."
(when (file-exists-p spacemacs-version--last-startup-check-file)
(load spacemacs-version--last-startup-check-file))
(let ((result
(or (null spacemacs-version--last-startup-check-time)
(> (- (float-time) spacemacs-version--last-startup-check-time)
spacemacs-version--startup-check-interval))))
(when result
(setq spacemacs-version--last-startup-check-time (float-time))
(spacemacs/dump-vars-to-file '(spacemacs-version--last-startup-check-time)
spacemacs-version--last-startup-check-file))
result))
(defun spacemacs//get-last-version (repo owner remote branch)
"Return the last tagged version of BRANCH on REMOTE repository from
OWNER REPO."
(let ((url (format "https://github.com/%s/%s" owner repo)))
(spacemacs//git-remove-remote remote)
(spacemacs//git-add-remote remote url)
;; removing this call according to issue #6692 proposal
;; (spacemacs//git-fetch-remote remote)
(spacemacs//git-fetch-tags remote branch))
(let ((version (spacemacs//git-latest-tag remote branch)))
(when version
(save-match-data
(string-match "^.*\\([0-9]+\\.[0-9]+\\.[0-9]+\\)$" version)
(match-string 1 version)))))
(defun spacemacs//git-has-remote (remote)
"Return non nil if REMOTE is declared."
(let ((proc-buffer "git-has-remote")
(default-directory (file-truename spacemacs-start-directory)))
(when (eq 0 (process-file "git" nil proc-buffer nil "remote"))
(with-current-buffer proc-buffer
(prog2
(goto-char (point-min))
(re-search-forward (format "^%s$" remote) nil 'noerror)
(kill-buffer proc-buffer))))))
(defun spacemacs//git-add-remote (remote url)
"Add a REMOTE with URL, return t if no error."
(let ((proc-buffer "git-add-remote")
(default-directory (file-truename spacemacs-start-directory)))
(prog1
(eq 0 (process-file "git" nil proc-buffer nil
"remote" "add" remote url))
(kill-buffer proc-buffer))))
(defun spacemacs//git-remove-remote (remote)
"Remove a REMOTE, return t if no error."
(let ((proc-buffer "git-remove-remote")
(default-directory (file-truename spacemacs-start-directory)))
(prog1
(eq 0 (process-file "git" nil proc-buffer nil
"remote" "remove" remote))
(kill-buffer proc-buffer))))
(defun spacemacs//git-fetch-remote (remote)
"Fetch last commits from REMOTE, return t if no error."
(let ((proc-buffer "git-fetch-remote")
(default-directory (file-truename spacemacs-start-directory)))
(prog1
(eq 0 (process-file "git" nil proc-buffer nil
"fetch" remote))
(kill-buffer proc-buffer))))
(defun spacemacs//git-fetch-tags (remote branch)
"Fetch the tags for BRANCH in REMOTE repository."
(let ((proc-buffer "git-fetch-tags")
(default-directory (file-truename spacemacs-start-directory)))
(prog1
;;;; original comment: seems necessary to fetch first
;; but we remove this according to issue #6692 proposal
;; (eq 0 (process-file "git" nil proc-buffer nil
;; "fetch" remote branch))
;; explicitly fetch the new tags
(eq 0 (process-file "git" nil proc-buffer nil
"fetch" "--tags" remote branch))
(kill-buffer proc-buffer))))
(defun spacemacs//git-hard-reset-to-tag (tag)
"Hard reset the current branch to specifed TAG."
(let ((proc-buffer "git-hard-reset")
(default-directory (file-truename spacemacs-start-directory)))
(prog1
(eq 0 (process-file "git" nil proc-buffer nil
"reset" "--hard" tag))
(kill-buffer proc-buffer))))
(defun spacemacs//git-latest-tag (remote branch)
"Returns the latest tag on REMOTE/BRANCH."
(let ((proc-buffer "git-latest-tag")
(default-directory (file-truename spacemacs-start-directory))
(where (format "%s/%s" remote branch)))
(when (eq 0 (process-file "git" nil proc-buffer nil
"describe" "--tags" "--abbrev=0"
"--match=v*" where "FETCH_HEAD"))
(with-current-buffer proc-buffer
(prog1
(when (buffer-string)
(goto-char (point-max))
(forward-line -1)
(replace-regexp-in-string
"\n$" ""
(buffer-substring (line-beginning-position)
(line-end-position))))
(kill-buffer proc-buffer))))))
(defun spacemacs//git-checkout (branch)
"Checkout the given BRANCH. Return t if there is no error."
(let ((proc-buffer "git-checkout")
(default-directory (file-truename spacemacs-start-directory)))
(prog1
(eq 0 (process-file "git" nil proc-buffer nil
"checkout" branch))
(kill-buffer proc-buffer))))
(defun spacemacs//git-get-current-branch ()
"Return the current branch. Return nil if an error occurred."
(let ((proc-buffer "git-get-current-branch")
(default-directory (file-truename spacemacs-start-directory)))
(when (eq 0 (process-file "git" nil proc-buffer nil
"symbolic-ref" "--short" "-q" "HEAD"))
(with-current-buffer proc-buffer
(prog1
(when (buffer-string)
(goto-char (point-min))
(replace-regexp-in-string
"\n$" ""
(buffer-substring (line-beginning-position)
(line-end-position))))
(kill-buffer proc-buffer))))))
(defun spacemacs//git-working-directory-dirty ()
"Non-nil if the user's emacs directory is not clean.
Returns the output of git status --porcelain."
(let ((proc-buffer "git-working-directory-dirty")
(default-directory (file-truename spacemacs-start-directory)))
(when (eq 0 (process-file "git" nil proc-buffer nil
"status" "--porcelain"))
(with-current-buffer proc-buffer
(prog1
(when (and (buffer-string)
;;simplecheckforanytext
(string-match-p "[^ \t\n]" (buffer-string)))
(replace-regexp-in-string "\n\\'" "" (buffer-string)))
(kill-buffer proc-buffer))))))
(defun spacemacs//deffaces-new-version-lighter (state)
"Define a new version lighter face for the given STATE."
(let* ((fname (intern
(format "spacemacs-mode-line-new-version-lighter-%s-face"
(symbol-name state))))
(foreground (face-foreground state)))
(eval `(defface ,fname '((t ()))
,(format "Color for new version lighter in mode line (%s)."
(symbol-name state))
:group 'spacemacs))
(set-face-attribute fname nil
:foreground foreground
:box (face-attribute 'mode-line :box))))
(defun spacemacs//compute-version-score (version)
"Returns an integer from the version list.
Example: (1 42 3) = 1 042 003"
(let ((i -1))
(cl-reduce '+ (mapcar (lambda (n) (setq i (1+ i)) (* n (expt 10 (* i 3))))
(reverse version)))))
(defun spacemacs/set-new-version-lighter-mode-line-faces ()
"Define or set the new version lighter mode-line faces."
(mapcar 'spacemacs//deffaces-new-version-lighter
'(error warning success)))
(spacemacs/set-new-version-lighter-mode-line-faces)
(add-hook 'spacemacs-post-theme-change-hook
'spacemacs/set-new-version-lighter-mode-line-faces)
(provide 'core-release-management)