* vc.el (vc-status-add-entry): Assume ENTRY is a list not a cons.
[bpt/emacs.git] / lisp / vc-bzr.el
index 801e194..7425b58 100644 (file)
@@ -1,11 +1,11 @@
 ;;; vc-bzr.el --- VC backend for the bzr revision control system
 
-;; Copyright (C) 2006, 2007  Free Software Foundation, Inc.
+;; Copyright (C) 2006, 2007, 2008  Free Software Foundation, Inc.
 
 ;; Author: Dave Love <fx@gnu.org>, Riccardo Murri <riccardo.murri@gmail.com>
 ;; Keywords: tools
 ;; Created: Sept 2006
-;; Version: 2007-09-05
+;; Version: 2008-01-04 (Bzr revno 25)
 ;; URL: http://launchpad.net/vc-bzr
 
 ;; This file is free software; you can redistribute it and/or modify
 
 ;;; Commentary:
 
-;; See <URL:http://bazaar-vcs.org/> concerning bzr.
-
-;; Load this library to register bzr support in VC.  It covers basic VC 
-;; functionality, but was only lightly exercised with a few Emacs/bzr
-;; version combinations, namely those current on the authors' PCs.
-;; See various Fixmes below.
+;; See <URL:http://bazaar-vcs.org/> concerning bzr.  See
+;; <URL:http://launchpad.net/vc-bzr> for alternate development
+;; branches of `vc-bzr'.
 
+;; Load this library to register bzr support in VC.  
 
 ;; Known bugs
 ;; ==========
   :group 'vc-bzr
   :type 'string)
 
-;; Fixme: there's probably no call for this.
-(defcustom vc-bzr-program-args nil
-  "List of global arguments to pass to `vc-bzr-program'."
-  :group 'vc-bzr
-  :type '(repeat string))
-
 (defcustom vc-bzr-diff-switches nil
   "String/list of strings specifying extra switches for bzr diff under VC."
   :type '(choice (const :tag "None" nil)
                  (repeat :tag "Argument List" :value ("") string))
   :group 'vc-bzr)
 
+(defcustom vc-bzr-log-switches nil
+  "String/list of strings specifying extra switches for `bzr log' under VC."
+  :type '(choice (const :tag "None" nil)
+                 (string :tag "Argument String")
+                 (repeat :tag "Argument List" :value ("") string))
+  :group 'vc-bzr)
+
 ;; since v0.9, bzr supports removing the progress indicators
 ;; by setting environment variable BZR_PROGRESS_BAR to "none".
 (defun vc-bzr-command (bzr-command buffer okstatus file-or-list &rest args)
   "Wrapper round `vc-do-command' using `vc-bzr-program' as COMMAND.
-Invoke the bzr command adding `BZR_PROGRESS_BAR=none' to the environment."
+Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and
+`LC_MESSAGES=C' to the environment."
   (let ((process-environment
          (list* "BZR_PROGRESS_BAR=none" ; Suppress progress output (bzr >=0.9)
-                "LC_ALL=C"              ; Force English output
+                "LC_MESSAGES=C"         ; Force English output
                 process-environment)))
     (apply 'vc-do-command buffer okstatus vc-bzr-program
-           file-or-list bzr-command (append vc-bzr-program-args args))))
+           file-or-list bzr-command args)))
 
 
 ;;;###autoload
