;;; vc-hg.el --- VC backend for the mercurial version control system
-;; Copyright (C) 2006, 2007, 2008 Free Software Foundation, Inc.
+;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
;; Author: Ivan Kanis
;; Keywords: tools
;; - state-heuristic (file) NOT NEEDED
;; - dir-status (dir update-function) OK
;; - dir-status-files (dir files ds uf) OK
-;; - dir-extra-headers (dir) OK
-;; - dir-printer (fileinfo) OK
+;; - dir-extra-headers (dir) OK
+;; - dir-printer (fileinfo) OK
;; * working-revision (file) OK
;; - latest-on-branch-p (file) ??
;; * checkout-model (files) OK
;; - merge-news (file) NEEDED
;; - steal-lock (file &optional revision) NOT NEEDED
;; HISTORY FUNCTIONS
-;; * print-log (files &optional buffer) OK
+;; * print-log (files buffer &optional shortlog start-revision limit) OK
;; - log-view-mode () OK
;; - show-log-entry (revision) NOT NEEDED, DEFAULT IS GOOD
;; - comment-history (file) NOT NEEDED
;; - annotate-current-time () NOT NEEDED
;; - annotate-extract-revision-at-line () OK
;; TAG SYSTEM
-;; - create-tag (dir name branchp) NEEDED
-;; - retrieve-tag (dir name update) NEEDED
+;; - create-tag (dir name branchp) NEEDED
+;; - retrieve-tag (dir name update) NEEDED
;; MISCELLANEOUS
;; - make-version-backups-p (file) ??
;; - repository-hostname (dirname) ??
;; - delete-file (file) TEST IT
;; - rename-file (old new) OK
;; - find-file-hook () PROBABLY NOT NEEDED
-;; - find-file-not-found-hook () PROBABLY NOT NEEDED
;; 2) Implement Stefan Monnier's advice:
;; vc-hg-registered and vc-hg-state
"String or list of strings specifying switches for Hg diff under VC.
If nil, use the value of `vc-diff-switches'. If t, use no switches."
:type '(choice (const :tag "Unspecified" nil)
- (const :tag "None" t)
- (string :tag "Argument String")
- (repeat :tag "Argument List" :value ("") string))
+ (const :tag "None" t)
+ (string :tag "Argument String")
+ (repeat :tag "Argument List" :value ("") string))
:version "23.1"
:group 'vc)
"Hg-specific version of `vc-state'."
(let*
((status nil)
+ (default-directory (file-name-directory file))
(out
- (with-output-to-string
- (with-current-buffer
- standard-output
- (setq status
- (condition-case nil
- ;; Ignore all errors.
- (call-process
- "hg" nil t nil "--cwd" (file-name-directory file)
- "status" "-A" (file-name-nondirectory file))
- ;; Some problem happened. E.g. We can't find an `hg'
- ;; executable.
- (error nil)))))))
+ (with-output-to-string
+ (with-current-buffer
+ standard-output
+ (setq status
+ (condition-case nil
+ ;; Ignore all errors.
+ (let ((process-environment
+ ;; Avoid localization of messages so we can parse the output.
+ (append (list "TERM=dumb" "LANGUAGE=C" "HGRC=") process-environment)))
+
+ (process-file
+ "hg" nil t nil
+ "status" "-A" (file-relative-name file)))
+ ;; Some problem happened. E.g. We can't find an `hg'
+ ;; executable.
+ (error nil)))))))
(when (eq 0 status)
- (when (null (string-match ".*: No such file or directory$" out))
- (let ((state (aref out 0)))
- (cond
- ((eq state ?=) 'up-to-date)
- ((eq state ?A) 'added)
- ((eq state ?M) 'edited)
- ((eq state ?I) 'ignored)
- ((eq state ?R) 'removed)
- ((eq state ?!) 'missing)
- ((eq state ??) 'unregistered)
- ((eq state ?C) 'up-to-date) ;; Older mercurials use this
- (t 'up-to-date)))))))
+ (when (null (string-match ".*: No such file or directory$" out))
+ (let ((state (aref out 0)))
+ (cond
+ ((eq state ?=) 'up-to-date)
+ ((eq state ?A) 'added)
+ ((eq state ?M) 'edited)
+ ((eq state ?I) 'ignored)
+ ((eq state ?R) 'removed)
+ ((eq state ?!) 'missing)
+ ((eq state ??) 'unregistered)
+ ((eq state ?C) 'up-to-date) ;; Older mercurials 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))
(out
- (with-output-to-string
- (with-current-buffer
- standard-output
- (setq status
- (condition-case nil
- ;; Ignore all errors.
- (call-process
- "hg" nil t nil "--cwd" (file-name-directory file)
- "log" "-l1" (file-name-nondirectory file))
- ;; Some problem happened. E.g. We can't find an `hg'
- ;; executable.
- (error nil)))))))
- (when (eq 0 status)
- (if (string-match "changeset: *\\([0-9]*\\)" out)
- (match-string 1 out)
- "0"))))
+ (with-output-to-string
+ (with-current-buffer
+ standard-output
+ (setq status
+ (condition-case nil
+ (let ((process-environment
+ ;; Avoid localization of messages so we can parse the output.
+ (append (list "TERM=dumb" "LANGUAGE=C" "HGRC=")
+ process-environment)))
+ ;; Ignore all errors.
+ (process-file
+ "hg" nil t nil
+ "parent" "--template" "{rev}" (file-relative-name file)))
+ ;; Some problem happened. E.g. We can't find an `hg'
+ ;; executable.
+ (error nil)))))))
+ (when (eq 0 status) out)))
;;; History functions
-(defun vc-hg-print-log (files &optional buffer)
- "Get change log associated with FILES."
- ;; `log-view-mode' needs to have the file names in order to function
- ;; correctly. "hg log" does not print it, so we insert it here by
- ;; hand.
+(defcustom vc-hg-log-switches nil
+ "String or list of strings specifying switches for hg log under VC."
+ :type '(choice (const :tag "None" nil)
+ (string :tag "Argument String")
+ (repeat :tag "Argument List" :value ("") string))
+ :group 'vc-hg)
+(defun vc-hg-print-log (files buffer &optional shortlog start-revision limit)
+ "Get change log associated with FILES."
;; `vc-do-command' creates the buffer, but we need it before running
;; the command.
(vc-setup-buffer buffer)
(let ((inhibit-read-only t))
(with-current-buffer
buffer
- (vc-hg-command buffer 0 files "log"))))
+ (apply 'vc-hg-command buffer 0 files "log"
+ (append
+ (when start-revision (list (format "-r%s:" start-revision)))
+ (when limit (list "-l" (format "%s" limit)))
+ (when shortlog '("--style" "compact"))
+ 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 vc-short-log)
(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-file-re) "\\`a\\`")
(set (make-local-variable 'log-view-per-file-logs) nil)
(set (make-local-variable 'log-view-message-re)
- "^changeset:[ \t]*\\([0-9]+\\):\\(.+\\)")
+ (if vc-short-log
+ "^\\([0-9]+\\)\\(?:\\[.*\\]\\)? +\\([0-9a-z]\\{12\\}\\) +\\(\\(?:[0-9]+\\)-\\(?:[0-9]+\\)-\\(?:[0-9]+\\) \\(?:[0-9]+\\):\\(?:[0-9]+\\) \\(?:[-+0-9]+\\)\\) +\\(.*\\)$"
+ "^changeset:[ \t]*\\([0-9]+\\):\\(.+\\)"))
(set (make-local-variable 'log-view-font-lock-keywords)
+ (if vc-short-log
+ (append `((,log-view-message-re
+ (1 'log-view-message-face)
+ (2 'log-view-message-face)
+ (3 'change-log-date)
+ (4 '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))
- ("^summary:[ \t]+\\(.+\\)" (1 'log-view-message))))))
+ 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))
+ ("^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."
(let* ((firstfile (car files))
- (working (and firstfile (vc-working-revision firstfile))))
+ (working (and firstfile (vc-working-revision firstfile))))
(when (and (equal oldvers working) (not newvers))
(setq oldvers nil))
(when (and (not oldvers) newvers)
(setq oldvers working))
- (apply #'vc-hg-command (or buffer "*vc-diff*") nil
- (mapcar (lambda (file) (file-name-nondirectory file)) files)
- "--cwd" (or (when firstfile (file-name-directory firstfile))
- (expand-file-name default-directory))
- "diff"
- (append
- (vc-switches 'hg 'diff)
- (when oldvers
- (if newvers
- (list "-r" oldvers "-r" newvers)
- (list "-r" oldvers)))))))
+ (apply #'vc-hg-command (or buffer "*vc-diff*") nil files "diff"
+ (append
+ (vc-switches 'hg 'diff)
+ (when oldvers
+ (if newvers
+ (list "-r" oldvers "-r" newvers)
+ (list "-r" oldvers)))))))
(defun vc-hg-revision-table (files)
(let ((default-directory (file-name-directory (car files))))
(defun vc-hg-annotate-command (file buffer &optional revision)
"Execute \"hg annotate\" on FILE, inserting the contents in BUFFER.
Optional arg REVISION is a revision to annotate from."
- (vc-hg-command buffer 0 file "annotate" "-d" "-n"
- (when revision (concat "-r" revision)))
- (with-current-buffer buffer
- (goto-char (point-min))
- (re-search-forward "^[ \t]*[0-9]")
- (delete-region (point-min) (match-beginning 0))))
+ (vc-hg-command buffer 0 file "annotate" "-d" "-n" "--follow"
+ (when revision (concat "-r" revision))))
(declare-function vc-annotate-convert-time "vc-annotate" (time))
;;215 Wed Jun 20 21:22:58 2007 -0700 foo.c: CONTENTS
;; i.e. VERSION_NUMBER DATE FILENAME: CONTENTS
(defconst vc-hg-annotate-re
- "^[ \t]*\\([0-9]+\\) \\(.\\{30\\}\\)[^:\n]*\\(:[^ \n][^:\n]*\\)*: ")
+ "^[ \t]*\\([0-9]+\\) \\(.\\{30\\}\\)\\(?:\\(: \\)\\|\\(?: +\\(.+\\): \\)\\)")
(defun vc-hg-annotate-time ()
(when (looking-at vc-hg-annotate-re)
(defun vc-hg-annotate-extract-revision-at-line ()
(save-excursion
(beginning-of-line)
- (when (looking-at vc-hg-annotate-re) (match-string-no-properties 1))))
+ (when (looking-at vc-hg-annotate-re)
+ (if (match-beginning 3)
+ (match-string-no-properties 1)
+ (cons (match-string-no-properties 1)
+ (expand-file-name (match-string-no-properties 4)))))))
(defun vc-hg-previous-revision (file rev)
(let ((newrev (1- (string-to-number rev))))
(defun vc-hg-next-revision (file rev)
(let ((newrev (1+ (string-to-number rev)))
- (tip-revision
- (with-temp-buffer
- (vc-hg-command t 0 nil "tip")
- (goto-char (point-min))
- (re-search-forward "^changeset:[ \t]*\\([0-9]+\\):")
- (string-to-number (match-string-no-properties 1)))))
+ (tip-revision
+ (with-temp-buffer
+ (vc-hg-command t 0 nil "tip")
+ (goto-char (point-min))
+ (re-search-forward "^changeset:[ \t]*\\([0-9]+\\):")
+ (string-to-number (match-string-no-properties 1)))))
;; We don't want to exceed the maximum possible revision number, ie
;; the tip revision.
(when (<= newrev tip-revision)
(let ((coding-system-for-read 'binary)
(coding-system-for-write 'binary))
(if rev
- (vc-hg-command buffer 0 file "cat" "-r" rev)
+ (vc-hg-command buffer 0 file "cat" "-r" rev)
(vc-hg-command buffer 0 file "cat"))))
;; Modeled after the similar function in vc-bzr.el
(defun vc-hg-extra-status-menu () vc-hg-extra-menu-map)
-(define-derived-mode vc-hg-outgoing-mode vc-hg-log-view-mode "Hg-Outgoing")
+(defvar log-view-vc-backend)
+
+(define-derived-mode vc-hg-outgoing-mode vc-hg-log-view-mode "Hg-Outgoing"
+ "Mode for browsing Hg outgoing changes."
+ (set (make-local-variable 'log-view-vc-backend) 'Hg))
-(define-derived-mode vc-hg-incoming-mode vc-hg-log-view-mode "Hg-Incoming")
+(define-derived-mode vc-hg-incoming-mode vc-hg-log-view-mode "Hg-Incoming"
+ "Mode for browsing Hg incoming changes."
+ (set (make-local-variable 'log-view-vc-backend) 'Hg))
(defstruct (vc-hg-extra-fileinfo
(:copier nil)
(vc-default-dir-printer 'Hg info)
(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"))
- (vc-hg-extra-fileinfo->extra-name extra))
- 'face 'font-lock-comment-face)))))
+ (format " (%s %s)"
+ (case (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)
- (translation '((?= . up-to-date)
- (?C . up-to-date)
- (?A . added)
- (?R . removed)
- (?M . edited)
- (?I . ignored)
- (?! . missing)
- (? . copy-rename-line)
- (?? . unregistered)))
- (translated nil)
- (result nil)
- (last-added nil)
- (last-line-copy nil))
+ (file nil)
+ (translation '((?= . up-to-date)
+ (?C . up-to-date)
+ (?A . added)
+ (?R . removed)
+ (?M . edited)
+ (?I . ignored)
+ (?! . missing)
+ (? . copy-rename-line)
+ (?? . unregistered)))
+ (translated nil)
+ (result nil)
+ (last-added nil)
+ (last-line-copy nil))
(goto-char (point-min))
(while (not (eobp))
- (setq translated (cdr (assoc (char-after) translation)))
- (setq file
- (buffer-substring-no-properties (+ (point) 2)
- (line-end-position)))
- (cond ((not translated)
- (setq last-line-copy nil))
- ((eq translated 'up-to-date)
- (setq last-line-copy nil))
- ((eq translated 'copy-rename-line)
- ;; For copied files the output looks like this:
- ;; A COPIED_FILE_NAME
- ;; ORIGINAL_FILE_NAME
- (setf (nth 2 last-added)
- (vc-hg-create-extra-fileinfo 'copied file))
- (setq last-line-copy t))
- ((and last-line-copy (eq translated 'removed))
- ;; For renamed files the output looks like this:
- ;; A NEW_FILE_NAME
- ;; ORIGINAL_FILE_NAME
- ;; R ORIGINAL_FILE_NAME
- ;; We need to adjust the previous entry to not think it is a copy.
- (setf (vc-hg-extra-fileinfo->rename-state (nth 2 last-added))
- 'renamed-from)
- (push (list file translated
- (vc-hg-create-extra-fileinfo
- 'renamed-to (nth 0 last-added))) result)
- (setq last-line-copy nil))
- (t
- (setq last-added (list file translated nil))
- (push last-added result)
- (setq last-line-copy nil)))
- (forward-line))
+ (setq translated (cdr (assoc (char-after) translation)))
+ (setq file
+ (buffer-substring-no-properties (+ (point) 2)
+ (line-end-position)))
+ (cond ((not translated)
+ (setq last-line-copy nil))
+ ((eq translated 'up-to-date)
+ (setq last-line-copy nil))
+ ((eq translated 'copy-rename-line)
+ ;; For copied files the output looks like this:
+ ;; A COPIED_FILE_NAME
+ ;; ORIGINAL_FILE_NAME
+ (setf (nth 2 last-added)
+ (vc-hg-create-extra-fileinfo 'copied file))
+ (setq last-line-copy t))
+ ((and last-line-copy (eq translated 'removed))
+ ;; For renamed files the output looks like this:
+ ;; A NEW_FILE_NAME
+ ;; ORIGINAL_FILE_NAME
+ ;; R ORIGINAL_FILE_NAME
+ ;; We need to adjust the previous entry to not think it is a copy.
+ (setf (vc-hg-extra-fileinfo->rename-state (nth 2 last-added))
+ 'renamed-from)
+ (push (list file translated
+ (vc-hg-create-extra-fileinfo
+ 'renamed-to (nth 0 last-added))) result)
+ (setq last-line-copy nil))
+ (t
+ (setq last-added (list file translated nil))
+ (push last-added result)
+ (setq last-line-copy nil)))
+ (forward-line))
(funcall update-function result)))
(defun vc-hg-dir-status (dir update-function)
(defun vc-hg-outgoing ()
(interactive)
- (let ((bname "*Hg outgoing*"))
- (vc-hg-command bname 0 nil "outgoing" "-n")
+ (let ((bname "*Hg outgoing*")
+ (vc-short-log nil))
+ (vc-hg-command bname 1 nil "outgoing" "-n")
(pop-to-buffer bname)
(vc-hg-outgoing-mode)))
(defun vc-hg-incoming ()
(interactive)
- (let ((bname "*Hg incoming*"))
+ (let ((bname "*Hg incoming*")
+ (vc-short-log nil))
(vc-hg-command bname 0 nil "incoming" "-n")
(pop-to-buffer bname)
(vc-hg-incoming-mode)))
(interactive)
(let ((marked-list (log-view-get-marked)))
(if marked-list
- (vc-hg-command
- nil 0 nil
- (cons "push"
- (apply 'nconc
- (mapcar (lambda (arg) (list "-r" arg)) marked-list))))
- (error "No log entries selected for push"))))
+ (vc-hg-command
+ nil 0 nil
+ (cons "push"
+ (apply 'nconc
+ (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
- (vc-hg-command
- nil 0 nil
- (cons "pull"
- (apply 'nconc
- (mapcar (lambda (arg) (list "-r" arg)) marked-list))))
+ (vc-hg-command
+ nil 0 nil
+ (cons "pull"
+ (apply 'nconc
+ (mapcar (lambda (arg) (list "-r" arg)) marked-list))))
(error "No log entries selected for pull"))))
;;; Internal functions