;;; core-spacemacs.el --- Spacemacs Core File ;; ;; Copyright (c) 2012-2016 Sylvain Benner & Contributors ;; ;; Author: Sylvain Benner ;; 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)