diff lisp/vc-bzr.el @ 82365:e5a68f18fcb9

Merge from emacs--rel--22 Revision: emacs@sv.gnu.org/emacs--devo--0--patch-851
author Miles Bader <miles@gnu.org>
date Mon, 13 Aug 2007 13:41:28 +0000
parents 36893fdf92ab 98c39e79e082
children 962fb740e73f 424b655804ca
line wrap: on
line diff
--- a/lisp/vc-bzr.el	Mon Aug 13 11:27:41 2007 +0000
+++ b/lisp/vc-bzr.el	Mon Aug 13 13:41:28 2007 +0000
@@ -2,15 +2,10 @@
 
 ;; Copyright (C) 2006, 2007  Free Software Foundation, Inc.
 
-;; NOTE: THIS IS A MODIFIED VERSION OF Dave Love's vc-bzr.el,
-;; which you can find at: http://www.loveshack.ukfsn.org/emacs/vc-bzr.el
-;; I could not get in touch with Dave Love by email, so
-;; I am releasing my changes separately. -- Riccardo
-
 ;; Author: Dave Love <fx@gnu.org>, Riccardo Murri <riccardo.murri@gmail.com>
 ;; Keywords: tools
 ;; Created: Sept 2006
-;; Version: 2007-05-24
+;; Version: 2007-08-03
 ;; URL: http://launchpad.net/vc-bzr
 
 ;; This file is free software; you can redistribute it and/or modify
@@ -31,9 +26,6 @@
 
 ;;; Commentary:
 
-;; NOTE: THIS IS A MODIFIED VERSION OF Dave Love's vc-bzr.el,
-;; which you can find at: http://www.loveshack.ukfsn.org/emacs/vc-bzr.el
-
 ;; See <URL:http://bazaar-vcs.org/> concerning bzr.
 
 ;; Load this library to register bzr support in VC.  It covers basic VC 
@@ -96,34 +88,73 @@
   (let ((process-environment
          (list* "BZR_PROGRESS_BAR=none" ; Suppress progress output (bzr >=0.9)
                 "LC_ALL=C"              ; Force English output
-                process-environment))
-        ;; bzr may attempt some kind of user interaction if its stdin/stdout
-        ;; is connected to a PTY; therefore, ask Emacs to use a pipe to
-        ;; communicate with it.
-        ;; This is redundant because vc-do-command does it already.  --Stef
-        (process-connection-type nil))
+                process-environment)))
     (apply 'vc-do-command buffer okstatus vc-bzr-program
            file-or-list bzr-command (append vc-bzr-program-args args))))
 
 
 ;;;###autoload
-(defconst vc-bzr-admin-dirname ".bzr")    ; FIXME: "_bzr" on w32?
+(defconst vc-bzr-admin-dirname ".bzr"    ; FIXME: "_bzr" on w32?
+  "Name of the directory containing Bzr repository status files.")
+;;;###autoload
+(defconst vc-bzr-admin-checkout-format-file
+  (concat vc-bzr-admin-dirname "/checkout/format"))
+(defconst vc-bzr-admin-dirstate
+  (concat vc-bzr-admin-dirname "/checkout/dirstate"))
+(defconst vc-bzr-admin-branch-format-file
+  (concat vc-bzr-admin-dirname "/branch/format"))
+(defconst vc-bzr-admin-revhistory
+  (concat vc-bzr-admin-dirname "/branch/revision-history"))
 
 ;;;###autoload (defun vc-bzr-registered (file)
-;;;###autoload   (if (vc-find-root file vc-bzr-admin-dirname)
+;;;###autoload   (if (vc-find-root file vc-bzr-admin-checkout-format-file)
 ;;;###autoload       (progn
 ;;;###autoload         (load "vc-bzr")
 ;;;###autoload         (vc-bzr-registered file))))
 
