[svk-commit] r2299 - trunk/utils

nobody at bestpractical.com nobody at bestpractical.com
Mon Feb 12 17:37:53 EST 2007


Author: nelhage
Date: Mon Feb 12 17:37:51 2007
New Revision: 2299

Added:
   trunk/utils/vc-svk.el

Log:
Adding a version of vc-svk updated with some fixes


Added: trunk/utils/vc-svk.el
==============================================================================
--- (empty file)
+++ trunk/utils/vc-svk.el	Mon Feb 12 17:37:51 2007
@@ -0,0 +1,820 @@
+;; Based upon vc-svn.el
+;; Hack--most things are very slow/may work improperly.
+
+;; Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+;;           Free Software Foundation, Inc.
+
+;; Author:      FSF (see vc.el for full credits)
+;; Maintainer:
+
+;; This file is not yet part of GNU Emacs.
+
+;; 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)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; I created this *only* to allow me to open files and do `vc-diff'.
+;; If anything else even works, it's not my fault. :) This needs a lot
+;; of love before it is a good VC backend, but it will allow you to do
+;; some minimal tasks.
+
+;; I primarily use FSF Emacs 22. It may not work with older Emacsen.
+;; Or it might, thanks to your bug reports.
+
+;; vc-svn commentary:
+;; This is preliminary support for Subversion (http://subversion.tigris.org/).
+;; It started as `sed s/cvs/svk/ 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.
+
+;;; Bugs:
+
+;; - VC-dired is (really) slow.
+;; - (svk) Most other commands are, too.
+
+;;; Changelog:
+;; * 20050707: Yikes! My original was inadvertently posted with two instances of misplaced parens/stale code and didn't work at all!
+;;
+;; * 20050711: Improve handling of files not in the repository.
+;;
+;; * Old bugs on Emacs 21.4 (are these fixed yet?):
+;; 00 (defalias) only takes 2 arguments
+;; 00 While switching to vc-dired-mode from dired mode: "No subdir-alist in lib".  vc-directory barfs with "Symbol's function definition is void: vc-stay-local-p".
+;; 00 Loading any file yields "Symbol's definition is void: assoc-string".
+;; 00 hate hate hate.
+;;
+;; * 20050816:
+;; 00 Fixed more problems with added and unknown files.
+;; 00 Fixed the `time-less-p' and `assoc-string' problems on Emacs <22.
+;; 00 Fixed some unsual cases in vc-svk-do-status.
+;; 00 Fixed vc-register (also fixes the defalias problem on Emacs <22).
+;; 00 *May* fix the vc-dired problem. I can't test this, and don't use it.
+;;
+;; * 20050817:
+;; 00 Handle directories correctly in `vc-svk-registered'.
+;; 00 A few more compatibility fixes.
+;;
+;; * 20051006:
+;; 00 Less crashing for subdirectories that aren't registered.
+;; 00 Misc. cleanups.
+;;
+;; * 20051017:
+;; 00 Tweak last fix.
+;;
+;; * 20051025:
+;; 00 Require SVK 1.03, use it to get better status.
+;; 00 Support vc-annotate (C-x v g) (more to be done).
+;;
+;; * 20051026: (The first new features in vc-svk that aren't in vc-svn!)
+;; 00 Accurate coloring in vc-annotate. If you don't like the lag while the cache builds, set `vc-svk-annotate-absolutely' to nil.
+;; 00 vc-annotate works in Emacs 21.
+;;
+;; * 20051102:
+;; 00 One line fix for symlink to one WD inside another.
+;;
+;; * 20060219:
+;; 00 Fix some rare crashes for un-added subtrees.
+;; 00 New feature: `vc-annotate-revision-previous-to-line'
+;;
+;; * 20060207 (Nelson Elhage):
+;; 00 vc-svk-co-paths looks at svk checkout --list, instead of parsing .svk/config, so it works properly with SVK 2.0
+;;
+;; * 20060207 (Nelson Elhage)
+;; 00 Checked into SVK subversion
+;; 00 Modified vc-svk-parse-parse-status to remove all svn-only cruft and fixed vc-svk-status-file-re
+
+
+;;; Code:
+
+(eval-when-compile
+  (require 'vc))
+(require 'cl)
+(require 'time-date)
+
+;; Compatibility with Emacs <22
+
+(if (fboundp 'time-less-p)
+    (defalias 'vc-svk-time-less-p 'time-less-p)
+  (defun vc-svk-time-less-p (t1 t2)
+    "Say whether time value T1 is less than time value T2."
+    (with-decoded-time-value ((high1 low1 micro1 t1)
+                              (high2 low2 micro2 t2))
+      (or (< high1 high2)
+          (and (= high1 high2)
+               (or (< low1 low2)
+                   (and (= low1 low2)
+                        (< micro1 micro2))))))))
+
+(if (fboundp 'assoc-string)
+    (defalias 'vc-svk-assoc-string 'assoc-string)
+  (defun vc-svk-assoc-string (key alist)
+    (assoc-default key alist
+                   (lambda (a b)
+                     (and (stringp a) (stringp b) (string-equal a b))))))
+
+;; SVK repositories are (almost always? all?) local anyway.
+(defmacro vc-svk-stay-local-p (file) nil)
+
+(if (fboundp 'vc-switches)
+    (defalias 'vc-svk-switches 'vc-switches)
+  (defun vc-svk-switches (backend op)
+    (let ((switches
+           (or (if backend
+                   (let ((sym (vc-make-backend-sym
+                               backend (intern (concat (symbol-name op)
+                                                       "-switches")))))
+                     (if (boundp sym) (symbol-value sym))))
+               (let ((sym (intern (format "vc-%s-switches" (symbol-name op)))))
+                 (if (boundp sym) (symbol-value sym)))
+               (cond
+                ((eq op 'diff) diff-switches)))))
+      (if (stringp switches) (list switches)
+        ;; If not a list, return nil.
+        ;; This is so we can set vc-diff-switches to t to override
+        ;; any switches in diff-switches.
+        (if (listp switches) switches)))))
+
+(unless (boundp 'vc-disable-async-diff)
+  ;; pessimistic assumption
+  (setq vc-disable-async-diff t))
+
+(if (boundp 'vc-annotate-parent-file)
+    (defun vc-svk-annotate-parent-file ()
+      vc-annotate-parent-file)
+  (defun vc-svk-annotate-parent-file ()
+    (buffer-file-name vc-parent-buffer)))
+
+(if (< emacs-major-version 22)
+    (defun vc-svk-date-to-day (date)
+      ;; SVN gives e.g. "2005-10-26T05:34:02.209866Z\n" which are
+      ;; rejected by Emacs <22.
+      (let ((i (string-match "T" date)))
+        (date-to-day (if i
+                         (concat (substring date 0 i)
+                                 " "
+                                 (substring date (1+ i)))
+                       date))))
+  (defalias 'vc-svk-date-to-day 'date-to-day))
+
+;;;
+;;; Customization options
+;;;
+
+(defcustom vc-svk-global-switches nil
+  "*Global switches to pass to any SVK command."
+  :type '(choice (const :tag "None" nil)
+                 (string :tag "Argument String")
+                 (repeat :tag "Argument List"
+                         :value ("")
+                         string))
+  :version "21.4"
+  :group 'vc)
+
+(defcustom vc-svk-register-switches nil
+  "*Extra switches for registering a file into SVK.
+A string or list of strings passed to the checkin program by
+\\[vc-register]."
+  :type '(choice (const :tag "None" nil)
+                 (string :tag "Argument String")
+                 (repeat :tag "Argument List"
+                         :value ("")
+                         string))
+  :version "21.4"
+  :group 'vc)
+
+(defcustom vc-svk-diff-switches
+  t                           ;`svk' doesn't support common args like -c or -b.
+  "String or list of strings specifying extra switches for svk diff under VC.
+If nil, use the value of `vc-diff-switches'.
+If you want to force an empty list of arguments, use t."
+  :type '(choice (const :tag "Unspecified" nil)
+                 (const :tag "None" t)
+                 (string :tag "Argument String")
+                 (repeat :tag "Argument List"
+                         :value ("")
+                         string))
+  :version "21.4"
+  :group 'vc)
+
+(defcustom vc-svk-header (or (cdr (assoc 'SVK vc-header-alist)) '("\$Id\$"))
+  "*Header keywords to be inserted by `vc-insert-headers'."
+  :version "21.4"
+  :type '(repeat string)
+  :group 'vc)
+
+(defconst vc-svk-use-edit nil
+  ;; Subversion does not provide this feature (yet).
+  "*Non-nil means to use `svk edit' to \"check out\" a file.
+This is only meaningful if you don't use the implicit checkout model
+\(i.e. if you have $SVKREAD set)."
+  ;; :type 'boolean
+  ;; :version "21.4"
+  ;; :group 'vc
+  )
+
+(defconst vc-svk-status-file-re
+  "^[ ADMCI?!~][ MC][ +] +\\([-0-9?]+\\) +\\([0-9?]+\\) +\\([^ ]+\\) +")
+
+;;;
+;;; State-querying functions
+;;;
+
+;;; FIXME
+;;;###autoload (add-to-list 'vc-handled-backends 'SVK)
+;;;###autoload (defun vc-svk-registered (file)
+;;;###autoload   (when (string-match
+;;;###autoload          "^Checkout Path:"
+;;;###autoload          (shell-command-to-string (concat "svk info "
+;;;###autoload                                           (expand-file-name file))))
+;;;###autoload     (setq file nil)
+;;;###autoload     (load "vc-svk")
+;;;###autoload     (vc-svk-registered file)))
+
+(add-to-list 'vc-handled-backends 'SVK)
+(defun vc-svk-registered (file)
+  "Check if FILE is SVK registered."
+
+  (let ((lfile (file-truename file))   ; SVK stores truenames
+        (file-buffer (current-buffer)))
+    (when (vc-svk-co-path-p lfile)
+      (save-window-excursion            ; being left in some random buffer
+                                        ; confuses `vc-find-file-hook'
+        (with-temp-buffer
+          (cd (file-name-directory lfile))
+          (condition-case nil
+              (progn
+                (vc-svk-do-status lfile)
+                (vc-svk-parse-status t (unless (string-equal file lfile)
+                                         file))
+                (eq 'SVK (vc-file-getprop file 'vc-backend)))
+            ;; We can't find an `svk' executable.  We could also deregister SVK.
+            (file-error nil)))))))
+
+(defun vc-svk-state (file &optional localp)
+  "SVK-specific version of `vc-state'."
+  (setq localp (or localp (vc-svk-stay-local-p file)))
+  (with-temp-buffer
+    (cd (file-name-directory file))
+    (vc-svk-do-status file)
+    (vc-svk-parse-status localp)
+    (vc-file-getprop file 'vc-state)))
+
+(defun vc-svk-state-heuristic (file)
+  "SVK-specific state heuristic."
+  (vc-svk-state file 'local))
+
+(defun vc-svk-dir-state (dir &optional localp)
+  "Find the SVK state of all files in DIR."
+  (setq localp (or localp (vc-svk-stay-local-p dir)))
+  (let ((default-directory dir))
+    ;; Don't specify DIR in this command, the default-directory is
+    ;; enough.  Otherwise it might fail with remote repositories.
+    (with-temp-buffer
+      (vc-svk-do-status dir)
+      (vc-svk-parse-status localp))))
+
+(defun vc-svk-workfile-version (file)
+  "SVK-specific version of `vc-workfile-version'."
+  ;; There is no need to consult RCS headers under SVK, because we
+  ;; get the workfile version for free when we recognize that a file
+  ;; is registered in SVK.
+  (vc-svk-registered file)
+  (vc-file-getprop file 'vc-workfile-version))
+
+(defun vc-log-version-at-point ()
+  "Extract the revision number at point as a string."
+  (buffer-substring-no-properties (1+ (point))
+                                  (save-excursion
+                                    (search-forward ":" nil t)
+                                    (1- (point)))))
+
+(defun vc-svk-previous-version (file rev)
+  "The greatest revision number string before REV in which FILE was modified."
+  ;; Parse log -q to find it. Non-optimal.
+  (with-temp-buffer
+    (vc-svk-command t 0 file "log" "-q")
+    (goto-char (point-min))
+    ;; If the file was modified in rev we can jump to it exactly.
+    (search-forward-regexp (concat "^r" rev) nil t)
+    (goto-char (match-beginning 0))
+    (let ((revnum (string-to-number rev)))
+      (unless (= revnum (string-to-number (vc-log-version-at-point)))
+        ;; Otherwise, go line-by-line looking for it.
+        (goto-char (point-min))
+        (forward-line 1)
+        (while (and (bolp) (< revnum
+                              (string-to-number (vc-log-version-at-point))))
+          (forward-line 2))
+        (forward-line -2))
+      ;; The line with the desired revnum:
+      (forward-line 2)
+      (when (bolp)
+        (vc-log-version-at-point)))))
+
+(defun vc-svk-checkout-model (file)
+  "SVK-specific version of `vc-checkout-model'."
+  ;; It looks like Subversion has no equivalent of CVSREAD.
+  'implicit)
+
+;; vc-svk-mode-line-string doesn't exist because the default implementation
+;; works just fine.
+
+(defun vc-svk-dired-state-info (file)
+  "SVK-specific version of `vc-dired-state-info'."
+  (let ((svk-state (vc-state file)))
+    (cond ((eq svk-state 'edited)
+           (if (equal (vc-workfile-version file) "0")
+               "(added)" "(modified)"))
+          ((eq svk-state 'needs-patch) "(patch)")
+          ((eq svk-state 'needs-merge) "(merge)"))))
+
+;;;
+;;; State-changing functions
+;;;
+
+(defun vc-svk-register (file &optional rev comment)
+  "Register FILE into the SVK version-control system.
+COMMENT can be used to provide an initial description of FILE.
+
+`vc-register-switches' and `vc-svk-register-switches' are passed to
+the SVK command (in that order)."
+  (apply 'vc-svk-command nil 0 file "add" (vc-svk-switches 'SVK 'register)))
+
+(defun vc-svk-could-register (file)
+  "Return non-nil if FILE could be registered in SVK.
+This is only possible if SVK is responsible for FILE's directory."
+  (and (vc-svk-co-path-of file)
+       (vc-svk-registered (file-name-directory
+                           (vc-svk-file-name-no-trailsep file)))))
+
+(defun vc-svk-init-version () "1")
+
+(defun vc-svk-checkin (file rev comment)
+  "SVK-specific version of `vc-backend-checkin'."
+  (let ((status (apply
+                 'vc-svk-command nil 1 file "ci"
+                 (nconc (list "-m" comment) (vc-svk-switches 'SVK 'checkin)))))
+    (set-buffer "*vc*")
+    (goto-char (point-min))
+    (unless (equal status 0)
+      ;; Check checkin problem.
+      (cond
+       ((search-forward "Transaction is out of date" nil t)
+        (vc-file-setprop file 'vc-state 'needs-merge)
+        (error (substitute-command-keys
+                (concat "Up-to-date check failed: "
+                        "type \\[vc-next-action] to merge in changes"))))
+       (t
+        (pop-to-buffer (current-buffer))
+        (goto-char (point-min))
+        (shrink-window-if-larger-than-buffer)
+        (error "Check-in failed"))))
+    ;; Update file properties
+    ;; (vc-file-setprop
+    ;;  file 'vc-workfile-version
+    ;;  (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2))
+    ))
+
+(defun vc-svk-find-version (file rev buffer)
+  (apply 'vc-svk-command
+         buffer 0 file
+         "cat"
+         (and rev (not (string= rev ""))
+              (concat "-r" rev))
+         (vc-svk-switches 'SVK 'checkout)))
+
+(defun vc-svk-checkout (file &optional editable rev)
+  (message "Checking out %s..." file)
+  (with-current-buffer (or (get-file-buffer file) (current-buffer))
+    (vc-call update file editable rev (vc-svk-switches 'SVK 'checkout)))
+  (vc-mode-line file)
+  (message "Checking out %s...done" file))
+
+(defun vc-svk-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 `svk-edit' if requested).
+      (and editable (not (eq (vc-svk-checkout-model file) 'implicit))
+           (if vc-svk-use-edit
+               (vc-svk-command nil 0 file "edit")
+             (set-file-modes file (logior (file-modes file) 128))
+             (if (equal file buffer-file-name) (toggle-read-only -1))))
+    ;; Check out a particular version (or recreate the file).
+    (vc-file-setprop file 'vc-workfile-version nil)
+    (apply 'vc-svk-command nil 0 file
+           "update"
+           ;; default for verbose checkout: clear the sticky tag so
+           ;; that the actual update will get the head of the trunk
+           (cond
+            ((null rev) "-rBASE")
+            ((or (eq rev t) (equal rev "")) nil)
+            (t (concat "-r" rev)))
+           switches)))
+
+(defun vc-svk-delete-file (file)
+  (vc-svk-command nil 0 file "remove"))
+
+(defun vc-svk-rename-file (old new)
+  (vc-svk-command nil 0 new "move" (file-relative-name old)))
+
+(defun vc-svk-revert (file &optional contents-done)
+  "Revert FILE to the version it was based on."
+  (unless contents-done
+    (vc-svk-command nil 0 file "revert"))
+  (unless (eq (vc-checkout-model file) 'implicit)
+    (if vc-svk-use-edit
+        (vc-svk-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)))))
+
+(defun vc-svk-merge (file first-version &optional second-version)
+  "Merge changes into current working copy of FILE.
+The changes are between FIRST-VERSION and SECOND-VERSION."
+  (vc-svk-command nil 0 file
+                 "merge"
+                 "-r" (if second-version
+                        (concat first-version ":" second-version)
+                      first-version))
+  (vc-file-setprop file 'vc-state 'edited)
+  (with-current-buffer (get-buffer "*vc*")
+    (goto-char (point-min))
+    (if (looking-at "C  ")
+        1                                ; signal conflict
+      0)))                                ; signal success
+
+(defun vc-svk-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-checkout-time 0)
+  (vc-svk-command nil 0 file "update")
+  ;; Analyze the merge result reported by SVK, and set
+  ;; file properties accordingly.
+  (with-current-buffer (get-buffer "*vc*")
+    (goto-char (point-min))
+    ;; get new workfile version
+    (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))
+    ;; 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]  \\)?"
+                       (regexp-quote (file-name-nondirectory file)))
+               nil t)
+              (cond
+               ;; Merge successful, we are in sync with repository now
+               ((string= (match-string 1) "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  ")
+                (vc-file-setprop file 'vc-state 'edited)
+                0);; indicate success to the caller
+               ;; Conflicts detected!
+               (t
+                (vc-file-setprop file 'vc-state 'edited)
+                1);; signal the error to the caller
+               )
+            (pop-to-buffer "*vc*")
+            (error "Couldn't analyze svk update result")))
+      (message "Merging changes into %s...done" file))))
+
+;;;
+;;; History functions
+;;;
+
+(defun vc-svk-print-log (file &optional buffer)
+  "Get change log associated with FILE."
+  (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"))
+    (vc-svk-command
+     buffer
+     (if (and (vc-svk-stay-local-p file) (fboundp 'start-process)) 'async 0)
+     file "log")))
+
+(defun vc-svk-diff (file &optional oldvers newvers buffer)
+  "Get a difference report using SVK 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 svk 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 "svk diff".
+        (apply 'vc-do-command buffer
+               1 "diff" file
+               (append (vc-svk-switches nil 'diff) '("/dev/null")))
+        ;; Even if it's empty, it's locally modified.
+        1)
+    (let* ((switches
+            (if vc-svk-diff-switches
+                (vc-svk-switches 'SVK 'diff)
+              (list "-x" (mapconcat 'identity (vc-svk-switches nil 'diff) " "))))
+           (async (and (not vc-disable-async-diff)
+                       (vc-svk-stay-local-p file)
+                       (or oldvers newvers) ; Svk diffs those locally.
+                       (fboundp 'start-process))))
+      (apply 'vc-svk-command buffer
+             (if async 'async 0)
+             file "diff"
+             (append
+              switches
+              (when oldvers
+                (list "-r" (if newvers (concat oldvers ":" newvers)
+                             oldvers)))))
+      (if async 1                      ; async diff => pessimistic assumption
+        ;; For some reason `svk diff' does not return a useful
+        ;; status w.r.t whether the diff was empty or not.
+        (buffer-size (get-buffer buffer))))))
+
+(defun vc-svk-diff-tree (dir &optional rev1 rev2)
+  "Diff all files at and below DIR."
+  (vc-svk-diff (file-name-as-directory dir) rev1 rev2))
+
+;;;
+;;; Snapshot system
+;;;
+
+(defun vc-svk-create-snapshot (dir name branchp)
+  "Assign to DIR's current version 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."
+  (vc-svk-command nil 0 dir "copy" name)
+  (when branchp (vc-svk-retrieve-snapshot dir name nil)))
+
+(defun vc-svk-retrieve-snapshot (dir name update)
+  "Retrieve a snapshot at and below DIR.
+NAME is the name of the snapshot; if it is empty, do a `svk update'.
+If UPDATE is non-nil, then update (resynch) any affected buffers.
+NAME is assumed to be a URL."
+  (vc-svk-command nil 0 dir "switch" name)
+  ;; FIXME: parse the output and obey `update'.
+  )
+
+;;;
+;;; Annotate
+;;;
+
+(defun vc-svk-annotate-command (file buf &optional rev)
+  (vc-svk-command buf 0 file "annotate" (if rev (concat "-r" rev)))
+  (with-current-buffer buf
+    (goto-char (point-min))
+    (delete-region (point) (line-end-position 3))))
+
+(defvar vc-svk-annotate-absolutely t
+  "Non-nil to ask SVK about each revision's date in `vc-svk-annotate-time'.
+Otherwise date annotations by revision number. There is a delay to get
+the revision dates at first and a little memory to cache them.")
+;; Keys: "<rev num>/depot/"
+(defvar vc-svk-annotate-rev-days (make-hash-table :test 'equal))
+
+(defvar vc-svk-annotate-buffer-depot nil)
+(make-variable-buffer-local 'vc-svk-buffer-depot)
+
+(defun vc-svk-annotate-time-of-rev (rev)
+  (let* ((file (vc-svk-annotate-parent-file))
+         (rev (or rev
+                  (vc-workfile-version file)))
+         (key (concat rev
+                      (or vc-svk-annotate-buffer-depot
+                          (setq vc-svk-annotate-buffer-depot
+                                (vc-svk-repository-hostname file))))))
+    (if vc-svk-annotate-absolutely
+        (or (gethash key vc-svk-annotate-rev-days)
+            (setf (gethash key vc-svk-annotate-rev-days)
+                  (vc-svk-date-to-day
+                   (shell-command-to-string
+                    (apply 'concat
+                           "svk propget --revprop svn:date -r" rev
+                           vc-svk-global-switches)))))
+      ;; Like SVN, arbitrarily assume 10 commmits per day.
+      (/ (string-to-number rev) 10.0))))
+
+(defun vc-svk-annotate-current-time ()
+  (vc-svk-annotate-time-of-rev vc-annotate-parent-rev))
+
+(defun vc-svk-annotate-time ()
+  (vc-svk-annotate-time-of-rev (vc-svk-annotate-extract-revision-at-line)))
+(defun vc-svk-annotate-difference (point)
+  ;; Emacs 21 compatibility.
+  (unless (= point (point-max))
+    (goto-char point)
+    (- (time-to-days (current-time))
+       (vc-svk-annotate-time))))
+
+(defun vc-svk-annotate-extract-revision-at-line ()
+  (save-excursion
+    (beginning-of-line)
+    (if (re-search-forward "^[ 	]+\\([0-9]+\\)[ 	]+("
+                           (line-end-position) t)
+        (match-string-no-properties 1)
+      nil)))
+
+;;;
+;;; Miscellaneous
+;;;
+
+;; Subversion makes backups for us, so don't bother.
+;; (defalias 'vc-svk-make-version-backups-p 'vc-svk-stay-local-p
+;;   "Return non-nil if version backups should be made for FILE.")
+
+(defun vc-svk-check-headers ()
+  "Check if the current file has any headers in it."
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
+\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
+
+;;;
+;;; Internal functions
+;;;
+
+(defun vc-svk-command (buffer okstatus file &rest flags)
+  "A wrapper around `vc-do-command' for use in vc-svk.el.
+The difference to vc-do-command is that this function always invokes `svk',
+and that it passes `vc-svk-global-switches' to it before FLAGS."
+  (if (file-exists-p (expand-file-name "~/.svk/lock"))
+      (error "Another svk might be running; remove ~/.svk/lock if not.")
+      (let ((args (if (stringp vc-svk-global-switches)
+                      (cons vc-svk-global-switches flags)
+                      (append vc-svk-global-switches
+                              flags))))
+        (apply 'vc-do-command buffer okstatus "svk" file
+               args))))
+
+(defun vc-svk-repository-hostname (file)
+  "Ask SVK for the depot of FILE."
+  ;; Used by vc-stay-local-p to make that decision per-hostname/path.
+  ;; Used in vc-svk to get depots.
+  (let ((info (vc-svk-do-info-string file)))
+    (when (string-match "Depot Path: \\(/.*?/\\)" info)
+      (match-string 1 info))))
+
+(defun vc-svk-parse-status (localp &optional linked-file)
+  "Parse output of `vc-svk-do-status' in the current buffer.
+Set file properties accordingly."
+  (let (file status)
+    (goto-char (point-min))
+    (while (re-search-forward vc-svk-status-file-re nil t)
+      (setq file (or linked-file
+                     (expand-file-name
+                      (buffer-substring (point) (line-end-position)))))
+      (setq status (char-after (line-beginning-position)))
+      (unless (eq status ??)
+        (vc-file-setprop file 'vc-backend 'SVK)
+        ;; Use the last-modified revision, so that searching in vc-print-log
+        ;; output works.
+        (vc-file-setprop file 'vc-workfile-version (match-string 2))
+        (vc-file-setprop
+         file 'vc-state
+         (cond
+           ((eq status ?\ )
+            (vc-file-setprop file 'vc-checkout-time
+                             (nth 5 (file-attributes file)))
+            '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-checkout-time 0)
+            'edited)
+           ((memq status '(?M ?C))
+            'edited)
+           (t 'edited)))))))
+
+(defun vc-svk-dir-state-heuristic (dir)
+  "Find the SVK state of all files in DIR, using only local information."
+  (vc-svk-dir-state dir 'local))
+
+(defun vc-svk-valid-symbolic-tag-name-p (tag)
+  "Return non-nil if TAG is a valid symbolic tag name."
+  ;; According to the SVK manual, a valid symbolic tag must start with
+  ;; an uppercase or lowercase letter and can contain uppercase and
+  ;; lowercase letters, digits, `-', and `_'.
+  (and (string-match "^[a-zA-Z]" tag)
+       (not (string-match "[^a-z0-9A-Z-_]" tag))))
+
+(defun vc-svk-valid-version-number-p (tag)
+  "Return non-nil if TAG is a valid version number."
+  (and (string-match "^[0-9]" tag)
+       (not (string-match "[^0-9]" tag))))
+
+(defun vc-svk-do-status (file)
+  ;; Don't crash if SVK didn't really have the file (e.g. un-added
+  ;; subdir of co path). Each such error message must be parsed
+  ;; equivilient to ? in `vc-svk-parse-status'.
+  (ignore-errors
+    (vc-svk-command t 0 file "status" "-Nv"))
+  ;; SVN always puts file at the top of status output.
+  ;; SVK puts it at the bottom if file is a dir, and additionally may
+  ;; output it as a relative path.
+  (when (file-directory-p file)
+    (save-excursion
+      (previous-line 1)
+      (delete-region (point)
+                     (point-min))
+      (delete-region (re-search-forward vc-svk-status-file-re nil t)
+                     (line-end-position))
+      (insert file))))
+
+(defsubst vc-svk-do-info-string (file)
+  (shell-command-to-string (concat "svk info "
+                                   (expand-file-name file))))
+
+(defun vc-svk-file-name-no-trailsep (file)
+  "Return filename minus trailing separators.
+
+Caution! Cheats and onlya removes them when Emacs is known to put
+them and they matter to vc-svk."
+  (let ((end (1- (length file))))
+    (if (and (file-directory-p file)
+             (string-equal (substring file end) "/"))
+        (substring file 0 end)
+      file)))
+
+(defvar vc-svk-co-paths nil)
+(defun vc-svk-co-paths ()
+  (interactive)
+  (let ((config "~/.svk/config")
+        mtime)
+    (when (file-readable-p config)
+      (setq mtime (nth 5 (file-attributes "~/.svk/config")))
+      (unless (and vc-svk-co-paths           ; has not it been loaded?
+                   (vc-svk-time-less-p mtime ; is it unmodified since?
+                                       (car vc-svk-co-paths)))
+        ;; (re)load
+        (setq vc-svk-co-paths (list mtime))
+        (with-temp-buffer
+          (vc-svk-command t 0 nil "checkout" "--list")
+          (goto-char (point-min))
+          (when (search-forward "==========\n" nil t)
+            (while (re-search-forward "^ +\\(.+\\) *\t\\(.+\\)$" nil t)
+              (add-to-list 'vc-svk-co-paths
+                           (list (match-string-no-properties 2)
+                                 (match-string-no-properties 1))))))
+        (setq vc-svk-co-paths (nreverse vc-svk-co-paths)))))
+  vc-svk-co-paths)
+
+;; These will often avoid slow calls to `vc-svk-command'.
+(defun vc-svk-co-path-p (file)
+  "Whether SVK manages a parent directory of FILE.
+Note that this does not try to guarantee SVK manages this particular
+subdirectory. That's for the full `vc-svk-registered' to decide."
+  (vc-svk-co-paths)
+  (block nil
+    (unless (file-exists-p file)
+      (return nil))
+    ;; Check file and each parent dir for svk-ness
+    ;; Yeah, this is not the greatest. And it's UNIX-centric.
+    (while (and file (not (string-equal file "/")))
+      ;; For both SVK and file-name-directory, dirnames must not
+      ;; include trailing /
+      (setq file (substring file 0 (string-match "/\\'" file)))
+      (if (vc-svk-assoc-string file vc-svk-co-paths)
+          (return t)
+        (setq file (file-name-directory file))))))
+
+(defun vc-svk-co-path-of (file)
+  "Return the CO path holding FILE, or nil."
+  (car (find-if #'(lambda (codir)
+                    (and (stringp codir)
+                         (string-match (concat "^" codir) file)))
+                vc-svk-co-paths
+                :key 'first)))
+
+(provide 'vc-svk)
+
+;;; Local Variables:
+;;; indent-tabs-mode: nil
+;;; End:
+;;; vc-svk.el ends here


More information about the svk-commit mailing list