]> code.delx.au - gnu-emacs/blobdiff - lisp/vc-svn.el
*** empty log message ***
[gnu-emacs] / lisp / vc-svn.el
index c48ce7a5f9918c705956916d0a024ce2c7ed76fc..0b34c30f630ce169384ab9e82ed30f5f303f1993 100644 (file)
@@ -1,6 +1,6 @@
 ;;; vc-svn.el --- non-resident support for Subversion version-control
 
-;; Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+;; Copyright (C) 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author:      FSF (see vc.el for full credits)
 ;; Maintainer:  Stefan Monnier <monnier@gnu.org>
@@ -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,
@@ -85,32 +85,46 @@ 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
-  )
-
+;; 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
 ;;;
 
+;;; vc-svn-admin-directory is generally not defined when the
+;;; autoloaded function is called.
+
 ;;;###autoload (defun vc-svn-registered (f)
-;;;###autoload   (when (file-readable-p (expand-file-name
-;;;###autoload                           ".svn/entries" (file-name-directory 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     (when (file-readable-p (expand-file-name
+;;;###autoload                             (concat admin-dir "/entries")
+;;;###autoload                             (file-name-directory f)))
 ;;;###autoload       (load "vc-svn")
-;;;###autoload       (vc-svn-registered f)))
+;;;###autoload       (vc-svn-registered f))))
 
 ;;;###autoload
 (add-to-list 'completion-ignored-extensions ".svn/")
 
 (defun vc-svn-registered (file)
   "Check if FILE is SVN registered."
-  (when (file-readable-p (expand-file-name ".svn/entries"
+  (when (file-readable-p (expand-file-name (concat vc-svn-admin-directory
+                                                  "/entries")
                                           (file-name-directory file)))
     (with-temp-buffer
       (cd (file-name-directory file))
@@ -196,17 +210,23 @@ 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."
-  (file-directory-p (expand-file-name ".svn"
+  (file-directory-p (expand-file-name vc-svn-admin-directory
                                      (if (file-directory-p file)
                                          file
                                        (file-name-directory file)))))
@@ -215,10 +235,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))
@@ -226,7 +247,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"))))
@@ -242,6 +264,7 @@ This is only possible if SVN is responsible for FILE's directory.")
     ))
 
 (defun vc-svn-find-version (file rev buffer)
+  "SVN-specific retrieval of a specified version into a buffer."
   (apply 'vc-svn-command
         buffer 0 file
         "cat"
@@ -258,13 +281,8 @@ 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)
     (apply 'vc-svn-command nil 0 file
@@ -286,12 +304,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.
@@ -329,18 +342,23 @@ The changes are between FIRST-VERSION and SECOND-VERSION."
         (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!
@@ -357,53 +375,52 @@ 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"))
+      (insert "Working file(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"
+     (if (and (= (length files) 1) (vc-stay-local-p (car files)) (fboundp 'start-process)) 'async 0)
+     files "log"
      ;; By default Subversion only shows the log upto the working version,
      ;; 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 versions of fileset FILES."
+  (and oldvers
+       (catch 'no
+        (dolist (f files)
+          (or (equal oldvers (vc-workfile-version 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)
+                       (vc-stay-local-p files)
                       (or oldvers newvers) ; Svn diffs those locally.
                       (fboundp 'start-process))))
       (apply 'vc-svn-command buffer
             (if async 'async 0)
-            file "diff"
+            files "diff"
             (append
              switches
              (when oldvers
@@ -412,7 +429,7 @@ 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))))))
+       (buffer-size (get-buffer buffer)))))
 
 (defun vc-svn-diff-tree (dir &optional rev1 rev2)
   "Diff all files at and below DIR."
@@ -459,11 +476,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
@@ -474,13 +496,17 @@ and that it passes `vc-svn-global-switches' to it before FLAGS."
     (let ((coding-system-for-read
           (or file-name-coding-system
               default-file-name-coding-system)))
-      (vc-insert-file (expand-file-name ".svn/entries" dirname)))
+      (vc-insert-file (expand-file-name (concat vc-svn-admin-directory
+                                               "/entries")
+                                       dirname)))
     (goto-char (point-min))
     (when (re-search-forward
-          ;; Old `svn' used name="svn:dir", newer use just name="".
+          ;; 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.
@@ -493,9 +519,13 @@ information about FILENAME and return its status."
   (let (file status)
     (goto-char (point-min))
     (while (re-search-forward
-           "^[ ADMCI?!~][ MC][ L][ +][ S]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\) +" nil t)
-      (setq file (expand-file-name
-                 (buffer-substring (point) (line-end-position))))
+            ;; Ignore the files with status in [IX?].
+           "^[ ACDGMR!~][ MC][ L][ +][ S]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\) +" nil t)
+      ;; If the username contains spaces, the output format is ambiguous,
+      ;; so don't trust the output's filename unless we have to.
+      (setq file (or filename
+                     (expand-file-name
+                      (buffer-substring (point) (line-end-position)))))
       (setq status (char-after (line-beginning-position)))
       (unless (eq status ??)
        ;; `vc-BACKEND-registered' must not set vc-backend,