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