-(defun vc-bzr-root-dir (file)
-  "Return the root directory in the hierarchy above FILE.
-Return nil if there isn't one."
-  (vc-find-root file vc-bzr-admin-dirname))
+(defun vc-bzr-root (file)
+  "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))))
 
 (defun vc-bzr-registered (file)
-  "Return non-nil if FILE is registered with bzr."
-  (if (vc-bzr-root-dir file) ; Short cut.
-      (vc-bzr-state file)))                    ; Expensive.
+  "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.
+
+If the `checkout/dirstate' file cannot be parsed, fall back to
+running `vc-bzr-state'."
+  (condition-case nil
+      (lexical-let ((root (vc-bzr-root file)))
+    (and root ; Short cut.
+         ;; This looks at internal files.  May break if they change
+         ;; their format.
+             (lexical-let
+                 ((dirstate-file (expand-file-name vc-bzr-admin-dirstate root)))
+               (if (file-exists-p dirstate-file)
+         (with-temp-buffer
+                     (insert-file-contents dirstate-file)
+           (goto-char (point-min))
+                     (when (looking-at "#bazaar dirstate flat format 3")
+           (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))))
+                 t))
+             (vc-bzr-state file)))  ; Expensive.
+    (file-error nil))) ; vc-bzr-program not found
 
 (defun vc-bzr-buffer-nonblank-p (&optional buffer)
   "Return non-nil if BUFFER contains any non-blank characters."
@@ -134,15 +165,34 @@
         (re-search-forward "[^ \t\n]" (point-max) t))))
 
 (defconst vc-bzr-state-words
-  "added\\|ignored\\|modified\\|removed\\|renamed\\|unknown"
+  "added\\|ignored\\|kind changed\\|modified\\|removed\\|renamed\\|unknown"
   "Regexp matching file status words as reported in `bzr' output.")
 
+(defun vc-bzr-file-name-relative (filename)
+  "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*))))
+    (and rootdir 
+         (file-relative-name filename* rootdir))))
+
 ;; FIXME:  Also get this in a non-registered sub-directory.
