* vc/diff-mode.el (diff-remove-trailing-whitespace): New function.
[bpt/emacs.git] / lisp / vc / vc-hg.el
index 689cd4d..727fb08 100644 (file)
@@ -1,8 +1,9 @@
-;;; 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.
@@ -132,13 +138,37 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
                  (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-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
 
@@ -174,7 +204,7 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
                             (append (list "TERM=dumb" "LANGUAGE=C")
                                     process-environment)))
                        (process-file
-                        "hg" nil t nil
+                        vc-hg-program nil t nil
                         "--config" "alias.status=status"
                         "--config" "defaults.status="
                         "status" "-A" (file-relative-name file)))
@@ -192,50 +222,19 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
              ((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
-                        "hg" 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
-                  "hg" 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
 
@@ -260,13 +259,14 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
             (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
@@ -274,33 +274,34 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
   (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."
@@ -318,6 +319,16 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
                   (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
@@ -327,10 +338,8 @@ If nil, use the value of `vc-diff-switches'.  If t, use no switches."
 
 ;; 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)
@@ -366,12 +375,12 @@ Optional arg REVISION is a revision to annotate from."
              (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
@@ -397,7 +406,7 @@ Optional arg REVISION is a revision to annotate from."
   "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."
@@ -427,7 +436,7 @@ 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
@@ -444,7 +453,7 @@ REV is ignored."
       (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."
@@ -476,7 +485,7 @@ 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->))
@@ -492,16 +501,15 @@ REV is the revision to check out into WORKFILE."
     (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)
@@ -556,7 +564,7 @@ REV is the revision to check out into WORKFILE."
   (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))))
@@ -603,24 +611,73 @@ REV is the revision to check out into WORKFILE."
                       (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."
-  (apply 'vc-do-command (or buffer "*vc*") okstatus "hg" file-or-list
+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)
            (append vc-hg-global-switches
@@ -631,5 +688,4 @@ and that it passes `vc-hg-global-switches' to it before FLAGS."
 
 (provide 'vc-hg)
 
-;; arch-tag: bd094dc5-715a-434f-a331-37b9fb7cd954
 ;;; vc-hg.el ends here