-(defconst vc-bzr-admin-dirname ".bzr"    ; FIXME: "_bzr" on w32?
+(defconst vc-bzr-admin-dirname ".bzr"
   "Name of the directory containing Bzr repository status files.")
 ;;;###autoload
 (defconst vc-bzr-admin-checkout-format-file
@@ -118,21 +118,34 @@ Invoke the bzr command adding `BZR_PROGRESS_BAR=none' to the environment."
   "Return the root directory of the bzr repository containing FILE."
   ;; Cache technique copied from vc-arch.el.
   (or (vc-file-getprop file 'bzr-root)
-      (vc-file-setprop
-       file 'bzr-root
-       (vc-find-root file vc-bzr-admin-checkout-format-file))))
+      (let ((root (vc-find-root file vc-bzr-admin-checkout-format-file)))
+       (when root (vc-file-setprop file 'bzr-root root)))))
 
-(defun vc-bzr-registered (file)
-  "Return non-nil if FILE is registered with bzr.
-
-For speed, this function tries first to parse Bzr internal file
-`checkout/dirstate', but it may fail if Bzr internal file format
-has changed.  As a safeguard, the `checkout/dirstate' file is
-only parsed if it contains the string `#bazaar dirstate flat
-format 3' in the first line.
+(require 'sha1)                         ;For sha1-program
 
-If the `checkout/dirstate' file cannot be parsed, fall back to
-running `vc-bzr-state'."
+(defun vc-bzr-sha1 (file)
+  (with-temp-buffer
+    (set-buffer-multibyte nil)
+    (let ((prog sha1-program)
+          (args nil))
+      (when (consp prog)
+       (setq args (cdr prog))
+        (setq prog (car prog)))
+      (apply 'call-process prog file t nil args)
+      (buffer-substring (point-min) (+ (point-min) 40)))))
+
+(defun vc-bzr-state-heuristic (file)
+  "Like `vc-bzr-state' but hopefully without running Bzr."
+  ;; `bzr status' is excrutiatingly slow with large histories and
+  ;; pending merges, so try to avoid using it until they fix their
+  ;; performance problems.
+  ;; This function tries first to parse Bzr internal file
+  ;; `checkout/dirstate', but it may fail if Bzr internal file format
+  ;; has changed.  As a safeguard, the `checkout/dirstate' file is
+  ;; only parsed if it contains the string `#bazaar dirstate flat
+  ;; format 3' in the first line.
+  ;; If the `checkout/dirstate' file cannot be parsed, fall back to
+  ;; running `vc-bzr-state'."
   (lexical-let ((root (vc-bzr-root file)))
     (when root    ; Short cut.
       ;; This looks at internal files.  May break if they change
@@ -147,13 +160,46 @@ running `vc-bzr-state'."
                 (vc-bzr-state file)     ; Some other unknown format?
               (let* ((relfile (file-relative-name file root))
                      (reldir (file-name-directory relfile)))
-                (re-search-forward
-                 (concat "^\0"
-                         (if reldir (regexp-quote (directory-file-name reldir)))
-                         "\0"
-                         (regexp-quote (file-name-nondirectory relfile))
-                         "\0")
-                 nil t)))))))))
+                (if (re-search-forward
+                     (concat "^\0"
+                             (if reldir (regexp-quote
+                                         (directory-file-name reldir)))
+                             "\0"
+                             (regexp-quote (file-name-nondirectory relfile))
+                             "\0"
+                             "[^\0]*\0"       ;id?
+                             "\\([^\0]*\\)\0" ;"a/f/d", a=removed?
+                             "[^\0]*\0"       ;sha1 (empty if conflicted)?
+                             "\\([^\0]*\\)\0" ;size?
+                             "[^\0]*\0"       ;"y/n", executable?
+                             "[^\0]*\0"       ;?
+                             "\\([^\0]*\\)\0" ;"a/f/d" a=added?
+                             "\\([^\0]*\\)\0" ;sha1 again?
+                             "[^\0]*\0"       ;size again?
+                             "[^\0]*\0"       ;"y/n", executable again?
+                             "[^\0]*\0"       ;last revid?
+                             ;; There are more fields when merges are pending.
+                             )
+                     nil t)
+                    ;; Apparently the second sha1 is the one we want: when
+                    ;; there's a conflict, the first sha1 is absent (and the
+                    ;; first size seems to correspond to the file with
+                    ;; conflict markers).
+                    (cond
+                     ((eq (char-after (match-beginning 1)) ?a) 'removed)
+                     ((eq (char-after (match-beginning 3)) ?a) 'added)
+                     ((and (eq (string-to-number (match-string 2))
+                               (nth 7 (file-attributes file)))
+                           (equal (match-string 4)
+                                  (vc-bzr-sha1 file)))
+                      'up-to-date)
+                     (t 'edited))
+                  'unregistered)))))))))
+
+(defun vc-bzr-registered (file)
+  "Return non-nil if FILE is registered with bzr."
+  (let ((state (vc-bzr-state-heuristic file)))
+    (not (memq state '(nil unregistered ignored)))))
 
 (defconst vc-bzr-state-words
   "added\\|ignored\\|kind changed\\|modified\\|removed\\|renamed\\|unknown"
@@ -163,12 +209,10 @@ running `vc-bzr-state'."
   "Return file name FILENAME stripped of the initial Bzr repository path."
   (lexical-let*
       ((filename* (expand-file-name filename))
-       (rootdir (vc-bzr-root (file-name-directory filename*))))
+       (rootdir (vc-bzr-root filename*)))
     (when rootdir 
          (file-relative-name filename* rootdir))))
 
-;; FIXME:  Also get this in a non-registered sub-directory.
-;; It already works for me. -- Riccardo
 (defun vc-bzr-status (file)
   "Return FILE status according to Bzr.
 Return value is a cons (STATUS . WARNING), where WARNING is a
@@ -197,16 +241,16 @@ If any error occurred in running `bzr status', then return nil."
                  ;; bzr prints paths relative to the repository root.
                  (concat "^\\(" vc-bzr-state-words "\\):[ \t\n]+"
                          (regexp-quote (vc-bzr-file-name-relative file))
-                         (if (file-directory-p file) "/?" "")
+                         ;; Bzr appends a '/' to directory names and
+                         ;; '*' to executable files
+                         (if (file-directory-p file) "/?" "\\*?")
                          "[ \t\n]*$")
                  nil t)
             (lexical-let ((statusword (match-string 1)))
               ;; Erase the status text that matched.
               (delete-region (match-beginning 0) (match-end 0))
               (setq status
-                    (and (equal ret 0) ; Seems redundant.  --Stef
-                         (intern (replace-regexp-in-string " " ""
-                                                         statusword))))))
+                    (intern (replace-regexp-in-string " " "" statusword)))))
           (when status
             (goto-char (point-min))
             (skip-chars-forward " \n\t") ;Throw away spaces.
@@ -226,19 +270,48 @@ If any error occurred in running `bzr status', then return nil."
       (if (cdr result)
           (message "Warnings in `bzr' output: %s" (cdr result)))
       (cdr (assq (car result)
-                 '((added . edited)
+                 '((added . added)
                    (kindchanged . edited)
                    (renamed . edited)
                    (modified . edited)
-                   (removed . edited)
-                   (ignored . nil)
-                   (unknown . nil)
+                   (removed . removed)
+                   (ignored . unregistered)
+                   (unknown . unregistered)
                    (unchanged . up-to-date)))))))
 
+(defun vc-bzr-resolve-when-done ()
+  "Call \"bzr resolve\" if the conflict markers have been removed."
+  (save-excursion
+    (goto-char (point-min))
+    (unless (re-search-forward "^<<<<<<< " nil t)
+      (vc-bzr-command "resolve" nil 0 buffer-file-name)
+      ;; Remove the hook so that it is not called multiple times.
+      (remove-hook 'after-save-hook 'vc-bzr-resolve-when-done t))))
+
+(defun vc-bzr-find-file-hook ()
+  (when (and buffer-file-name
+             ;; FIXME: We should check that "bzr status" says "conflict".
+             (file-exists-p (concat buffer-file-name ".BASE"))
+             (file-exists-p (concat buffer-file-name ".OTHER"))
+             (file-exists-p (concat buffer-file-name ".THIS"))
+             ;; If "bzr status" says there's a conflict but there are no
+             ;; conflict markers, it's not clear what we should do.
+             (save-excursion
+               (goto-char (point-min))
+               (re-search-forward "^<<<<<<< " nil t)))
+    ;; TODO: the merge algorithm used in `bzr merge' is nicely configurable,
+    ;; but the one in `bzr pull' isn't, so it would be good to provide an
+    ;; elisp function to remerge from the .BASE/OTHER/THIS files.
+    (smerge-start-session)
+    (add-hook 'after-save-hook 'vc-bzr-resolve-when-done nil t)
+    (message "There are unresolved conflicts in this file")))
+
 (defun vc-bzr-workfile-unchanged-p (file)
   (eq 'unchanged (car (vc-bzr-status file))))
 
 (defun vc-bzr-working-revision (file)
+  ;; Together with the code in vc-state-heuristic, this makes it possible
+  ;; to get the initial VC state of a Bzr file even if Bzr is not installed.
   (lexical-let*
       ((rootdir (vc-bzr-root file))
        (branch-format-file (expand-file-name vc-bzr-admin-branch-format-file
@@ -280,6 +353,10 @@ If any error occurred in running `bzr status', then return nil."
   "Create a new Bzr repository."
   (vc-bzr-command "init" nil 0 nil))
 
+(defun vc-bzr-init-revision (&optional file)
+  "Always return nil, as Bzr cannot register explicit versions."
+  nil)
+
 (defun vc-bzr-register (files &optional rev comment)
   "Register FILE under bzr.
 Signal an error unless REV is nil.
@@ -308,7 +385,7 @@ or a superior directory.")
 
 (defun vc-bzr-unregister (file)
   "Unregister FILE from bzr."
-  (vc-bzr-command "remove" nil 0 file))
+  (vc-bzr-command "remove" nil 0 file "--keep"))
 
 (defun vc-bzr-checkin (files rev comment)
   "Check FILE in to bzr with log message COMMENT.
@@ -316,6 +393,13 @@ REV non-nil gets an error."
   (if rev (error "Can't check in a specific revision with bzr"))
   (vc-bzr-command "commit" nil 0 files "-m" comment))
 
+(defun vc-bzr-find-version (file rev buffer)
+  "Fetch version REV of file FILE and put it into BUFFER."
+    (with-current-buffer buffer
+      (if (and rev (stringp rev) (not (string= rev "")))
+          (vc-bzr-command "cat" t 0 file "-r" rev)
+        (vc-bzr-command "cat" t 0 file))))
+
 (defun vc-bzr-checkout (file &optional editable rev destfile)
   "Checkout revision REV of FILE from bzr to DESTFILE.
 EDITABLE is ignored."
@@ -324,7 +408,7 @@ EDITABLE is ignored."
   (let ((coding-system-for-read 'binary)
         (coding-system-for-write 'binary))
     (with-temp-file destfile
-      (if rev
+      (if (and rev (stringp rev) (not (string= rev "")))
           (vc-bzr-command "cat" t 0 file "-r" rev)
         (vc-bzr-command "cat" t 0 file)))))
 
@@ -340,10 +424,9 @@ EDITABLE is ignored."
 (define-derived-mode vc-bzr-log-view-mode log-view-mode "Bzr-Log-View"
   (remove-hook 'log-view-mode-hook 'vc-bzr-log-view-mode) ;Deactivate the hack.
   (require 'add-log)
-  ;; Don't have file markers, so use impossible regexp.
-  (set (make-local-variable 'log-view-file-re) "\\'\\`")
+  (set (make-local-variable 'log-view-file-re) "^Working file:[ \t]+\\(.+\\)")
   (set (make-local-variable 'log-view-message-re)
-       "^ *-+\n *\\(?:revno: \\([0-9]+\\)\\|merged: .+\\)")
+       "^ *-+\n *\\(?:revno: \\([0-9.]+\\)\\|merged: .+\\)")
   (set (make-local-variable 'log-view-font-lock-keywords)
        ;; log-view-font-lock-keywords is careful to use the buffer-local
        ;; value of log-view-message-re only since Emacs-23.
@@ -357,40 +440,51 @@ EDITABLE is ignored."
 
 (defun vc-bzr-print-log (files &optional buffer) ; get buffer arg in Emacs 22
   "Get bzr change log for FILES into specified BUFFER."
-  ;; Fixme: This might need the locale fixing up if things like `revno'
-  ;; got localized, but certainly it shouldn't use LC_ALL=C.
-  (vc-bzr-command "log" buffer 0 files)
-  ;; FIXME: Until Emacs-23, VC was missing a hook to sort out the mode for
-  ;; the buffer, or at least set the regexps right.
-  (unless (fboundp 'vc-default-log-view-mode)
-    (add-hook 'log-view-mode-hook 'vc-bzr-log-view-mode)))
+  ;; `vc-do-command' creates the buffer, but we need it before running
+  ;; the command.
+  (vc-setup-buffer buffer)
+  ;; If the buffer exists from a previous invocation it might be
+  ;; read-only.
+    ;; FIXME: `vc-bzr-command' runs `bzr log' with `LC_MESSAGES=C', so
+    ;; the log display may not what the user wants - but I see no other
+    ;; way of getting the above regexps working.
+    (dolist (file files)
+    (vc-exec-after
+     `(let ((inhibit-read-only t))
+      (with-current-buffer buffer
+       ;; Insert the file name so that log-view.el can find it.
+          (insert "Working file: " ',file "\n")) ;; Like RCS/CVS.
+        (apply 'vc-bzr-command "log" ',buffer 'async ',file
+               ',(if (stringp vc-bzr-log-switches)
+                (list vc-bzr-log-switches)
+                   vc-bzr-log-switches))))))
 
 (defun vc-bzr-show-log-entry (revision)
   "Find entry for patch name REVISION in bzr change log buffer."
   (goto-char (point-min))
   (let (case-fold-search)
-    (if (re-search-forward (concat "^-+\nrevno: " revision "$") nil t)
+    (if (re-search-forward
+        ;; "revno:" can appear either at the beginning of a line, or indented.
+        (concat "^[ ]*-+\n[ ]*revno: " 
+                ;; The revision can contain ".", quote it so that it
+                ;; does not interfere with regexp matching.
+                (regexp-quote revision) "$") nil t)
         (beginning-of-line 0)
       (goto-char (point-min)))))
 
-(autoload 'vc-diff-switches-list "vc" nil nil t)
-
 (defun vc-bzr-diff (files &optional rev1 rev2 buffer)
   "VC bzr backend for diff."
-  (let ((working (vc-working-revision (if (consp files) (car files) files))))
-    (if (and (equal rev1 working) (not rev2))
-        (setq rev1 nil))
-    (if (and (not rev1) rev2)
-        (setq rev1 working))
-    ;; bzr diff produces condition code 1 for some reason.
-    (apply #'vc-bzr-command "diff" (or buffer "*vc-diff*") 1 files
-           "--diff-options" (mapconcat 'identity (vc-diff-switches-list bzr)
-                                       " ")
-           (when rev1
-             (if rev2
-                 (list "-r" (format "%s..%s" rev1 rev2))
-               (list "-r" rev1))))))
-
+  ;; `bzr diff' exits with code 1 if diff is non-empty.
+  (apply #'vc-bzr-command "diff" (or buffer "*vc-diff*") 'async files
+         "--diff-options" (mapconcat 'identity 
+                                     (vc-diff-switches-list bzr)
+                                    " ")
+         ;; This `when' is just an optimization because bzr-1.2 is *much*
+         ;; faster when the revision argument is not given.
+         (when (or rev1 rev2)
+           (list "-r" (format "%s..%s"
+                              (or rev1 "revno:-1") 
+                              (or rev2 ""))))))
 
 
 ;; FIXME: vc-{next,previous}-revision need fixing in vc.el to deal with
@@ -437,14 +531,6 @@ property containing author and date information."
         (replace-match "")
         (insert tag " |")))))
 
-;; Definition from Emacs 22
-(unless (fboundp 'vc-annotate-convert-time)
-  (defun vc-annotate-convert-time (time)
-    "Convert a time value to a floating-point number of days.
-The argument TIME is a list as returned by `current-time' or
-`encode-time', only the first two elements of that list are considered."
-    (/ (+ (* (float (car time)) (lsh 1 16)) (cadr time)) 24 3600)))
-
 (defun vc-bzr-annotate-time ()
   (when (re-search-forward "^ *[0-9]+ |" nil t)
     (let ((prop (get-text-property (line-beginning-position) 'help-echo)))
@@ -464,12 +550,6 @@ Return nil if current line isn't annotated."
     (if (looking-at " *\\([0-9]+\\) | ")
         (match-string-no-properties 1))))
 
-;; Not needed for Emacs 22
-(defun vc-bzr-annotate-difference (point)
-  (let ((next-time (vc-bzr-annotate-time)))
-    (if next-time
-        (- (vc-annotate-convert-time (current-time)) next-time))))
-
 (defun vc-bzr-command-discarding-stderr (command &rest args)
   "Execute shell command COMMAND (with ARGS); return its output and exitcode.
 Return value is a cons (EXITCODE . OUTPUT), where EXITCODE is
@@ -485,7 +565,7 @@ stream.  Standard error output is discarded."
 ;; and implement a command to run ediff and `bzr resolve' once the 
 ;; changes have been merged.
 (defun vc-bzr-dir-state (dir &optional localp)
-  "Find the VC state of all files in DIR.
+  "Find the VC state of all files in DIR and its subdirectories.
 Optional argument LOCALP is always ignored."
   (let ((bzr-root-directory (vc-bzr-root dir))
         (at-start t)
@@ -498,7 +578,8 @@ Optional argument LOCALP is always ignored."
     ;; evidence of the contrary.
     (setq at-start t)
     (with-temp-buffer
-      (vc-bzr-command "ls" t 0 nil "--versioned" "--non-recursive")
+      (buffer-disable-undo)            ;; Because these buffers can get huge
+      (vc-bzr-command "ls" t 0 nil "--versioned")
       (goto-char (point-min))
       (while (or at-start
                  (eq 0 (forward-line)))
@@ -507,9 +588,13 @@ Optional argument LOCALP is always ignored."
                      (buffer-substring-no-properties 
                       (line-beginning-position) (line-end-position))
                      bzr-root-directory)))
+          ;; files are up-to-date unless they appear in the `bzr
+          ;; status' output below
           (vc-file-setprop file 'vc-state 'up-to-date)
           ;; XXX: is this correct? what happens if one 
           ;; mixes different SCMs in the same dir?
+          ;; Anyway, we're looking at the output of `bzr ls --versioned',
+          ;; so we know these files are registered with Bzr.
           (vc-file-setprop file 'vc-backend 'Bzr))))
     ;; `bzr status' reports on added/modified/renamed and unknown/ignored files
     (setq at-start t)
@@ -521,7 +606,7 @@ Optional argument LOCALP is always ignored."
         (setq at-start nil)
         (cond 
          ((looking-at "^added") 
-          (setq current-vc-state 'edited)
+          (setq current-vc-state 'added)
           (setq current-bzr-state 'added))
          ((looking-at "^kind changed") 
           (setq current-vc-state 'edited)
@@ -532,8 +617,11 @@ Optional argument LOCALP is always ignored."
          ((looking-at "^renamed") 
           (setq current-vc-state 'edited)
           (setq current-bzr-state 'renamed))
-         ((looking-at "^\\(unknown\\|ignored\\)")
-          (setq current-vc-state nil)
+         ((looking-at "^ignored")
+          (setq current-vc-state 'ignored)
+          (setq current-bzr-state 'not-versioned))
+         ((looking-at "^unknown")
+          (setq current-vc-state 'unregistered)
           (setq current-bzr-state 'not-versioned))
          ((looking-at "  ")
           ;; file names are indented by two spaces
@@ -561,11 +649,95 @@ Optional argument LOCALP is always ignored."
 (defun vc-bzr-dired-state-info (file)
   "Bzr-specific version of `vc-dired-state-info'."
   (if (eq 'edited (vc-state file))
-      (let ((bzr-state (vc-file-getprop file 'vc-bzr-state)))
-        (if bzr-state
-            (concat "(" (symbol-name bzr-state) ")")
-          ;; else fall back to default vc representation
-          (vc-default-dired-state-info 'Bzr file)))))
+        (concat "(" (symbol-name (or (vc-file-getprop file 'vc-bzr-state) 
+                                     'edited)) ")")
+    ;; else fall back to default vc.el representation
+    (vc-default-dired-state-info 'Bzr file)))
+
+;; XXX Experimental function for the vc-dired replacement.
+;; XXX: this needs testing, it's probably incomplete. 
+(defun vc-bzr-after-dir-status (update-function status-buffer)
+  (let ((status-str nil)
+       (file nil)
+       (translation '(("+N" . added)
+                      ("-D" . removed)
+                      (" M" . edited)
+                      ;; XXX: what about ignored files?
+                      (" D" . missing)
+                      ("? " . unregistered)))
+       (translated nil)
+       (result nil))
+      (goto-char (point-min))
+      (while (not (eobp))
+       (setq status-str
+             (buffer-substring-no-properties (point) (+ (point) 2)))
+       (setq file
+             (buffer-substring-no-properties (+ (point) 4)
+                                             (line-end-position)))
+       (setq translated (assoc status-str translation))
+       (push (list file (cdr translated)) result)
+       (forward-line))
+      (funcall update-function result status-buffer)))
+
+;; XXX Experimental function for the vc-dired replacement.
+;; XXX This probably needs some further refinement and testing.
+(defun vc-bzr-dir-status (dir update-function status-buffer)
+  "Return a list of conses (file . state) for DIR."
+  ;; XXX: Is this the right command to use?
+  (vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S")
+  (vc-exec-after
+   `(vc-bzr-after-dir-status (quote ,update-function) ,status-buffer)))
+
+;;; Revision completion
+
+(defun vc-bzr-complete-with-prefix (prefix action table string pred)
+  (let ((comp (complete-with-action action table string pred)))
+    (if (stringp comp)
+        (concat prefix comp)
+      comp)))
+
+(defun vc-bzr-revision-completion-table (files)
+  (lexical-let ((files files))
+    ;; What about using `files'?!?  --Stef
+    (lambda (string pred action)
+      (cond
+       ((string-match "\\`\\(ancestor\\|branch\\|\\(revno:\\)?[-0-9]+:\\):"
+                      string)
+        (vc-bzr-complete-with-prefix (substring string 0 (match-end 0))
+                                     action
+                                     'read-file-name-internal
+                                     (substring string (match-end 0))
+                                     ;; Dropping `pred'.   Maybe we should just
+                                     ;; stash it in `read-file-name-predicate'?
+                                     nil))
+       ((string-match "\\`\\(before\\):" string)
+        (vc-bzr-complete-with-prefix (substring string 0 (match-end 0))
+                                     action
+                                     (vc-bzr-revision-completion-table files)
+                                     (substring string (match-end 0))
+                                     pred))
+       ((string-match "\\`\\(tag\\):" string)
+        (let ((prefix (substring string 0 (match-end 0)))
+              (tag (substring string (match-end 0)))
+              (table nil))
+          (with-temp-buffer
+            ;; "bzr-1.2 tags" is much faster with --show-ids.
+            (call-process vc-bzr-program nil '(t) nil "tags" "--show-ids")
+            ;; The output is ambiguous, unless we assume that revids do not
+            ;; contain spaces.
+            (goto-char (point-min))
+            (while (re-search-forward "^\\(.*[^ \n]\\) +[^ \n]*$" nil t)
+              (push (match-string-no-properties 1) table)))
+          (vc-bzr-complete-with-prefix prefix action table tag pred)))
+
+       ((string-match "\\`\\(revid\\):" string)
+        ;; FIXME: How can I get a list of revision ids?
+        )
+       (t
+        (complete-with-action action '("revno:" "revid:" "last:" "before:"
+                                       "tag:" "date:" "ancestor:" "branch:"
+                                       "submit:")
+                              string pred))))))
 
 (eval-after-load "vc"
   '(add-to-list 'vc-directory-exclusion-list vc-bzr-admin-dirname t))