-(defun vc-bzr-state (file)
+;; 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
+string or nil, and STATUS is one of the symbols: 'added,
+'ignored, 'kindchange, 'modified, 'removed, 'renamed, 'unknown,
+which directly correspond to `bzr status' output, or 'unchanged
+for files whose copy in the working tree is identical to the one
+in the branch repository, or nil for files that are not
+registered with Bzr.
+
+If any error occurred in running `bzr status', then return nil."
+  (condition-case nil
   (with-temp-buffer
-    (cd (file-name-directory file))
-    (let ((ret (vc-bzr-command "status" t 255 file))
-          (state 'up-to-date))
+        (let ((ret (vc-bzr-command "status" t 0 file))
+              (status 'unchanged))
       ;; the only secure status indication in `bzr status' output
       ;; is a couple of lines following the pattern::
       ;;   | <status>:
@@ -153,45 +203,93 @@
       (goto-char (point-min))
       (when
           (re-search-forward
+               ;; bzr prints paths relative to the repository root
            (concat "^\\(" vc-bzr-state-words "\\):[ \t\n]+"
-                   (file-name-nondirectory file) "[ \t\n]*$")
+                       (regexp-quote (vc-bzr-file-name-relative file)) 
+                       (if (file-directory-p file) "/?" "")
+                       "[ \t\n]*$")
            (point-max) t)
         (let ((start (match-beginning 0))
               (end (match-end 0)))
           (goto-char start)
-          (setq state
+              (setq status
                 (cond
                  ((not (equal ret 0)) nil)
-                 ((looking-at "added\\|renamed\\|modified\\|removed") 'edited)
-                 ((looking-at "unknown\\|ignored") nil)))
+                     ((looking-at "added") 'added)
+                     ((looking-at "kind changed") 'kindchange)
+                     ((looking-at "renamed") 'renamed)
+                     ((looking-at "modified") 'modified)
+                     ((looking-at "removed") 'removed)
+                     ((looking-at "ignored") 'ignored)
+                     ((looking-at "unknown") 'unknown)))
           ;; erase the status text that matched
           (delete-region start end)))
-      (when (vc-bzr-buffer-nonblank-p)
-        ;; "bzr" will output some warnings and informational messages
-        ;; to the user to stderr; due to Emacs' `vc-do-command' (and,
-        ;; it seems, `start-process' itself), we cannot catch stderr
+          (if status
+              (cons status
+            ;; "bzr" will output warnings and informational messages to
+            ;; stderr; due to Emacs' `vc-do-command' (and, it seems,
+            ;; `start-process' itself) limitations, we cannot catch stderr
         ;; and stdout into different buffers.  So, if there's anything
         ;; left in the buffer after removing the above status
         ;; keywords, let us just presume that any other message from
         ;; "bzr" is a user warning, and display it.
-        (message "Warnings in `bzr' output: %s"
-               (buffer-substring (point-min) (point-max))))
-      (when state
-        (vc-file-setprop file 'vc-workfile-version
-                         (vc-bzr-workfile-version file))
-        (vc-file-setprop file 'vc-state state))
-      state)))
+                    (if (vc-bzr-buffer-nonblank-p)
+                        (buffer-substring (point-min) (point-max)))))))
+    (file-error nil))) ; vc-bzr-program not found
+
+(defun vc-bzr-state (file)
+  (lexical-let ((result (vc-bzr-status file)))
+    (when (consp result)
+      (if (cdr result)
+          (message "Warnings in `bzr' output: %s" (cdr result)))
+      (cdr (assq (car result)
+                 '((added . edited)
+                   (kindchange . edited)
+                   (renamed . edited)
+                   (modified . edited)
+                   (removed . edited)
+                   (ignored . nil)
+                   (unknown . nil)
+                   (unchanged . up-to-date)))))))
 
 (defun vc-bzr-workfile-unchanged-p (file)
-  (eq 'up-to-date (vc-bzr-state file)))
+  (eq 'unchanged (car (vc-bzr-status file))))
 
 (defun vc-bzr-workfile-version (file)
-  ;; Looks like this could be obtained via counting lines in
-  ;; .bzr/branch/revision-history.
+  (lexical-let*
+      ((rootdir (vc-bzr-root file))
+       (branch-format-file (concat rootdir "/" vc-bzr-admin-branch-format-file))
+       (revhistory-file (concat rootdir "/" vc-bzr-admin-revhistory))
+       (lastrev-file (concat rootdir "/" "branch/last-revision")))
+    ;; Count lines in .bzr/branch/revision-history to avoid forking a
+    ;; bzr process.  This looks at internal files.  May break if they
+    ;; change their format.
+    (if (file-exists-p branch-format-file)
   (with-temp-buffer
-    (vc-bzr-command "revno" t 0 file)
-    (goto-char (point-min))
-    (buffer-substring (point) (line-end-position))))
+          (insert-file-contents branch-format-file) 
+          (goto-char (point-min))
+          (cond
+           ((or
+             (looking-at "Bazaar-NG branch, format 0.0.4")
+             (looking-at "Bazaar-NG branch format 5"))
+            ;; count lines in .bzr/branch/revision-history
+          (insert-file-contents revhistory-file) 
+            (number-to-string (count-lines (line-end-position) (point-max))))
+           ((looking-at "Bazaar Branch Format 6 (bzr 0.15)")
+            ;; revno is the first number in .bzr/branch/last-revision
+            (insert-file-contents lastrev-file) 
+            (goto-char (line-end-position))
+            (if (re-search-forward "[0-9]+" nil t)
+                (buffer-substring (match-beginning 0) (match-end 0))))))
+      ;; fallback to calling "bzr revno"
+      (lexical-let*
+          ((result (vc-bzr-command-discarding-stderr
+                    vc-bzr-program "revno" file))
+           (exitcode (car result))
+           (output (cdr result)))
+        (cond
+         ((eq exitcode 0) (substring output 0 -1))
+         (t nil))))))
 
 (defun vc-bzr-checkout-model (file)
   'implicit)
@@ -209,7 +307,7 @@
 
 ;; Could run `bzr status' in the directory and see if it succeeds, but
 ;; that's relatively expensive.
-(defalias 'vc-bzr-responsible-p 'vc-bzr-root-dir
+(defalias 'vc-bzr-responsible-p 'vc-bzr-root
   "Return non-nil if FILE is (potentially) controlled by bzr.
 The criterion is that there is a `.bzr' directory in the same
 or a superior directory.")
@@ -250,7 +348,7 @@
 
 (defun vc-bzr-revert (file &optional contents-done)
   (unless contents-done
-    (with-temp-buffer (vc-bzr-command "revert" t 'async file))))
+    (with-temp-buffer (vc-bzr-command "revert" t 0 file))))
 
 (defvar log-view-message-re)
 (defvar log-view-file-re)
@@ -294,13 +392,11 @@
         (beginning-of-line 0)
       (goto-char (point-min)))))
 
-;; Fixem: vc-bzr-wash-log
-
 (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-workfile-version (car files))))
