(x_scroll_bar_set_handle, x_scroll_bar_handle_click)
[bpt/emacs.git] / lisp / vc-svn.el
index 916930f..b83f8e0 100644 (file)
@@ -9,7 +9,7 @@
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 
 ;;; Commentary:
 
-;; This is preliminary support for Subversion (http://subversion.tigris.org/).
-;; It started as `sed s/cvs/svn/ vc.cvs.el' (from version 1.56)
-;; and hasn't been completely fixed since.
-
-;; Sync'd with Subversion's vc-svn.el as of revision 5801.
+;; Sync'd with Subversion's vc-svn.el as of revision 5801. but this version
+;; has been extensively modified since to handle filesets.
 
 ;;; Bugs:
 
@@ -85,23 +82,21 @@ If you want to force an empty list of arguments, use t."
   :type '(repeat string)
   :group 'vc)
 
-(defconst vc-svn-use-edit nil
-  ;; Subversion does not provide this feature (yet).
-  "*Non-nil means to use `svn edit' to \"check out\" a file.
-This is only meaningful if you don't use the implicit checkout model
-\(i.e. if you have $SVNREAD set)."
-  ;; :type 'boolean
-  ;; :version "22.1"
-  ;; :group 'vc
-  )
-
-(defvar vc-svn-admin-directory
-  (cond ((and (eq system-type 'windows-nt)
+;; We want to autoload it for use by the autoloaded version of
+;; vc-svn-registered, but we want the value to be compiled at startup, not
+;; at dump time.
+;; ;;;###autoload
+(defconst vc-svn-admin-directory
+  (cond ((and (memq system-type '(cygwin windows-nt ms-dos))
              (getenv "SVN_ASP_DOT_NET_HACK"))
         "_svn")
        (t ".svn"))
   "The name of the \".svn\" subdirectory or its equivalent.")
 
+;;; Properties of the backend
+
+(defun vc-svn-revision-granularity ()
+     'repository)
 ;;;
 ;;; State-querying functions
 ;;;
@@ -111,12 +106,12 @@ This is only meaningful if you don't use the implicit checkout model
 
 ;;;###autoload (defun vc-svn-registered (f)
 ;;;###autoload   (let ((admin-dir (cond ((and (eq system-type 'windows-nt)
-;;;###autoload                                (getenv "SVN_ASP_DOT_NET_HACK"))
-;;;###autoload                           "_svn")
-;;;###autoload                          (t ".svn"))))
+;;;###autoload                                (getenv "SVN_ASP_DOT_NET_HACK"))
+;;;###autoload                           "_svn")
+;;;###autoload                          (t ".svn"))))
 ;;;###autoload     (when (file-readable-p (expand-file-name
-;;;###autoload                             (concat admin-dir "/entries")
-;;;###autoload                             (file-name-directory f)))
+;;;###autoload                             (concat admin-dir "/entries")
+;;;###autoload                             (file-name-directory f)))
 ;;;###autoload       (load "vc-svn")
 ;;;###autoload       (vc-svn-registered f))))
 
@@ -165,13 +160,13 @@ This is only meaningful if you don't use the implicit checkout model
       (vc-svn-command t 0 nil "status" (if localp "-v" "-u"))
       (vc-svn-parse-status))))
 
-(defun vc-svn-workfile-version (file)
-  "SVN-specific version of `vc-workfile-version'."
+(defun vc-svn-working-revision (file)
+  "SVN-specific version of `vc-working-revision'."
   ;; There is no need to consult RCS headers under SVN, because we
   ;; get the workfile version for free when we recognize that a file
   ;; is registered in SVN.
   (vc-svn-registered file)
-  (vc-file-getprop file 'vc-workfile-version))
+  (vc-file-getprop file 'vc-working-revision))
 
 (defun vc-svn-checkout-model (file)
   "SVN-specific version of `vc-checkout-model'."
@@ -185,25 +180,25 @@ This is only meaningful if you don't use the implicit checkout model
   "SVN-specific version of `vc-dired-state-info'."
   (let ((svn-state (vc-state file)))
     (cond ((eq svn-state 'edited)
-          (if (equal (vc-workfile-version file) "0")
+          (if (equal (vc-working-revision file) "0")
               "(added)" "(modified)"))
          ((eq svn-state 'needs-patch) "(patch)")
          ((eq svn-state 'needs-merge) "(merge)"))))
 
-(defun vc-svn-previous-version (file rev)
+(defun vc-svn-previous-revision (file rev)
   (let ((newrev (1- (string-to-number rev))))
     (when (< 0 newrev)
       (number-to-string newrev))))
 
-(defun vc-svn-next-version (file rev)
+(defun vc-svn-next-revision (file rev)
   (let ((newrev (1+ (string-to-number rev))))
-    ;; The "workfile version" is an uneasy conceptual fit under Subversion;
+    ;; The "working revision" is an uneasy conceptual fit under Subversion;
     ;; we use it as the upper bound until a better idea comes along.  If the
     ;; workfile version W coincides with the tree's latest revision R, then
     ;; this check prevents a "no such revision: R+1" error.  Otherwise, it
     ;; inhibits showing of W+1 through R, which could be considered anywhere
     ;; from gracious to impolite.
-    (unless (< (string-to-number (vc-file-getprop file 'vc-workfile-version))
+    (unless (< (string-to-number (vc-file-getprop file 'vc-working-revision))
                newrev)
       (number-to-string newrev))))
 
@@ -212,13 +207,19 @@ This is only meaningful if you don't use the implicit checkout model
 ;;; State-changing functions
 ;;;
 
-(defun vc-svn-register (file &optional rev comment)
-  "Register FILE into the SVN version-control system.
-COMMENT can be used to provide an initial description of FILE.
+(defun vc-svn-create-repo ()
+  "Create a new SVN repository."
+  (vc-do-command nil 0 "svnadmin" '("create" "SVN"))
+  (vc-do-command nil 0 "svn" '(".") 
+                "checkout" (concat "file://" default-directory "SVN")))
+
+(defun vc-svn-register (files &optional rev comment)
+  "Register FILES into the SVN version-control system.
+The COMMENT argument is ignored  This does an add but not a commit.
 
 `vc-register-switches' and `vc-svn-register-switches' are passed to
 the SVN command (in that order)."
-  (apply 'vc-svn-command nil 0 file "add" (vc-switches 'SVN 'register)))
+  (apply 'vc-svn-command nil 0 files "add" (vc-switches 'SVN 'register)))
 
 (defun vc-svn-responsible-p (file)
   "Return non-nil if SVN thinks it is responsible for FILE."
@@ -231,10 +232,11 @@ the SVN command (in that order)."
   "Return non-nil if FILE could be registered in SVN.
 This is only possible if SVN is responsible for FILE's directory.")
 
-(defun vc-svn-checkin (file rev comment)
+(defun vc-svn-checkin (files rev comment)
   "SVN-specific version of `vc-backend-checkin'."
+  (if rev (error "Committing to a specific revision is unsupported in SVN."))
   (let ((status (apply
-                 'vc-svn-command nil 1 file "ci"
+                 'vc-svn-command nil 1 files "ci"
                  (nconc (list "-m" comment) (vc-switches 'SVN 'checkin)))))
     (set-buffer "*vc*")
     (goto-char (point-min))
@@ -242,7 +244,8 @@ This is only possible if SVN is responsible for FILE's directory.")
       ;; Check checkin problem.
       (cond
        ((search-forward "Transaction is out of date" nil t)
-        (vc-file-setprop file 'vc-state 'needs-merge)
+        (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge))
+             files)
         (error (substitute-command-keys
                 (concat "Up-to-date check failed: "
                         "type \\[vc-next-action] to merge in changes"))))
@@ -253,11 +256,12 @@ This is only possible if SVN is responsible for FILE's directory.")
         (error "Check-in failed"))))
     ;; Update file properties
     ;; (vc-file-setprop
-    ;;  file 'vc-workfile-version
+    ;;  file 'vc-working-revision
     ;;  (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2))
     ))
 
-(defun vc-svn-find-version (file rev buffer)
+(defun vc-svn-find-revision (file rev buffer)
+  "SVN-specific retrieval of a specified version into a buffer."
   (apply 'vc-svn-command
         buffer 0 file
         "cat"
@@ -274,15 +278,10 @@ This is only possible if SVN is responsible for FILE's directory.")
 
 (defun vc-svn-update (file editable rev switches)
   (if (and (file-exists-p file) (not rev))
-      ;; If no revision was specified, just make the file writable
-      ;; if necessary (using `svn-edit' if requested).
-      (and editable (not (eq (vc-svn-checkout-model file) 'implicit))
-          (if vc-svn-use-edit
-              (vc-svn-command nil 0 file "edit")
-            (set-file-modes file (logior (file-modes file) 128))
-            (if (equal file buffer-file-name) (toggle-read-only -1))))
+      ;; If no revision was specified, there's nothing to do.
+      nil
     ;; Check out a particular version (or recreate the file).
-    (vc-file-setprop file 'vc-workfile-version nil)
+    (vc-file-setprop file 'vc-working-revision nil)
     (apply 'vc-svn-command nil 0 file
           "update"
           ;; default for verbose checkout: clear the sticky tag so
@@ -302,12 +301,7 @@ This is only possible if SVN is responsible for FILE's directory.")
 (defun vc-svn-revert (file &optional contents-done)
   "Revert FILE to the version it was based on."
   (unless contents-done
-    (vc-svn-command nil 0 file "revert"))
-  (unless (eq (vc-checkout-model file) 'implicit)
-    (if vc-svn-use-edit
-        (vc-svn-command nil 0 file "unedit")
-      ;; Make the file read-only by switching off all w-bits
-      (set-file-modes file (logand (file-modes file) 3950)))))
+    (vc-svn-command nil 0 file "revert")))
 
 (defun vc-svn-merge (file first-version &optional second-version)
   "Merge changes into current working copy of FILE.
@@ -327,36 +321,41 @@ The changes are between FIRST-VERSION and SECOND-VERSION."
 (defun vc-svn-merge-news (file)
   "Merge in any new changes made to FILE."
   (message "Merging changes into %s..." file)
-  ;; (vc-file-setprop file 'vc-workfile-version nil)
+  ;; (vc-file-setprop file 'vc-working-revision nil)
   (vc-file-setprop file 'vc-checkout-time 0)
   (vc-svn-command nil 0 file "update")
   ;; Analyze the merge result reported by SVN, and set
   ;; file properties accordingly.
   (with-current-buffer (get-buffer "*vc*")
     (goto-char (point-min))
-    ;; get new workfile version
+    ;; get new working revision
     (if (re-search-forward
         "^\\(Updated to\\|At\\) revision \\([0-9]+\\)" nil t)
-       (vc-file-setprop file 'vc-workfile-version (match-string 2))
-      (vc-file-setprop file 'vc-workfile-version nil))
+       (vc-file-setprop file 'vc-working-revision (match-string 2))
+      (vc-file-setprop file 'vc-working-revision nil))
     ;; get file status
     (goto-char (point-min))
     (prog1
         (if (looking-at "At revision")
             0 ;; there were no news; indicate success
           (if (re-search-forward
-               (concat "^\\([CGDU]  \\)?"
+               ;; Newer SVN clients have 3 columns of chars (one for the
+               ;; file's contents, then second for its properties, and the
+               ;; third for lock-grabbing info), before the 2 spaces.
+               ;; We also used to match the filename in column 0 without any
+               ;; meta-info before it, but I believe this can never happen.
+               (concat "^\\(\\([ACGDU]\\)\\(.[B ]\\)?  \\)"
                        (regexp-quote (file-name-nondirectory file)))
                nil t)
               (cond
                ;; Merge successful, we are in sync with repository now
-               ((string= (match-string 1) "U  ")
+               ((string= (match-string 2) "U")
                 (vc-file-setprop file 'vc-state 'up-to-date)
                 (vc-file-setprop file 'vc-checkout-time
                                  (nth 5 (file-attributes file)))
                 0);; indicate success to the caller
                ;; Merge successful, but our own changes are still in the file
-               ((string= (match-string 1) "G  ")
+               ((string= (match-string 2) "G")
                 (vc-file-setprop file 'vc-state 'edited)
                 0);; indicate success to the caller
                ;; Conflicts detected!
@@ -373,53 +372,60 @@ The changes are between FIRST-VERSION and SECOND-VERSION."
 ;;; History functions
 ;;;
 
-(defun vc-svn-print-log (file &optional buffer)
-  "Get change log associated with FILE."
+(defun vc-svn-print-log (files &optional buffer)
+  "Get change log(s) associated with FILES."
   (save-current-buffer
     (vc-setup-buffer buffer)
     (let ((inhibit-read-only t))
       (goto-char (point-min))
       ;; Add a line to tell log-view-mode what file this is.
-      (insert "Working file: " (file-relative-name file) "\n"))
+      ;; FIXME if there are multiple files, log-view-current-file
+      ;; breaks.  It's trivial to adapt log-view-file-re for the
+      ;; changed prefix, but less trivial to make
+      ;; log-view-current-file actually do the right thing in the
+      ;; multiple file case.
+      (insert (format "Working file%s: "
+                     (if (= (length files) 1)
+                         ""
+                       "s"))
+                     (vc-delistify (mapcar 'file-relative-name files)) "\n"))
     (vc-svn-command
      buffer
-     (if (and (vc-stay-local-p file) (fboundp 'start-process)) 'async 0)
-     file "log"
-     ;; By default Subversion only shows the log upto the working version,
+     (if (and (= (length files) 1) (vc-stay-local-p (car files))) 'async 0)
+     files "log"
+     ;; By default Subversion only shows the log upto the working revision,
      ;; whereas we also want the log of the subsequent commits.  At least
      ;; that's what the vc-cvs.el code does.
      "-rHEAD:0")))
 
-(defun vc-svn-diff (file &optional oldvers newvers buffer)
-  "Get a difference report using SVN between two versions of FILE."
-  (unless buffer (setq buffer "*vc-diff*"))
-  (if (and oldvers (equal oldvers (vc-workfile-version file)))
-      ;; Use nil rather than the current revision because svn handles it
-      ;; better (i.e. locally).
-      (setq oldvers nil))
-  (if (string= (vc-workfile-version file) "0")
-      ;; This file is added but not yet committed; there is no master file.
-      (if (or oldvers newvers)
-         (error "No revisions of %s exist" file)
-       ;; We regard this as "changed".
-       ;; Diff it against /dev/null.
-       ;; Note: this is NOT a "svn diff".
-       (apply 'vc-do-command buffer
-              1 "diff" file
-              (append (vc-switches nil 'diff) '("/dev/null")))
-       ;; Even if it's empty, it's locally modified.
-       1)
-    (let* ((switches
+(defun vc-svn-wash-log ()
+  "Remove all non-comment information from log output."
+  ;; FIXME: not implemented for SVN
+  nil)
+
+(defun vc-svn-diff (files &optional oldvers newvers buffer)
+  "Get a difference report using SVN between two revisions of fileset FILES."
+  (and oldvers
+       (catch 'no
+        (dolist (f files)
+          (or (equal oldvers (vc-working-revision f))
+              (throw 'no nil)))
+        t)
+       ;; Use nil rather than the current revision because svn handles
+       ;; it better (i.e. locally).  Note that if _any_ of the files
+       ;; has a different revision, we fetch the lot, which is
+       ;; obviously sub-optimal.
+       (setq oldvers nil))
+  (let* ((switches
            (if vc-svn-diff-switches
                (vc-switches 'SVN 'diff)
              (list "-x" (mapconcat 'identity (vc-switches nil 'diff) " "))))
           (async (and (not vc-disable-async-diff)
-                       (vc-stay-local-p file)
-                      (or oldvers newvers) ; Svn diffs those locally.
-                      (fboundp 'start-process))))
+                       (vc-stay-local-p files)
+                      (or oldvers newvers)))) ; Svn diffs those locally.
       (apply 'vc-svn-command buffer
             (if async 'async 0)
-            file "diff"
+            files "diff"
             (append
              switches
              (when oldvers
@@ -428,18 +434,14 @@ The changes are between FIRST-VERSION and SECOND-VERSION."
       (if async 1                    ; async diff => pessimistic assumption
        ;; For some reason `svn diff' does not return a useful
        ;; status w.r.t whether the diff was empty or not.
-       (buffer-size (get-buffer buffer))))))
-
-(defun vc-svn-diff-tree (dir &optional rev1 rev2)
-  "Diff all files at and below DIR."
-  (vc-svn-diff (file-name-as-directory dir) rev1 rev2))
+       (buffer-size (get-buffer buffer)))))
 
 ;;;
 ;;; Snapshot system
 ;;;
 
 (defun vc-svn-create-snapshot (dir name branchp)
-  "Assign to DIR's current version a given NAME.
+  "Assign to DIR's current revision a given NAME.
 If BRANCHP is non-nil, the name is created as a branch (and the current
 workspace is immediately moved to that new branch).
 NAME is assumed to be a URL."
@@ -475,11 +477,16 @@ NAME is assumed to be a URL."
 ;;; Internal functions
 ;;;
 
-(defun vc-svn-command (buffer okstatus file &rest flags)
+(defcustom vc-svn-program "svn"
+  "Name of the svn executable."
+  :type 'string
+  :group 'vc)
+
+(defun vc-svn-command (buffer okstatus file-or-list &rest flags)
   "A wrapper around `vc-do-command' for use in vc-svn.el.
 The difference to vc-do-command is that this function always invokes `svn',
 and that it passes `vc-svn-global-switches' to it before FLAGS."
-  (apply 'vc-do-command buffer okstatus "svn" file
+  (apply 'vc-do-command buffer okstatus vc-svn-program file-or-list
          (if (stringp vc-svn-global-switches)
              (cons vc-svn-global-switches flags)
            (append vc-svn-global-switches
@@ -498,12 +505,41 @@ and that it passes `vc-svn-global-switches' to it before FLAGS."
           ;; Old `svn' used name="svn:this_dir", newer use just name="".
           (concat "name=\"\\(?:svn:this_dir\\)?\"[\n\t ]*"
                   "\\(?:[-a-z]+=\"[^\"]*\"[\n\t ]*\\)*?"
-                  "url=\"\\([^\"]+\\)\"") nil t)
+                  "url=\"\\(?1:[^\"]+\\)\""
+                   ;; Yet newer ones don't use XML any more.
+                   "\\|^\ndir\n[0-9]+\n\\(?1:.*\\)") nil t)
       ;; This is not a hostname but a URL.  This may actually be considered
       ;; as a feature since it allows vc-svn-stay-local to specify different
       ;; behavior for different modules on the same server.
       (match-string 1))))
 
+(defun vc-svn-resolve-when-done ()
+  "Call \"svn resolved\" if the conflict markers have been removed."
+  (save-excursion
+    (goto-char (point-min))
+    (if (not (re-search-forward "^<<<<<<< " nil t))
+        (vc-svn-command nil 0 buffer-file-name "resolved"))))
+
+;; Inspired by vc-arch-find-file-hook.
+(defun vc-svn-find-file-hook ()
+  (when (eq ?C (vc-file-getprop buffer-file-name 'vc-svn-status))
+    ;; If the file is marked as "conflicted", then we should try and call
+    ;; "svn resolved" when applicable.
+    (if (save-excursion
+          (goto-char (point-min))
+          (re-search-forward "^<<<<<<< " nil t))
+        ;; There are conflict markers.
+        (progn
+          (smerge-mode 1)
+          (add-hook 'after-save-hook 'vc-svn-resolve-when-done nil t))
+      ;; There are no conflict markers.  This is problematic: maybe it means
+      ;; the conflict has been resolved and we should immediately call "svn
+      ;; resolved", or it means that the file's type does not allow Svn to
+      ;; use conflict markers in which case we don't really know what to do.
+      ;; So let's just punt for now.
+      nil)
+    (message "There are unresolved conflicts in this file")))
+
 (defun vc-svn-parse-status (&optional filename)
   "Parse output of \"svn status\" command in the current buffer.
 Set file properties accordingly.  Unless FILENAME is non-nil, parse only
@@ -525,7 +561,9 @@ information about FILENAME and return its status."
        (unless filename (vc-file-setprop file 'vc-backend 'SVN))
        ;; Use the last-modified revision, so that searching in vc-print-log
        ;; output works.
-       (vc-file-setprop file 'vc-workfile-version (match-string 3))
+       (vc-file-setprop file 'vc-working-revision (match-string 3))
+        ;; Remember Svn's own status.
+        (vc-file-setprop file 'vc-svn-status status)
        (vc-file-setprop
         file 'vc-state
         (cond
@@ -537,7 +575,7 @@ information about FILENAME and return its status."
             'up-to-date))
          ((eq status ?A)
           ;; If the file was actually copied, (match-string 2) is "-".
-          (vc-file-setprop file 'vc-workfile-version "0")
+          (vc-file-setprop file 'vc-working-revision "0")
           (vc-file-setprop file 'vc-checkout-time 0)
           'edited)
          ((memq status '(?M ?C))
@@ -559,8 +597,8 @@ information about FILENAME and return its status."
   (and (string-match "^[a-zA-Z]" tag)
        (not (string-match "[^a-z0-9A-Z-_]" tag))))
 
-(defun vc-svn-valid-version-number-p (tag)
-  "Return non-nil if TAG is a valid version number."
+(defun vc-svn-valid-revision-number-p (tag)
+  "Return non-nil if TAG is a valid revision number."
   (and (string-match "^[0-9]" tag)
        (not (string-match "[^0-9]" tag))))