* help-fns.el (describe-variable): Treat list return values from
[bpt/emacs.git] / lisp / vc-svn.el
CommitLineData
1fd3454a
SM
1;;; vc-svn.el --- non-resident support for Subversion version-control
2
ae940284 3;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009
a80a6b03 4;; Free Software Foundation, Inc.
1fd3454a
SM
5
6;; Author: FSF (see vc.el for full credits)
7;; Maintainer: Stefan Monnier <monnier@gnu.org>
8
9;; This file is part of GNU Emacs.
10
eb3fa2cf 11;; GNU Emacs is free software: you can redistribute it and/or modify
1fd3454a 12;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
1fd3454a
SM
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
eb3fa2cf 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
1fd3454a
SM
23
24;;; Commentary:
25
f9914e54
ER
26;; Sync'd with Subversion's vc-svn.el as of revision 5801. but this version
27;; has been extensively modified since to handle filesets.
1fd3454a 28
1fd3454a
SM
29;;; Code:
30
31(eval-when-compile
32 (require 'vc))
33
34;;;
35;;; Customization options
36;;;
37
f9d5dc48
GM
38;; FIXME there is also svnadmin.
39(defcustom vc-svn-program "svn"
40 "Name of the SVN executable."
41 :type 'string
42 :group 'vc)
43
1fd3454a 44(defcustom vc-svn-global-switches nil
f9d5dc48 45 "Global switches to pass to any SVN command."
1fd3454a
SM
46 :type '(choice (const :tag "None" nil)
47 (string :tag "Argument String")
48 (repeat :tag "Argument List"
49 :value ("")
50 string))
bf247b6e 51 :version "22.1"
1fd3454a
SM
52 :group 'vc)
53
54(defcustom vc-svn-register-switches nil
c8d6b4bc 55 "Switches for registering a file into SVN.
1fd3454a 56A string or list of strings passed to the checkin program by
c8d6b4bc
GM
57\\[vc-register]. If nil, use the value of `vc-register-switches'.
58If t, use no switches."
59 :type '(choice (const :tag "Unspecified" nil)
60 (const :tag "None" t)
1fd3454a 61 (string :tag "Argument String")
c8d6b4bc 62 (repeat :tag "Argument List" :value ("") string))
bf247b6e 63 :version "22.1"
1fd3454a
SM
64 :group 'vc)
65
8462aca5
SM
66(defcustom vc-svn-diff-switches
67 t ;`svn' doesn't support common args like -c or -b.
68 "String or list of strings specifying extra switches for svn diff under VC.
9751169a
GM
69If nil, use the value of `vc-diff-switches' (or `diff-switches'),
70together with \"-x --diff-cmd=diff\" (since svn diff does not
71support the default \"-c\" value of `diff-switches'). If you
72want to force an empty list of arguments, use t."
8462aca5
SM
73 :type '(choice (const :tag "Unspecified" nil)
74 (const :tag "None" t)
1fd3454a
SM
75 (string :tag "Argument String")
76 (repeat :tag "Argument List"
77 :value ("")
78 string))
bf247b6e 79 :version "22.1"
1fd3454a
SM
80 :group 'vc)
81
82(defcustom vc-svn-header (or (cdr (assoc 'SVN vc-header-alist)) '("\$Id\$"))
f9d5dc48 83 "Header keywords to be inserted by `vc-insert-headers'."
bf247b6e 84 :version "22.1"
1fd3454a
SM
85 :type '(repeat string)
86 :group 'vc)
87
fc2fb30c
SM
88;; We want to autoload it for use by the autoloaded version of
89;; vc-svn-registered, but we want the value to be compiled at startup, not
90;; at dump time.
91;; ;;;###autoload
92(defconst vc-svn-admin-directory
93 (cond ((and (memq system-type '(cygwin windows-nt ms-dos))
ace2fad9
CY
94 (getenv "SVN_ASP_DOT_NET_HACK"))
95 "_svn")
96 (t ".svn"))
97 "The name of the \".svn\" subdirectory or its equivalent.")
98
8cdd17b4
ER
99;;; Properties of the backend
100
70e2f6c7
ER
101(defun vc-svn-revision-granularity () 'repository)
102(defun vc-svn-checkout-model (files) 'implicit)
103
1fd3454a
SM
104;;;
105;;; State-querying functions
106;;;
107
ace2fad9
CY
108;;; vc-svn-admin-directory is generally not defined when the
109;;; autoloaded function is called.
110
1fd3454a 111;;;###autoload (defun vc-svn-registered (f)
ace2fad9 112;;;###autoload (let ((admin-dir (cond ((and (eq system-type 'windows-nt)
fc2fb30c
SM
113;;;###autoload (getenv "SVN_ASP_DOT_NET_HACK"))
114;;;###autoload "_svn")
115;;;###autoload (t ".svn"))))
ace2fad9 116;;;###autoload (when (file-readable-p (expand-file-name
fc2fb30c
SM
117;;;###autoload (concat admin-dir "/entries")
118;;;###autoload (file-name-directory f)))
1fd3454a 119;;;###autoload (load "vc-svn")
ace2fad9 120;;;###autoload (vc-svn-registered f))))
1fd3454a
SM
121
122(defun vc-svn-registered (file)
123 "Check if FILE is SVN registered."
ace2fad9
CY
124 (when (file-readable-p (expand-file-name (concat vc-svn-admin-directory
125 "/entries")
1fd3454a
SM
126 (file-name-directory file)))
127 (with-temp-buffer
128 (cd (file-name-directory file))
aaef169d 129 (let ((status
df4da7f4
SM
130 (condition-case nil
131 ;; Ignore all errors.
132 (vc-svn-command t t file "status" "-v")
133 ;; Some problem happened. E.g. We can't find an `svn'
134 ;; executable. We used to only catch `file-error' but when
135 ;; the process is run on a remote host via Tramp, the error
136 ;; is only reported via the exit status which is turned into
137 ;; an `error' by vc-do-command.
138 (error nil))))
139 (when (eq 0 status)
b5e791bd
DN
140 (let ((parsed (vc-svn-parse-status file)))
141 (and parsed (not (memq parsed '(ignored unregistered))))))))))
1fd3454a
SM
142
143(defun vc-svn-state (file &optional localp)
144 "SVN-specific version of `vc-state'."
77bf3f54 145 (setq localp (or localp (vc-stay-local-p file 'SVN)))
1fd3454a
SM
146 (with-temp-buffer
147 (cd (file-name-directory file))
148 (vc-svn-command t 0 file "status" (if localp "-v" "-u"))
bc8c1bb4 149 (vc-svn-parse-status file)))
1fd3454a
SM
150
151(defun vc-svn-state-heuristic (file)
152 "SVN-specific state heuristic."
153 (vc-svn-state file 'local))
154
a80a6b03
GM
155;; FIXME it would be better not to have the "remote" argument,
156;; but to distinguish the two output formats based on content.
157(defun vc-svn-after-dir-status (callback &optional remote)
c222c25f 158 (let ((state-map '((?A . added)
b2ee56c9 159 (?C . conflict)
b2ee56c9
SM
160 (?I . ignored)
161 (?M . edited)
e6c01f09 162 (?D . removed)
b2ee56c9
SM
163 (?R . removed)
164 (?? . unregistered)
165 ;; This is what vc-svn-parse-status does.
166 (?~ . edited)))
524a655d 167 (re (if remote "^\\(.\\)..... \\([ *]\\) +\\(?:[-0-9]+\\)? \\(.*\\)$"
a80a6b03
GM
168 ;; Subexp 2 is a dummy in this case, so the numbers match.
169 "^\\(.\\)....\\(.\\) \\(.*\\)$"))
c222c25f
DN
170 result)
171 (goto-char (point-min))
a80a6b03 172 (while (re-search-forward re nil t)
c222c25f 173 (let ((state (cdr (assq (aref (match-string 1) 0) state-map)))
a80a6b03
GM
174 (filename (match-string 3)))
175 (and remote (string-equal (match-string 2) "*")
176 ;; FIXME are there other possible combinations?
177 (cond ((eq state 'edited) (setq state 'needs-merge))
178 ((not state) (setq state 'needs-update))))
524a655d 179 (when (and state (not (string= "." filename)))
1b3f2d4e 180 (setq result (cons (list filename state) result)))))
b2ee56c9 181 (funcall callback result)))
c222c25f 182
c1b51374 183(defun vc-svn-dir-status (dir callback)
c222c25f
DN
184 "Run 'svn status' for DIR and update BUFFER via CALLBACK.
185CALLBACK is called as (CALLBACK RESULT BUFFER), where
186RESULT is a list of conses (FILE . STATE) for directory DIR."
a80a6b03 187 ;; FIXME should this rather be all the files in dir?
1826af5e
DN
188 ;; FIXME: the vc-stay-local-p logic below is disabled, it ends up
189 ;; calling synchronously (vc-svn-registered DIR) => calling svn status -v DIR
190 ;; which is VERY SLOW for big trees and it makes emacs
191 ;; completely unresponsive during that time.
77bf3f54 192 (let* ((local (and nil (vc-stay-local-p dir 'SVN)))
1826af5e 193 (remote (or t (not local) (eq local 'only-file))))
a80a6b03
GM
194 (vc-svn-command (current-buffer) 'async nil "status"
195 (if remote "-u"))
115c0061 196 (vc-exec-after
a80a6b03 197 `(vc-svn-after-dir-status (quote ,callback) ,remote))))
f8e89f19 198
847fb889
DN
199(defun vc-svn-dir-status-files (dir files default-state callback)
200 (apply 'vc-svn-command (current-buffer) 'async nil "status" files)
201 (vc-exec-after
202 `(vc-svn-after-dir-status (quote ,callback))))
203
13ad7457 204(defun vc-svn-dir-extra-headers (dir)
2ec0d864
ER
205 "Generate extra status headers for a Subversion working copy."
206 (vc-svn-command "*vc*" 0 nil "info")
207 (let ((repo
def61be2 208 (save-excursion
2ec0d864
ER
209 (and (progn
210 (set-buffer "*vc*")
211 (goto-char (point-min))
212 (re-search-forward "Repository Root: *\\(.*\\)" nil t))
213 (match-string 1)))))
214 (concat
215 (cond (repo
216 (concat
217 (propertize "Repository : " 'face 'font-lock-type-face)
218 (propertize repo 'face 'font-lock-variable-name-face)))
219 (t "")))))
220
ac3f4c6f
ER
221(defun vc-svn-working-revision (file)
222 "SVN-specific version of `vc-working-revision'."
1fd3454a
SM
223 ;; There is no need to consult RCS headers under SVN, because we
224 ;; get the workfile version for free when we recognize that a file
225 ;; is registered in SVN.
226 (vc-svn-registered file)
ac3f4c6f 227 (vc-file-getprop file 'vc-working-revision))
1fd3454a 228
5cc7cb96
SM
229;; vc-svn-mode-line-string doesn't exist because the default implementation
230;; works just fine.
231
5b5afd50 232(defun vc-svn-previous-revision (file rev)
cbbd2cd3
TTN
233 (let ((newrev (1- (string-to-number rev))))
234 (when (< 0 newrev)
235 (number-to-string newrev))))
236
5b5afd50 237(defun vc-svn-next-revision (file rev)
cbbd2cd3 238 (let ((newrev (1+ (string-to-number rev))))
5b5afd50 239 ;; The "working revision" is an uneasy conceptual fit under Subversion;
cbbd2cd3
TTN
240 ;; we use it as the upper bound until a better idea comes along. If the
241 ;; workfile version W coincides with the tree's latest revision R, then
242 ;; this check prevents a "no such revision: R+1" error. Otherwise, it
243 ;; inhibits showing of W+1 through R, which could be considered anywhere
244 ;; from gracious to impolite.
ac3f4c6f 245 (unless (< (string-to-number (vc-file-getprop file 'vc-working-revision))
cbbd2cd3
TTN
246 newrev)
247 (number-to-string newrev))))
248
1fd3454a
SM
249
250;;;
251;;; State-changing functions
252;;;
253
8cdd17b4
ER
254(defun vc-svn-create-repo ()
255 "Create a new SVN repository."
2888a97e 256 (vc-do-command "*vc*" 0 "svnadmin" '("create" "SVN"))
f9d5dc48 257 (vc-do-command "*vc*" 0 vc-svn-program '(".")
8cdd17b4
ER
258 "checkout" (concat "file://" default-directory "SVN")))
259
260(defun vc-svn-register (files &optional rev comment)
261 "Register FILES into the SVN version-control system.
262The COMMENT argument is ignored This does an add but not a commit.
c8d6b4bc
GM
263Passes either `vc-svn-register-switches' or `vc-register-switches'
264to the SVN command."
8cdd17b4 265 (apply 'vc-svn-command nil 0 files "add" (vc-switches 'SVN 'register)))
1fd3454a
SM
266
267(defun vc-svn-responsible-p (file)
268 "Return non-nil if SVN thinks it is responsible for FILE."
ace2fad9 269 (file-directory-p (expand-file-name vc-svn-admin-directory
1fd3454a
SM
270 (if (file-directory-p file)
271 file
272 (file-name-directory file)))))
273
274(defalias 'vc-svn-could-register 'vc-svn-responsible-p
275 "Return non-nil if FILE could be registered in SVN.
276This is only possible if SVN is responsible for FILE's directory.")
277
8cdd17b4 278(defun vc-svn-checkin (files rev comment)
1fd3454a 279 "SVN-specific version of `vc-backend-checkin'."
4ced8551 280 (if rev (error "Committing to a specific revision is unsupported in SVN"))
5129f10c 281 (let ((status (apply
8cdd17b4 282 'vc-svn-command nil 1 files "ci"
5129f10c 283 (nconc (list "-m" comment) (vc-switches 'SVN 'checkin)))))
1fd3454a
SM
284 (set-buffer "*vc*")
285 (goto-char (point-min))
286 (unless (equal status 0)
287 ;; Check checkin problem.
288 (cond
fd140743 289 ((search-forward "Transaction is out of date" nil t)
8cdd17b4
ER
290 (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge))
291 files)
1fd3454a
SM
292 (error (substitute-command-keys
293 (concat "Up-to-date check failed: "
294 "type \\[vc-next-action] to merge in changes"))))
295 (t
296 (pop-to-buffer (current-buffer))
297 (goto-char (point-min))
298 (shrink-window-if-larger-than-buffer)
299 (error "Check-in failed"))))
300 ;; Update file properties
301 ;; (vc-file-setprop
ac3f4c6f 302 ;; file 'vc-working-revision
1fd3454a
SM
303 ;; (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2))
304 ))
305
ac3f4c6f 306(defun vc-svn-find-revision (file rev buffer)
8cdd17b4 307 "SVN-specific retrieval of a specified version into a buffer."
1fd3454a
SM
308 (apply 'vc-svn-command
309 buffer 0 file
310 "cat"
311 (and rev (not (string= rev ""))
312 (concat "-r" rev))
fd140743 313 (vc-switches 'SVN 'checkout)))
1fd3454a
SM
314
315(defun vc-svn-checkout (file &optional editable rev)
316 (message "Checking out %s..." file)
317 (with-current-buffer (or (get-file-buffer file) (current-buffer))
a749e19d 318 (vc-svn-update file editable rev (vc-switches 'SVN 'checkout)))
77bf3f54 319 (vc-mode-line file 'SVN)
1fd3454a
SM
320 (message "Checking out %s...done" file))
321
322(defun vc-svn-update (file editable rev switches)
323 (if (and (file-exists-p file) (not rev))
fc2fb30c
SM
324 ;; If no revision was specified, there's nothing to do.
325 nil
1fd3454a 326 ;; Check out a particular version (or recreate the file).
ac3f4c6f 327 (vc-file-setprop file 'vc-working-revision nil)
1fd3454a 328 (apply 'vc-svn-command nil 0 file
1fd3454a 329 "update"
5cc7cb96
SM
330 (cond
331 ((null rev) "-rBASE")
332 ((or (eq rev t) (equal rev "")) nil)
333 (t (concat "-r" rev)))
1fd3454a
SM
334 switches)))
335
3bdc13e4
SM
336(defun vc-svn-delete-file (file)
337 (vc-svn-command nil 0 file "remove"))
338
2766aaaf
SM
339(defun vc-svn-rename-file (old new)
340 (vc-svn-command nil 0 new "move" (file-relative-name old)))
341
1fd3454a
SM
342(defun vc-svn-revert (file &optional contents-done)
343 "Revert FILE to the version it was based on."
344 (unless contents-done
fc2fb30c 345 (vc-svn-command nil 0 file "revert")))
1fd3454a
SM
346
347(defun vc-svn-merge (file first-version &optional second-version)
348 "Merge changes into current working copy of FILE.
349The changes are between FIRST-VERSION and SECOND-VERSION."
350 (vc-svn-command nil 0 file
c217cb04 351 "merge"
02610d0e 352 "-r" (if second-version
c217cb04
SM
353 (concat first-version ":" second-version)
354 first-version))
1fd3454a
SM
355 (vc-file-setprop file 'vc-state 'edited)
356 (with-current-buffer (get-buffer "*vc*")
357 (goto-char (point-min))
c217cb04
SM
358 (if (looking-at "C ")
359 1 ; signal conflict
1fd3454a
SM
360 0))) ; signal success
361
362(defun vc-svn-merge-news (file)
363 "Merge in any new changes made to FILE."
364 (message "Merging changes into %s..." file)
ac3f4c6f 365 ;; (vc-file-setprop file 'vc-working-revision nil)
1fd3454a
SM
366 (vc-file-setprop file 'vc-checkout-time 0)
367 (vc-svn-command nil 0 file "update")
368 ;; Analyze the merge result reported by SVN, and set
369 ;; file properties accordingly.
370 (with-current-buffer (get-buffer "*vc*")
371 (goto-char (point-min))
5b5afd50 372 ;; get new working revision
1fd3454a 373 (if (re-search-forward
1468d754 374 "^\\(Updated to\\|At\\) revision \\([0-9]+\\)" nil t)
ac3f4c6f
ER
375 (vc-file-setprop file 'vc-working-revision (match-string 2))
376 (vc-file-setprop file 'vc-working-revision nil))
1fd3454a 377 ;; get file status
1468d754 378 (goto-char (point-min))
1fd3454a 379 (prog1
1468d754 380 (if (looking-at "At revision")
1fd3454a
SM
381 0 ;; there were no news; indicate success
382 (if (re-search-forward
459b1fe4
SM
383 ;; Newer SVN clients have 3 columns of chars (one for the
384 ;; file's contents, then second for its properties, and the
385 ;; third for lock-grabbing info), before the 2 spaces.
386 ;; We also used to match the filename in column 0 without any
387 ;; meta-info before it, but I believe this can never happen.
388 (concat "^\\(\\([ACGDU]\\)\\(.[B ]\\)? \\)"
1468d754 389 (regexp-quote (file-name-nondirectory file)))
1fd3454a
SM
390 nil t)
391 (cond
392 ;; Merge successful, we are in sync with repository now
459b1fe4 393 ((string= (match-string 2) "U")
1fd3454a
SM
394 (vc-file-setprop file 'vc-state 'up-to-date)
395 (vc-file-setprop file 'vc-checkout-time
396 (nth 5 (file-attributes file)))
397 0);; indicate success to the caller
398 ;; Merge successful, but our own changes are still in the file
459b1fe4 399 ((string= (match-string 2) "G")
1fd3454a
SM
400 (vc-file-setprop file 'vc-state 'edited)
401 0);; indicate success to the caller
402 ;; Conflicts detected!
403 (t
404 (vc-file-setprop file 'vc-state 'edited)
405 1);; signal the error to the caller
406 )
407 (pop-to-buffer "*vc*")
408 (error "Couldn't analyze svn update result")))
409 (message "Merging changes into %s...done" file))))
410
42a0a135
ER
411(defun vc-svn-modify-change-comment (files rev comment)
412 "Modify the change comments for a specified REV.
413You must have ssh access to the repository host, and the directory Emacs
e09deae9 414uses locally for temp files must also be writable by you on that host.
fd064451
DN
415This is only supported if the repository access method is either file://
416or svn+ssh://."
417 (let (tempfile host remotefile directory fileurl-p)
42a0a135 418 (with-temp-buffer
f9d5dc48 419 (vc-do-command (current-buffer) 0 vc-svn-program nil "info")
fd064451
DN
420 (goto-char (point-min))
421 (unless (re-search-forward "Repository Root: \\(file://\\(/.*\\)\\)\\|\\(svn\\+ssh://\\([^/]+\\)\\(/.*\\)\\)" nil t)
422 (error "Repository information is unavailable"))
423 (if (match-string 1)
424 (progn
425 (setq fileurl-p t)
426 (setq directory (match-string 2)))
427 (setq host (match-string 4))
428 (setq directory (match-string 5))
429 (setq remotefile (concat host ":" tempfile))))
430 (with-temp-file (setq tempfile (make-temp-file user-mail-address))
431 (insert comment))
432 (if fileurl-p
433 ;; Repository Root is a local file.
434 (progn
435 (unless (vc-do-command
2888a97e 436 "*vc*" 0 "svnadmin" nil
def61be2 437 "setlog" "--bypass-hooks" directory
fd064451
DN
438 "-r" rev (format "%s" tempfile))
439 (error "Log edit failed"))
440 (delete-file tempfile))
441
442 ;; Remote repository, using svn+ssh.
2888a97e 443 (unless (vc-do-command "*vc*" 0 "scp" nil "-q" tempfile remotefile)
fd064451
DN
444 (error "Copy of comment to %s failed" remotefile))
445 (unless (vc-do-command
2888a97e 446 "*vc*" 0 "ssh" nil "-q" host
fd064451
DN
447 (format "svnadmin setlog --bypass-hooks %s -r %s %s; rm %s"
448 directory rev tempfile tempfile))
449 (error "Log edit failed")))))
1fd3454a
SM
450
451;;;
452;;; History functions
453;;;
454
def61be2
JB
455(defvar log-view-per-file-logs)
456
6653c6b7
DN
457(define-derived-mode vc-svn-log-view-mode log-view-mode "SVN-Log-View"
458 (require 'add-log)
459 (set (make-local-variable 'log-view-per-file-logs) nil))
460
8cdd17b4
ER
461(defun vc-svn-print-log (files &optional buffer)
462 "Get change log(s) associated with FILES."
1fd3454a 463 (save-current-buffer
b349012b 464 (vc-setup-buffer buffer)
1fd3454a
SM
465 (let ((inhibit-read-only t))
466 (goto-char (point-min))
13b56025
ER
467 (if files
468 (dolist (file files)
469 (insert "Working file: " file "\n")
470 (vc-svn-command
471 buffer
472 'async
77bf3f54 473 ;; (if (and (= (length files) 1) (vc-stay-local-p file 'SVN)) 'async 0)
13b56025
ER
474 (list file)
475 "log"
476 ;; By default Subversion only shows the log up to the
477 ;; working revision, whereas we also want the log of the
478 ;; subsequent commits. At least that's what the
479 ;; vc-cvs.el code does.
480 "-rHEAD:0"))
481 ;; Dump log for the entire directory.
482 (vc-svn-command buffer 0 nil "log" "-rHEAD:0")))))
8cdd17b4 483
8cdd17b4 484(defun vc-svn-diff (files &optional oldvers newvers buffer)
5b5afd50 485 "Get a difference report using SVN between two revisions of fileset FILES."
22b5692c
NR
486 (and oldvers
487 (not newvers)
488 files
489 (catch 'no
490 (dolist (f files)
491 (or (equal oldvers (vc-working-revision f))
492 (throw 'no nil)))
493 t)
494 ;; Use nil rather than the current revision because svn handles
495 ;; it better (i.e. locally). Note that if _any_ of the files
496 ;; has a different revision, we fetch the lot, which is
497 ;; obviously sub-optimal.
498 (setq oldvers nil))
8cdd17b4 499 (let* ((switches
f9d1f3be
SM
500 (if vc-svn-diff-switches
501 (vc-switches 'SVN 'diff)
9751169a
GM
502 (list "--diff-cmd=diff" "-x"
503 (mapconcat 'identity (vc-switches nil 'diff) " "))))
2d4e93b9 504 (async (and (not vc-disable-async-diff)
77bf3f54 505 (vc-stay-local-p files 'SVN)
fe1919ab 506 (or oldvers newvers)))) ; Svn diffs those locally.
b349012b 507 (apply 'vc-svn-command buffer
2766aaaf 508 (if async 'async 0)
8cdd17b4 509 files "diff"
2766aaaf 510 (append
f9d1f3be 511 switches
2766aaaf
SM
512 (when oldvers
513 (list "-r" (if newvers (concat oldvers ":" newvers)
514 oldvers)))))
fd140743
SM
515 (if async 1 ; async diff => pessimistic assumption
516 ;; For some reason `svn diff' does not return a useful
517 ;; status w.r.t whether the diff was empty or not.
8cdd17b4 518 (buffer-size (get-buffer buffer)))))
1fd3454a 519
1fd3454a 520;;;
370fded4 521;;; Tag system
1fd3454a
SM
522;;;
523
370fded4 524(defun vc-svn-create-tag (dir name branchp)
5b5afd50 525 "Assign to DIR's current revision a given NAME.
1fd3454a 526If BRANCHP is non-nil, the name is created as a branch (and the current
5cc7cb96
SM
527workspace is immediately moved to that new branch).
528NAME is assumed to be a URL."
529 (vc-svn-command nil 0 dir "copy" name)
370fded4 530 (when branchp (vc-svn-retrieve-tag dir name nil)))
1fd3454a 531
370fded4
ER
532(defun vc-svn-retrieve-tag (dir name update)
533 "Retrieve a tag at and below DIR.
534NAME is the name of the tag; if it is empty, do a `svn update'.
5cc7cb96
SM
535If UPDATE is non-nil, then update (resynch) any affected buffers.
536NAME is assumed to be a URL."
537 (vc-svn-command nil 0 dir "switch" name)
538 ;; FIXME: parse the output and obey `update'.
539 )
1fd3454a
SM
540
541;;;
542;;; Miscellaneous
543;;;
544
545;; Subversion makes backups for us, so don't bother.
77bf3f54
DN
546;; (defun vc-svn-make-version-backups-p (file)
547;; "Return non-nil if version backups should be made for FILE."
548;; (vc-stay-local-p file 'SVN))
1fd3454a
SM
549
550(defun vc-svn-check-headers ()
551 "Check if the current file has any headers in it."
552 (save-excursion
553 (goto-char (point-min))
554 (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
555\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
556
557
558;;;
559;;; Internal functions
560;;;
561
8cdd17b4 562(defun vc-svn-command (buffer okstatus file-or-list &rest flags)
1fd3454a
SM
563 "A wrapper around `vc-do-command' for use in vc-svn.el.
564The difference to vc-do-command is that this function always invokes `svn',
565and that it passes `vc-svn-global-switches' to it before FLAGS."
2888a97e 566 (apply 'vc-do-command (or buffer "*vc*") okstatus vc-svn-program file-or-list
1fd3454a
SM
567 (if (stringp vc-svn-global-switches)
568 (cons vc-svn-global-switches flags)
569 (append vc-svn-global-switches
570 flags))))
571
7ffc77d3
SM
572(defun vc-svn-repository-hostname (dirname)
573 (with-temp-buffer
574 (let ((coding-system-for-read
575 (or file-name-coding-system
576 default-file-name-coding-system)))
ace2fad9
CY
577 (vc-insert-file (expand-file-name (concat vc-svn-admin-directory
578 "/entries")
579 dirname)))
7ffc77d3
SM
580 (goto-char (point-min))
581 (when (re-search-forward
c45b3be3 582 ;; Old `svn' used name="svn:this_dir", newer use just name="".
17a5a301
SM
583 (concat "name=\"\\(?:svn:this_dir\\)?\"[\n\t ]*"
584 "\\(?:[-a-z]+=\"[^\"]*\"[\n\t ]*\\)*?"
0b0dad41
SM
585 "url=\"\\(?1:[^\"]+\\)\""
586 ;; Yet newer ones don't use XML any more.
587 "\\|^\ndir\n[0-9]+\n\\(?1:.*\\)") nil t)
17a5a301
SM
588 ;; This is not a hostname but a URL. This may actually be considered
589 ;; as a feature since it allows vc-svn-stay-local to specify different
590 ;; behavior for different modules on the same server.
591 (match-string 1))))
1fd3454a 592
1c67a814
SM
593(defun vc-svn-resolve-when-done ()
594 "Call \"svn resolved\" if the conflict markers have been removed."
595 (save-excursion
596 (goto-char (point-min))
54648b5c
DN
597 (unless (re-search-forward "^<<<<<<< " nil t)
598 (vc-svn-command nil 0 buffer-file-name "resolved")
599 ;; Remove the hook so that it is not called multiple times.
600 (remove-hook 'after-save-hook 'vc-svn-resolve-when-done t))))
1c67a814
SM
601
602;; Inspired by vc-arch-find-file-hook.
603(defun vc-svn-find-file-hook ()
604 (when (eq ?C (vc-file-getprop buffer-file-name 'vc-svn-status))
605 ;; If the file is marked as "conflicted", then we should try and call
606 ;; "svn resolved" when applicable.
607 (if (save-excursion
608 (goto-char (point-min))
609 (re-search-forward "^<<<<<<< " nil t))
610 ;; There are conflict markers.
611 (progn
28e4e2b4 612 (smerge-start-session)
1c67a814
SM
613 (add-hook 'after-save-hook 'vc-svn-resolve-when-done nil t))
614 ;; There are no conflict markers. This is problematic: maybe it means
615 ;; the conflict has been resolved and we should immediately call "svn
616 ;; resolved", or it means that the file's type does not allow Svn to
617 ;; use conflict markers in which case we don't really know what to do.
618 ;; So let's just punt for now.
619 nil)
620 (message "There are unresolved conflicts in this file")))
621
bc8c1bb4 622(defun vc-svn-parse-status (&optional filename)
1fd3454a 623 "Parse output of \"svn status\" command in the current buffer.
bc8c1bb4
SM
624Set file properties accordingly. Unless FILENAME is non-nil, parse only
625information about FILENAME and return its status."
1fd3454a
SM
626 (let (file status)
627 (goto-char (point-min))
628 (while (re-search-forward
484c1b1f 629 ;; Ignore the files with status X.
245cacf1 630 "^\\(?:\\?\\|[ ACDGIMR!~][ MC][ L][ +][ S]..\\([ *]\\) +\\([-0-9]+\\) +\\([0-9?]+\\) +\\([^ ]+\\)\\) +" nil t)
c45b3be3
SM
631 ;; If the username contains spaces, the output format is ambiguous,
632 ;; so don't trust the output's filename unless we have to.
633 (setq file (or filename
634 (expand-file-name
635 (buffer-substring (point) (line-end-position)))))
1fd3454a 636 (setq status (char-after (line-beginning-position)))
484c1b1f 637 (if (eq status ??)
4f07b9f2 638 (vc-file-setprop file 'vc-state 'unregistered)
fd140743
SM
639 ;; Use the last-modified revision, so that searching in vc-print-log
640 ;; output works.
4f07b9f2 641 (vc-file-setprop file 'vc-working-revision (match-string 3))
1c67a814 642 ;; Remember Svn's own status.
4f07b9f2
ER
643 (vc-file-setprop file 'vc-svn-status status)
644 (vc-file-setprop
1fd3454a
SM
645 file 'vc-state
646 (cond
647 ((eq status ?\ )
648 (if (eq (char-after (match-beginning 1)) ?*)
3702367b 649 'needs-update
4f07b9f2 650 (vc-file-setprop file 'vc-checkout-time
1fd3454a
SM
651 (nth 5 (file-attributes file)))
652 'up-to-date))
653 ((eq status ?A)
fd140743 654 ;; If the file was actually copied, (match-string 2) is "-".
4f07b9f2
ER
655 (vc-file-setprop file 'vc-working-revision "0")
656 (vc-file-setprop file 'vc-checkout-time 0)
484c1b1f 657 'added)
7fbb4797
DN
658 ((eq status ?C)
659 (vc-file-setprop file 'vc-state 'conflict))
660 ((eq status '?M)
1fd3454a
SM
661 (if (eq (char-after (match-beginning 1)) ?*)
662 'needs-merge
663 'edited))
722f037f 664 ((eq status ?I)
4f07b9f2 665 (vc-file-setprop file 'vc-state 'ignored))
e6c01f09 666 ((memq status '(?D ?R))
4f07b9f2 667 (vc-file-setprop file 'vc-state 'removed))
bc8c1bb4 668 (t 'edited)))))
245cacf1 669 (when filename (vc-file-getprop filename 'vc-state))))
1fd3454a 670
1fd3454a
SM
671(defun vc-svn-valid-symbolic-tag-name-p (tag)
672 "Return non-nil if TAG is a valid symbolic tag name."
673 ;; According to the SVN manual, a valid symbolic tag must start with
674 ;; an uppercase or lowercase letter and can contain uppercase and
675 ;; lowercase letters, digits, `-', and `_'.
676 (and (string-match "^[a-zA-Z]" tag)
677 (not (string-match "[^a-z0-9A-Z-_]" tag))))
678
5b5afd50
ER
679(defun vc-svn-valid-revision-number-p (tag)
680 "Return non-nil if TAG is a valid revision number."
1fd3454a
SM
681 (and (string-match "^[0-9]" tag)
682 (not (string-match "[^0-9]" tag))))
683
17a5a301
SM
684;; Support for `svn annotate'
685
686(defun vc-svn-annotate-command (file buf &optional rev)
687 (vc-svn-command buf 0 file "annotate" (if rev (concat "-r" rev))))
688
689(defun vc-svn-annotate-time-of-rev (rev)
690 ;; Arbitrarily assume 10 commmits per day.
691 (/ (string-to-number rev) 10.0))
692
e53ac718
DN
693(defvar vc-annotate-parent-rev)
694
17a5a301
SM
695(defun vc-svn-annotate-current-time ()
696 (vc-svn-annotate-time-of-rev vc-annotate-parent-rev))
697
698(defconst vc-svn-annotate-re "[ \t]*\\([0-9]+\\)[ \t]+[^\t ]+ ")
699
700(defun vc-svn-annotate-time ()
701 (when (looking-at vc-svn-annotate-re)
702 (goto-char (match-end 0))
703 (vc-svn-annotate-time-of-rev (match-string 1))))
704
705(defun vc-svn-annotate-extract-revision-at-line ()
706 (save-excursion
707 (beginning-of-line)
708 (if (looking-at vc-svn-annotate-re) (match-string 1))))
709
1fd3454a
SM
710(provide 'vc-svn)
711
f9d1f3be 712;; arch-tag: 02f10c68-2b4d-453a-90fc-1eee6cfb268d
1fd3454a 713;;; vc-svn.el ends here