-;;; vc-hg.el --- VC backend for the mercurial version control system
+;;; vc-hg.el --- VC backend for the mercurial version control system -*- lexical-binding: t -*-
-;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+;; Copyright (C) 2006-2012 Free Software Foundation, Inc.
;; Author: Ivan Kanis
+;; Maintainer: FSF
;; Keywords: vc tools
;; Package: vc
;;; Code:
(eval-when-compile
- (require 'cl)
+ (require 'cl-lib)
(require 'vc)
(require 'vc-dir))
;;; Customization options
+(defgroup vc-hg nil
+ "VC Mercurial (hg) backend."
+ :version "24.1"
+ :group 'vc)
+
(defcustom vc-hg-global-switches nil
"Global switches to pass to any Hg command."
:type '(choice (const :tag "None" nil)
(string :tag "Argument String")
(repeat :tag "Argument List" :value ("") string))
:version "22.2"
- :group 'vc)
+ :group 'vc-hg)
(defcustom vc-hg-diff-switches t ; Hg doesn't support common args like -u
"String or list of strings specifying switches for Hg diff under VC.
(string :tag "Argument String")
(repeat :tag "Argument List" :value ("") string))
:version "23.1"
- :group 'vc)
+ :group 'vc-hg)
(defcustom vc-hg-program "hg"
"Name of the Mercurial executable (excluding any arguments)."
:type 'string
- :group 'vc)
+ :group 'vc-hg)
+
+(defcustom vc-hg-root-log-format
+ '("{rev}:{tags}: {author|person} {date|shortdate} {desc|firstline}\\n"
+ "^\\([0-9]+\\):\\([^:]*\\): \\(.*?\\)[ \t]+\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)"
+ ((1 'log-view-message-face)
+ (2 'change-log-list)
+ (3 'change-log-name)
+ (4 'change-log-date)))
+ "Mercurial log template for `vc-print-root-log'.
+This should be a list (TEMPLATE REGEXP KEYWORDS), where TEMPLATE
+is the \"--template\" argument string to pass to Mercurial,
+REGEXP is a regular expression matching the resulting Mercurial
+output, and KEYWORDS is a list of `font-lock-keywords' for
+highlighting the Log View buffer."
+ :type '(list string string (repeat sexp))
+ :group 'vc-hg
+ :version "24.1")
+
\f
;;; Properties of the backend
+(defvar vc-hg-history nil)
+
(defun vc-hg-revision-granularity () 'repository)
-(defun vc-hg-checkout-model (files) 'implicit)
+(defun vc-hg-checkout-model (_files) 'implicit)
;;; State querying functions
((eq state ?R) 'removed)
((eq state ?!) 'missing)
((eq state ??) 'unregistered)
- ((eq state ?C) 'up-to-date) ;; Older mercurials use this
+ ((eq state ?C) 'up-to-date) ;; Older mercurial versions use this.
(t 'up-to-date)))))))
(defun vc-hg-working-revision (file)
"Hg-specific version of `vc-working-revision'."
- (let*
- ((status nil)
- (default-directory (file-name-directory file))
- ;; Avoid localization of messages so we can parse the output.
- (avoid-local-env (append (list "TERM=dumb" "LANGUAGE=C")
- process-environment))
- (out
- (with-output-to-string
- (with-current-buffer
- standard-output
- (setq status
- (condition-case nil
- (let ((process-environment avoid-local-env))
- ;; Ignore all errors.
- (process-file
- vc-hg-program nil t nil
- "--config" "alias.parents=parents"
- "--config" "defaults.parents="
- "parents" "--template" "{rev}" (file-relative-name file)))
- ;; Some problem happened. E.g. We can't find an `hg'
- ;; executable.
- (error nil)))))))
- (if (eq 0 status)
- out
- ;; Check if the file is in the 'added state, the above hg
- ;; command does not distinguish between 'added and 'unregistered.
- (setq status
- (condition-case nil
- (let ((process-environment avoid-local-env))
- (process-file
- vc-hg-program nil nil nil
- ;; We use "log" here, if there's a faster command
- ;; that returns true for an 'added file and false
- ;; for an 'unregistered one, we could use that.
- "log" "-l1" (file-relative-name file)))
- ;; Some problem happened. E.g. We can't find an `hg'
- ;; executable.
- (error nil)))
- (when (eq 0 status) "0"))))
+ (let ((default-directory (if (file-directory-p file)
+ (file-name-as-directory file)
+ (file-name-directory file))))
+ (ignore-errors
+ (with-output-to-string
+ (process-file vc-hg-program nil standard-output nil
+ "log" "-l" "1" "--template" "{rev}"
+ (file-relative-name file))))))
;;; History functions
(nconc
(when start-revision (list (format "-r%s:" start-revision)))
(when limit (list "-l" (format "%s" limit)))
- (when shortlog (list "--style" "compact"))
+ (when shortlog (list "--template" (car vc-hg-root-log-format)))
vc-hg-log-switches)))))
(defvar log-view-message-re)
(defvar log-view-file-re)
(defvar log-view-font-lock-keywords)
(defvar log-view-per-file-logs)
+(defvar log-view-expanded-log-entry-function)
(define-derived-mode vc-hg-log-view-mode log-view-mode "Hg-Log-View"
(require 'add-log) ;; we need the add-log faces
(set (make-local-variable 'log-view-per-file-logs) nil)
(set (make-local-variable 'log-view-message-re)
(if (eq vc-log-view-type 'short)
- "^\\([0-9]+\\)\\(\\[.*\\]\\)? +\\([0-9a-z]\\{12\\}\\) +\\(\\(?:[0-9]+\\)-\\(?:[0-9]+\\)-\\(?:[0-9]+\\) \\(?:[0-9]+\\):\\(?:[0-9]+\\) \\(?:[-+0-9]+\\)\\) +\\(.*\\)$"
+ (cadr vc-hg-root-log-format)
"^changeset:[ \t]*\\([0-9]+\\):\\(.+\\)"))
+ ;; Allow expanding short log entries
+ (when (eq vc-log-view-type 'short)
+ (setq truncate-lines t)
+ (set (make-local-variable 'log-view-expanded-log-entry-function)
+ 'vc-hg-expanded-log-entry))
(set (make-local-variable 'log-view-font-lock-keywords)
(if (eq vc-log-view-type 'short)
- (append `((,log-view-message-re
- (1 'log-view-message-face)
- (2 'highlight nil lax)
- (3 'log-view-message-face)
- (4 'change-log-date)
- (5 'change-log-name))))
- (append
- log-view-font-lock-keywords
- '(
- ;; Handle the case:
- ;; user: FirstName LastName <foo@bar>
- ("^user:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
- (1 'change-log-name)
- (2 'change-log-email))
- ;; Handle the cases:
- ;; user: foo@bar
- ;; and
- ;; user: foo
- ("^user:[ \t]+\\([A-Za-z0-9_.+-]+\\(?:@[A-Za-z0-9_.-]+\\)?\\)"
- (1 'change-log-email))
- ("^date: \\(.+\\)" (1 'change-log-date))
- ("^tag: +\\([^ ]+\\)$" (1 'highlight))
- ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message)))))))
+ (list (cons (nth 1 vc-hg-root-log-format)
+ (nth 2 vc-hg-root-log-format)))
+ (append
+ log-view-font-lock-keywords
+ '(
+ ;; Handle the case:
+ ;; user: FirstName LastName <foo@bar>
+ ("^user:[ \t]+\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.+-]+@[A-Za-z0-9_.-]+\\)[>)]"
+ (1 'change-log-name)
+ (2 'change-log-email))
+ ;; Handle the cases:
+ ;; user: foo@bar
+ ;; and
+ ;; user: foo
+ ("^user:[ \t]+\\([A-Za-z0-9_.+-]+\\(?:@[A-Za-z0-9_.-]+\\)?\\)"
+ (1 'change-log-email))
+ ("^date: \\(.+\\)" (1 'change-log-date))
+ ("^tag: +\\([^ ]+\\)$" (1 'highlight))
+ ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message)))))))
(defun vc-hg-diff (files &optional oldvers newvers buffer)
"Get a difference report using hg between two revisions of FILES."
(list "-r" oldvers "-r" newvers)
(list "-r" oldvers)))))))
+(defun vc-hg-expanded-log-entry (revision)
+ (with-temp-buffer
+ (vc-hg-command t nil nil "log" "-r" revision)
+ (goto-char (point-min))
+ (unless (eobp)
+ ;; Indent the expanded log entry.
+ (indent-region (point-min) (point-max) 2)
+ (goto-char (point-max))
+ (buffer-string))))
+
(defun vc-hg-revision-table (files)
(let ((default-directory (file-name-directory (car files))))
(with-temp-buffer
;; Modeled after the similar function in vc-cvs.el
(defun vc-hg-revision-completion-table (files)
- (lexical-let ((files files)
- table)
- (setq table (lazy-completion-table
- table (lambda () (vc-hg-revision-table files))))
+ (letrec ((table (lazy-completion-table
+ table (lambda () (vc-hg-revision-table files)))))
table))
(defun vc-hg-annotate-command (file buffer &optional revision)
(expand-file-name (match-string-no-properties 4)
(vc-hg-root default-directory)))))))
-(defun vc-hg-previous-revision (file rev)
+(defun vc-hg-previous-revision (_file rev)
(let ((newrev (1- (string-to-number rev))))
(when (>= newrev 0)
(number-to-string newrev))))
-(defun vc-hg-next-revision (file rev)
+(defun vc-hg-next-revision (_file rev)
(let ((newrev (1+ (string-to-number rev)))
(tip-revision
(with-temp-buffer
"Rename file from OLD to NEW using `hg mv'."
(vc-hg-command nil 0 new "mv" old))
-(defun vc-hg-register (files &optional rev comment)
+(defun vc-hg-register (files &optional _rev _comment)
"Register FILES under hg.
REV is ignored.
COMMENT is ignored."
(declare-function log-edit-extract-headers "log-edit" (headers string))
-(defun vc-hg-checkin (files rev comment)
+(defun vc-hg-checkin (files _rev comment)
"Hg-specific version of `vc-backend-checkin'.
REV is ignored."
(apply 'vc-hg-command nil 0 files
(vc-hg-command buffer 0 file "cat"))))
;; Modeled after the similar function in vc-bzr.el
-(defun vc-hg-checkout (file &optional editable rev)
+(defun vc-hg-checkout (file &optional _editable rev)
"Retrieve a revision of FILE.
EDITABLE is ignored.
REV is the revision to check out into WORKFILE."
(defvar log-view-vc-backend)
-(defstruct (vc-hg-extra-fileinfo
+(cl-defstruct (vc-hg-extra-fileinfo
(:copier nil)
(:constructor vc-hg-create-extra-fileinfo (rename-state extra-name))
(:conc-name vc-hg-extra-fileinfo->))
(when extra
(insert (propertize
(format " (%s %s)"
- (case (vc-hg-extra-fileinfo->rename-state extra)
- ('copied "copied from")
- ('renamed-from "renamed from")
- ('renamed-to "renamed to"))
+ (pcase (vc-hg-extra-fileinfo->rename-state extra)
+ (`copied "copied from")
+ (`renamed-from "renamed from")
+ (`renamed-to "renamed to"))
(vc-hg-extra-fileinfo->extra-name extra))
'face 'font-lock-comment-face)))))
(defun vc-hg-after-dir-status (update-function)
- (let ((status-char nil)
- (file nil)
+ (let ((file nil)
(translation '((?= . up-to-date)
(?C . up-to-date)
(?A . added)
(vc-exec-after
`(vc-hg-after-dir-status (quote ,update-function))))
-(defun vc-hg-dir-status-files (dir files default-state update-function)
+(defun vc-hg-dir-status-files (dir files _default-state update-function)
(apply 'vc-hg-command (current-buffer) 'async dir "status" "-C" files)
(vc-exec-after
`(vc-hg-after-dir-status (quote ,update-function))))
(mapcar (lambda (arg) (list "-r" arg)) marked-list)))
(error "No log entries selected for push"))))
-(defun vc-hg-pull ()
- (interactive)
- (let ((marked-list (log-view-get-marked)))
- (if marked-list
- (apply #'vc-hg-command
- nil 0 nil
- "pull"
- (apply 'nconc
- (mapcar (lambda (arg) (list "-r" arg)) marked-list)))
- (error "No log entries selected for pull"))))
+(defvar vc-hg-error-regexp-alist nil
+ ;; 'hg pull' does not list modified files, so, for now, the only
+ ;; benefit of `vc-compilation-mode' is that one can get rid of
+ ;; *vc-hg* buffer with 'q' or 'z'.
+ ;; TODO: call 'hg incoming' before pull/merge to get the list of
+ ;; modified files
+ "Value of `compilation-error-regexp-alist' in *vc-hg* buffers.")
+
+(defun vc-hg-pull (prompt)
+ "Issue a Mercurial pull command.
+If called interactively with a set of marked Log View buffers,
+call \"hg pull -r REVS\" to pull in the specified revisions REVS.
+
+With a prefix argument or if PROMPT is non-nil, prompt for a
+specific Mercurial pull command. The default is \"hg pull -u\",
+which fetches changesets from the default remote repository and
+then attempts to update the working directory."
+ (interactive "P")
+ (let (marked-list)
+ ;; The `vc-hg-pull' command existed before the `pull' VC action
+ ;; was implemented. Keep it for backward compatibility.
+ (if (and (called-interactively-p 'interactive)
+ (setq marked-list (log-view-get-marked)))
+ (apply #'vc-hg-command
+ nil 0 nil
+ "pull"
+ (apply 'nconc
+ (mapcar (lambda (arg) (list "-r" arg))
+ marked-list)))
+ (let* ((root (vc-hg-root default-directory))
+ (buffer (format "*vc-hg : %s*" (expand-file-name root)))
+ (command "pull")
+ (hg-program vc-hg-program)
+ ;; Fixme: before updating the working copy to the latest
+ ;; state, should check if it's visiting an old revision.
+ (args '("-u")))
+ ;; If necessary, prompt for the exact command.
+ (when prompt
+ (setq args (split-string
+ (read-shell-command "Run Hg (like this): "
+ (format "%s pull -u" hg-program)
+ 'vc-hg-history)
+ " " t))
+ (setq hg-program (car args)
+ command (cadr args)
+ args (cddr args)))
+ (apply 'vc-do-async-command buffer root hg-program
+ command args)
+ (with-current-buffer buffer (vc-exec-after '(vc-compilation-mode 'hg)))
+ (vc-set-async-update buffer)))))
+
+(defun vc-hg-merge-branch ()
+ "Merge incoming changes into the current working directory.
+This runs the command \"hg merge\"."
+ (let* ((root (vc-hg-root default-directory))
+ (buffer (format "*vc-hg : %s*" (expand-file-name root))))
+ (apply 'vc-do-async-command buffer root vc-hg-program '("merge"))
+ (with-current-buffer buffer (vc-exec-after '(vc-compilation-mode 'hg)))
+ (vc-set-async-update buffer)))
;;; Internal functions
(defun vc-hg-command (buffer okstatus file-or-list &rest flags)
"A wrapper around `vc-do-command' for use in vc-hg.el.
-The difference to vc-do-command is that this function always invokes `hg',
-and that it passes `vc-hg-global-switches' to it before FLAGS."
+This function differs from vc-do-command in that it invokes
+`vc-hg-program', and passes `vc-hg-global-switches' to it before FLAGS."
(apply 'vc-do-command (or buffer "*vc*") okstatus vc-hg-program file-or-list
(if (stringp vc-hg-global-switches)
(cons vc-hg-global-switches flags)
(provide 'vc-hg)
-;; arch-tag: bd094dc5-715a-434f-a331-37b9fb7cd954
;;; vc-hg.el ends here