(add-to-list 'vc-handled-backends 'SVN)
(defcustom vc-svn-program-name "svn"
"*Name of Subversion client program, for use by Emacs's VC package."
:type 'string
:group 'vc
:version "21.2.90.2")
(defcustom vc-svn-diff-switches nil
"*A string or list of strings specifying extra switches for `svn diff' under VC."
:type '(repeat string)
:group 'vc
:version "21.2.90.2")
(defun vc-svn-registered (file)
"Return true if FILE is registered under Subversion."
(and (file-exists-p (expand-file-name ".svn/entries"
(file-name-directory file)))
(not (null (vc-svn-run-status file)))))
(put 'vc-svn-with-output-buffer 'lisp-indent-function 0)
(defmacro vc-svn-with-output-buffer (&rest body)
"Save excursion, switch to buffer ` *Subversion Output*', and erase it."
`(save-excursion
(set-buffer (get-buffer-create " *Subversion Output*"))
(erase-buffer)
,@body))
(defun vc-svn-pop-up-error (&rest args)
"Pop up the Subversion output buffer, and raise an error with ARGS."
(pop-to-buffer " *Subversion Output*")
(goto-char (point-min))
(shrink-window-if-larger-than-buffer)
(apply 'error args))
(defun vc-svn-run-status (file &optional update)
"Run `svn status -v' on FILE, and return the result.
If optional arg UPDATE is true, pass the `-u' flag to check against
the repository, across the network.
See `vc-svn-parse-status' for a description of the result."
(vc-svn-with-output-buffer
(let ((status (apply 'call-process vc-svn-program-name nil t nil
(append '("status" "-v")
(if update '("-u") '())
(list file)))))
(goto-char (point-min))
(if (not (equal 0 status)) (if (or (looking-at "\\(.*\n\\)*.*is not a working copy")
(looking-at "\\(.*\n\\)*.*is not a versioned resource")
(looking-at "\\(.*\n\\)*.*: No such file or directory"))
nil
(vc-svn-pop-up-error
"Error running Subversion to check status of `%s'"
(file-name-nondirectory file)))
(vc-svn-parse-status)))))
(defun vc-svn-parse-status ()
"Parse the output from `svn status -v' at point.
We return nil for a file not under Subversion's control,
or (STATE LOCAL CHANGED) for files that are, where:
STATE is the file's VC state (see the documentation for `vc-state'),
LOCAL is the base revision in the working copy, and
CHANGED is the last revision in which it was changed.
Both LOCAL and CHANGED are strings, not numbers.
If we passed `svn status' the `-u' flag, then CHANGED could be a later
revision than LOCAL.
If the file is newly added, LOCAL is \"0\" and CHANGED is nil."
(let ((state (vc-svn-parse-state-only)))
(cond
((not state) nil)
((looking-at "....\\s-+\\(\\*\\s-+\\)?[-0]\\s-+\\(\\?\\|[0-9]+\\)")
(list state "0" nil))
((looking-at "....\\s-+\\(\\*\\s-+\\)?\\([0-9]+\\)\\s-+\\([0-9]+\\)")
(list state
(match-string 2)
(match-string 3)))
((looking-at "^I +") nil) ((looking-at " \\{40\\}") nil) (t (error "Couldn't parse output from `svn status -v'")))))
(defun vc-svn-parse-state-only ()
"Parse the output from `svn status -v' at point, and return a state.
The documentation for the function `vc-state' describes the possible values."
(cond
((looking-at "\\?\\|^$") nil)
((looking-at " ..\\s-+\\*") 'needs-patch)
((looking-at " ") 'up-to-date)
((looking-at "\\S-+\\s-+\\*") 'needs-merge)
(t 'edited)))
(defun vc-svn-state (file)
"Return the current version control state of FILE.
For a list of possible return values, see `vc-state'.
This function should do a full and reliable state computation; it is
usually called immediately after `C-x v v'. `vc-svn-state-heuristic'
provides a faster heuristic when visiting a file.
For svn this does *not* check for updates in the repository, because
that needlessly slows down vc when the repository is remote. Instead,
we rely on Subversion to trap situations such as needing a merge
before commit."
(car (vc-svn-run-status file)))
(defun vc-svn-state-heuristic (file)
"Estimate the version control state of FILE at visiting time.
For a list of possible values, see the doc string of `vc-state'.
This is supposed to be considerably faster than `vc-svn-state'. It
just runs `svn status -v', without the `-u' flag, so it's a strictly
local operation."
(car (vc-svn-run-status file)))
(defun vc-svn-workfile-version (file)
"Return the current workfile version of FILE."
(cadr (vc-svn-run-status file)))
(defun vc-svn-checkout-model (file)
'implicit)
(defun vc-svn-register (file &optional rev comment)
"Register FILE with Subversion.
REV is an initial revision; Subversion ignores it.
COMMENT is an initial description of the file; currently this is ignored."
(vc-svn-with-output-buffer
(let ((status (call-process vc-svn-program-name nil t nil "add" file)))
(or (equal 0 status) (vc-svn-pop-up-error "Error running Subversion to add `%s'"
(file-name-nondirectory file))))))
(defun vc-svn-checkin (file rev comment)
(apply 'vc-do-command nil 0 vc-svn-program-name file
"commit" (if comment (list "-m" comment) '())))
(defun vc-svn-checkout (file &optional editable rev destfile)
"Check out revision REV of FILE into the working area.
If EDITABLE is non-nil, do a regular update, otherwise check out the
requested REV to temp file DESTFILE. If both EDITABLE and DESTFILE
are non-nil, raise an error.
If REV is non-nil, that is the revision to check out (default is
current workfile version). If REV is the empty string, that means to
check out the head of the trunk. For Subversion, that's equivalent to
passing nil."
(if editable
(progn
(when destfile
(error "VC asked Subversion to check out a file under another name"))
(when (equal rev "")
(setq rev nil))
(apply 'vc-do-command nil 0 vc-svn-program-name file
"update" (if rev (list "-r" rev) '()))
(vc-file-setprop file 'vc-workfile-version nil))
(with-temp-file destfile
(apply 'vc-do-command t 0 vc-svn-program-name file
"cat" (if (equal rev "") '() (list "-r" rev))))))
(defun vc-svn-revert (file &optional contents-done)
"Revert FILE back to the current workfile version.
If optional arg CONTENTS-DONE is non-nil, then the contents of FILE
have already been reverted from a version backup, and this function
only needs to update the status of FILE within the backend. This
function ignores the CONTENTS-DONE argument."
(vc-do-command nil 0 vc-svn-program-name file "revert"))
(defun vc-svn-merge-news (file)
"Merge recent changes into FILE.
This calls `svn update'. In the case of conflicts, Subversion puts
conflict markers into the file and leaves additional temporary files
containing the `ancestor', `mine', and `other' files.
You may need to run `svn resolved' by hand once these conflicts have
been resolved.
Returns a vc status, which is used to determine whether conflicts need
to be merged."
(prog1
(vc-do-command nil 0 vc-svn-program-name file "update")
(vc-file-setprop file 'vc-checkout-time 0)
(vc-file-setprop file 'vc-workfile-version
(vc-svn-workfile-version file))))
(defun vc-svn-print-log (file)
"Insert the revision log of FILE into the *vc* buffer."
(vc-do-command nil 'async vc-svn-program-name file "log"))
(defun vc-svn-show-log-entry (version)
"Search the log entry for VERSION in the current buffer.
Make sure it is displayed in the buffer's window."
(when (re-search-forward (concat "^-+\n\\(rev\\) "
(regexp-quote version)
":[^|]+|[^|]+| [0-9]+ lines?"))
(goto-char (match-beginning 1))
(recenter 1)))
(defun vc-svn-diff (file &optional rev1 rev2)
"Insert the diff for FILE into the *vc-diff* buffer.
If REV1 and REV2 are non-nil, report differences from REV1 to REV2.
If REV1 is nil, use the current workfile version (as found in the
repository) as the older version; if REV2 is nil, use the current
workfile contents as the newer version.
This function returns a status of either 0 (no differences found), or
1 (either non-empty diff or the diff is run asynchronously)."
(let* ((diff-switches-list
(condition-case nil
(vc-diff-switches-list svn)
(void-variable (vc-diff-switches-list 'SVN))))
(status (vc-svn-run-status file))
(local (elt status 1))
(changed (elt status 2))
(rev1 (if (and rev1 (not (equal rev1 local))) rev1))
(rev-switches-list
(cond
((and rev1 rev2) (list "-r" (format "%s:%s" rev1 rev2)))
(rev1 (list "-r" rev1))
(rev2 (list "-r" (format "%s:%s" local rev2)))
(t '())))
(async (or rev1 rev2)))
(let ((status (apply 'vc-do-command "*vc-diff*" (if async 'async 0)
vc-svn-program-name file
(append '("diff") rev-switches-list))))
(if (or async (> (buffer-size (get-buffer "*vc-diff*")) 0))
1 0))))
(defun vc-svn-find-version (file rev buffer)
(vc-do-command buffer 0 vc-svn-program-name file
"cat" "-r" rev))
(defun vc-svn-annotate-command (file buffer &optional version)
"Execute \"svn annotate\" on FILE, inserting the contents in BUFFER.
Optional arg VERSION is a revision to annotate from."
(vc-do-command buffer 0 vc-svn-program-name file "annotate"
(if version (concat "-r" version))))
(defun vc-svn-annotate-difference (point)
"Difference between the time of the line and the current time.
Return values are as defined for `current-time'."
nil)
(provide 'vc-svn)