+  (let ((working (vc-workfile-version (if (consp files) (car files) files))))
     (if (and (equal rev1 working) (not rev2))
         (setq rev1 nil))
     (if (and (not rev1) rev2)
@@ -317,9 +413,8 @@
 
 (defalias 'vc-bzr-diff-tree 'vc-bzr-diff)
 
-;; Fixme: implement vc-bzr-dir-state, vc-bzr-dired-state-info
 
-;; Fixme: vc-{next,previous}-version need fixing in vc.el to deal with
+;; FIXME: vc-{next,previous}-version need fixing in vc.el to deal with
 ;; straight integer versions.
 
 (defun vc-bzr-delete-file (file)
@@ -399,17 +494,16 @@
     (if next-time
         (- (vc-annotate-convert-time (current-time)) next-time))))
 
-;; FIXME: `bzr root' will return the real path to the repository root,
-;; that is, it can differ from the buffer's current directory name
-;; if there are any symbolic links.
-(defun vc-bzr-root (dir)
-  "Return the root directory of the bzr repository containing DIR."
-  ;; Cache technique copied from vc-arch.el.
-  (or (vc-file-getprop dir 'bzr-root)
-      (vc-file-setprop
-       dir 'bzr-root
-       (substring 
-	(shell-command-to-string (concat vc-bzr-program " root " dir)) 0 -1))))
+(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
+the (numerical) exit code of the process, and OUTPUT is a string
+containing whatever the process sent to its standard output
+stream.  Standard error output is discarded."
+  (with-temp-buffer
+    (cons
+     (apply #'call-process command nil (list (current-buffer) nil) nil args)
+     (buffer-substring (point-min) (point-max)))))
 
 ;; TODO: it would be nice to mark the conflicted files in  VC Dired,
 ;; and implement a command to run ediff and `bzr resolve' once the 
@@ -453,6 +547,9 @@
          ((looking-at "^added") 
           (setq current-vc-state 'edited)
           (setq current-bzr-state 'added))
+         ((looking-at "^kind changed") 
+          (setq current-vc-state 'edited)
+          (setq current-bzr-state 'kindchange))
          ((looking-at "^modified") 
           (setq current-vc-state 'edited)
           (setq current-bzr-state 'modified))
@@ -499,7 +596,7 @@
 (add-to-list 'vc-handled-backends 'Bzr)
 
 (eval-after-load "vc"
-  '(add-to-list 'vc-directory-exclusion-list ".bzr" t))
+  '(add-to-list 'vc-directory-exclusion-list vc-bzr-admin-dirname t))
 
 (defconst vc-bzr-unload-hook
   (lambda ()