1 ;;;; vc-svn.el --- a VC backend for Subversion
2 ;;;; Jim Blandy <jimb@red-bean.com> --- July 2002
4 ;;; #########################################################################
6 ;;; ## NOTE: THIS IS NOT THE MASTER VERSION OF VC-SVN.EL ##
8 ;;; ## The canonical vc-svn.el now lives in the FSF Emacs tree, at ##
9 ;;; ## http://savannah.gnu.org/cgi-bin/viewcvs/emacs/emacs/lisp/vc-svn.el. ##
10 ;;; ## The version here is maintained only because it is compatible with ##
11 ;;; ## older releases of Emacs, since (as of this writing) the one in the ##
12 ;;; ## FSF tree hasn't made it into an official release of Emacs yet. ##
13 ;;; ## Eventually it will, though, and sometime after that the version ##
14 ;;; ## here will go away. ##
16 ;;; #########################################################################
18 ;;; Writing this back end has shown up some problems in VC: bugs,
19 ;;; shortcomings in the back end interface, and so on. But I want to
20 ;;; first produce code that Subversion users can use with an already
21 ;;; released Emacs distribution.
23 ;;; So for now we're working within the limitations of the released
24 ;;; VC; once we've got something functional, then we can start writing
28 ;;; To make this file load on demand, put this file into a directory
29 ;;; in `load-path', and add this line to a startup file:
31 ;;; (add-to-list 'vc-handled-backends 'SVN)
35 ;;; Provide more of the optional VC backend functions:
37 ;;; - merge across arbitrary revisions
39 ;;; Maybe we want more info in mode-line-string. Status of props? Status
40 ;;; compared to what's in the repository (svn st -u) ?
42 ;;; VC passes the vc-svn-register function a COMMENT argument, which
43 ;;; is like the file description in CVS and RCS. Could we store the
44 ;;; COMMENT as a Subversion property? Would that show up in fancy DAV
45 ;;; web folder displays, or would it just languish in obscurity, the
46 ;;; way CVS and RCS descriptions do?
48 ;;; After manual merging, need some way to run `svn resolved'. Perhaps
49 ;;; we should just prompt for approval when somebody tries to commit a
52 ;;; vc-svn ought to handle more gracefully an attempted commit that
53 ;;; fails with "Transaction is out of date". Probably the best
54 ;;; approach is to ask "file is not up-to-date; do you want to merge
55 ;;; now?" I think vc-cvs does this.
57 ;;; Perhaps show the "conflicted" marker in the modeline?
59 ;;; If conflicted, before committing or merging, ask the user if they
60 ;;; want to mark the file as resolved.
62 ;;; Won't searching for strings in svn output cause trouble if the
63 ;;; locale language is not English?
65 ;;; After merging news, need to recheck our idea of which workfile
66 ;;; version we have. Reverting the file does this but we need to
67 ;;; force it. Note that this can be necessary even if the file has
70 ;;; Does everything work properly if we're rolled back to an old
73 ;;; Perhaps need to implement vc-svn-latest-on-branch-p?
78 ;;; Make sure vc's documentation for `workfile-unchanged-p' default
79 ;;; function mentions that it must not run asynchronously, and the
80 ;;; symptoms if it does.
82 ;;; Fix logic for finding log entries.
84 ;;; Allow historical diff to choose an appropriate default previous
85 ;;; revision number. I think this entails moving vc-previous-revision
86 ;;; et al into the back end.
88 ;;; Should vc-BACKEND-checkout really have to set the workfile version
91 ;;; Fix smerge for svn conflict markers.
93 ;;; We can have files which are not editable for reasons other than
94 ;;; needing to be checked out. For example, they might be a read-only
95 ;;; view of an old revision opened with [C-x v ~]. (See vc-merge)
97 ;;; Would be nice if there was a way to mark a file as
98 ;;; just-checked-out, aside from updating the checkout-time property
99 ;;; which in theory is not to be changed by backends.
102 (add-to-list 'vc-handled-backends
'SVN
)
104 (defcustom vc-svn-program-name
"svn"
105 "*Name of Subversion client program, for use by Emacs's VC package."
108 :version
"21.2.90.2")
110 (defcustom vc-svn-diff-switches nil
111 "*A string or list of strings specifying extra switches for `svn diff' under VC."
112 :type
'(repeat string
)
114 :version
"21.2.90.2")
116 (defun vc-svn-registered (file)
117 "Return true if FILE is registered under Subversion."
118 ;; First, a quick false positive test: is there a `.svn/entries' file?
119 (and (file-exists-p (expand-file-name ".svn/entries"
120 (file-name-directory file
)))
121 (not (null (vc-svn-run-status file
)))))
124 (put 'vc-svn-with-output-buffer
'lisp-indent-function
0)
125 (defmacro vc-svn-with-output-buffer
(&rest body
)
126 "Save excursion, switch to buffer ` *Subversion Output*', and erase it."
128 ;; Let's not delete this buffer when we're done --- leave
129 ;; it around for debugging.
130 (set-buffer (get-buffer-create " *Subversion Output*"))
135 (defun vc-svn-pop-up-error (&rest args
)
136 "Pop up the Subversion output buffer, and raise an error with ARGS."
137 (pop-to-buffer " *Subversion Output*")
138 (goto-char (point-min))
139 (shrink-window-if-larger-than-buffer)
143 (defun vc-svn-run-status (file &optional update
)
144 "Run `svn status -v' on FILE, and return the result.
145 If optional arg UPDATE is true, pass the `-u' flag to check against
146 the repository, across the network.
147 See `vc-svn-parse-status' for a description of the result."
148 (vc-svn-with-output-buffer
150 ;; We should call vc-do-command here, but Subversion exits with an
151 ;; error status if FILE isn't under its control, and we want to
152 ;; return that as nil, not display it to the user. We can tell
155 (let ((status (apply 'call-process vc-svn-program-name nil t nil
156 (append '("status" "-v")
157 (if update
'("-u") '())
159 (goto-char (point-min))
160 (if (not (equal 0 status
)) ; not zerop; status can be a string
161 ;; If you ask for the status of a file that isn't even in a
162 ;; Subversion-controlled directory, then Subversion exits with
164 (if (or (looking-at "\\(.*\n\\)*.*is not a working copy")
165 (looking-at "\\(.*\n\\)*.*is not a versioned resource")
166 (looking-at "\\(.*\n\\)*.*: No such file or directory"))
168 ;; Other errors we should actually report to the user.
170 "Error running Subversion to check status of `%s'"
171 (file-name-nondirectory file
)))
173 ;; Otherwise, we've got valid status output in the buffer, so
175 (vc-svn-parse-status)))))
178 (defun vc-svn-parse-status ()
179 "Parse the output from `svn status -v' at point.
180 We return nil for a file not under Subversion's control,
181 or (STATE LOCAL CHANGED) for files that are, where:
182 STATE is the file's VC state (see the documentation for `vc-state'),
183 LOCAL is the base revision in the working copy, and
184 CHANGED is the last revision in which it was changed.
185 Both LOCAL and CHANGED are strings, not numbers.
186 If we passed `svn status' the `-u' flag, then CHANGED could be a later
188 If the file is newly added, LOCAL is \"0\" and CHANGED is nil."
189 (let ((state (vc-svn-parse-state-only)))
192 ;; A newly added file has no revision.
193 ((looking-at "....\\s-+\\(\\*\\s-+\\)?[-0]\\s-+\\(\\?\\|[0-9]+\\)")
194 (list state
"0" nil
))
195 ((looking-at "....\\s-+\\(\\*\\s-+\\)?\\([0-9]+\\)\\s-+\\([0-9]+\\)")
199 ((looking-at "^I +") nil
) ;; An ignored file
200 ((looking-at " \\{40\\}") nil
) ;; A file that is not in the wc nor svn?
201 (t (error "Couldn't parse output from `svn status -v'")))))
204 (defun vc-svn-parse-state-only ()
205 "Parse the output from `svn status -v' at point, and return a state.
206 The documentation for the function `vc-state' describes the possible values."
208 ;; Be careful --- some of the later clauses here could yield false
209 ;; positives, if the clauses preceding them didn't screen those
210 ;; out. Making a pattern more selective could break something.
212 ;; nil The given file is not under version control,
213 ;; or does not exist.
214 ((looking-at "\\?\\|^$") nil
)
216 ;; 'needs-patch The file has not been edited by the
217 ;; user, but there is a more recent version
218 ;; on the current branch stored in the
220 ((looking-at " ..\\s-+\\*") 'needs-patch
)
222 ;; 'up-to-date The working file is unmodified with
223 ;; respect to the latest version on the
224 ;; current branch, and not locked.
226 ;; This is also returned for files which do not
227 ;; exist, as will be the case when finding a
228 ;; new file in a svn-controlled directory. That
229 ;; case is handled in vc-svn-parse-status.
230 ((looking-at " ") 'up-to-date
)
232 ;; 'needs-merge The file has been edited by the user,
233 ;; and there is also a more recent version
234 ;; on the current branch stored in the
235 ;; master file. This state can only occur
236 ;; if locking is not used for the file.
237 ((looking-at "\\S-+\\s-+\\*") 'needs-merge
)
239 ;; 'edited The working file has been edited by the
240 ;; user. If locking is used for the file,
241 ;; this state means that the current
242 ;; version is locked by the calling user.
246 ;;; Is it really safe not to check for updates? I haven't seen any
247 ;;; cases where failing to check causes a problem that is not caught
248 ;;; in some other way. However, there *are* cases where checking
249 ;;; needlessly causes network delay, such as C-x v v. The common case
250 ;;; is for the commit to be OK; we can handle errors if they occur. -- mbp
251 (defun vc-svn-state (file)
252 "Return the current version control state of FILE.
253 For a list of possible return values, see `vc-state'.
255 This function should do a full and reliable state computation; it is
256 usually called immediately after `C-x v v'. `vc-svn-state-heuristic'
257 provides a faster heuristic when visiting a file.
259 For svn this does *not* check for updates in the repository, because
260 that needlessly slows down vc when the repository is remote. Instead,
261 we rely on Subversion to trap situations such as needing a merge
263 (car (vc-svn-run-status file
)))
266 (defun vc-svn-state-heuristic (file)
267 "Estimate the version control state of FILE at visiting time.
268 For a list of possible values, see the doc string of `vc-state'.
269 This is supposed to be considerably faster than `vc-svn-state'. It
270 just runs `svn status -v', without the `-u' flag, so it's a strictly
272 (car (vc-svn-run-status file
)))
276 (defun vc-svn-workfile-version (file)
277 "Return the current workfile version of FILE."
278 (cadr (vc-svn-run-status file
)))
281 (defun vc-svn-checkout-model (file)
285 (defun vc-svn-register (file &optional rev comment
)
286 "Register FILE with Subversion.
287 REV is an initial revision; Subversion ignores it.
288 COMMENT is an initial description of the file; currently this is ignored."
289 (vc-svn-with-output-buffer
290 (let ((status (call-process vc-svn-program-name nil t nil
"add" file
)))
291 (or (equal 0 status
) ; not zerop; status can be a string
292 (vc-svn-pop-up-error "Error running Subversion to add `%s'"
293 (file-name-nondirectory file
))))))
296 (defun vc-svn-checkin (file rev comment
)
297 (apply 'vc-do-command nil
0 vc-svn-program-name file
298 "commit" (if comment
(list "-m" comment
) '())))
301 (defun vc-svn-checkout (file &optional editable rev destfile
)
302 "Check out revision REV of FILE into the working area.
304 If EDITABLE is non-nil, do a regular update, otherwise check out the
305 requested REV to temp file DESTFILE. If both EDITABLE and DESTFILE
306 are non-nil, raise an error.
308 If REV is non-nil, that is the revision to check out (default is
309 current workfile version). If REV is the empty string, that means to
310 check out the head of the trunk. For Subversion, that's equivalent to
315 (error "VC asked Subversion to check out a file under another name"))
318 (apply 'vc-do-command nil
0 vc-svn-program-name file
319 "update" (if rev
(list "-r" rev
) '()))
320 (vc-file-setprop file
'vc-workfile-version nil
))
321 (with-temp-file destfile
322 (apply 'vc-do-command t
0 vc-svn-program-name file
323 "cat" (if (equal rev
"") '() (list "-r" rev
))))))
326 (defun vc-svn-revert (file &optional contents-done
)
327 "Revert FILE back to the current workfile version.
328 If optional arg CONTENTS-DONE is non-nil, then the contents of FILE
329 have already been reverted from a version backup, and this function
330 only needs to update the status of FILE within the backend. This
331 function ignores the CONTENTS-DONE argument."
332 (vc-do-command nil
0 vc-svn-program-name file
"revert"))
335 (defun vc-svn-merge-news (file)
336 "Merge recent changes into FILE.
338 This calls `svn update'. In the case of conflicts, Subversion puts
339 conflict markers into the file and leaves additional temporary files
340 containing the `ancestor', `mine', and `other' files.
342 You may need to run `svn resolved' by hand once these conflicts have
345 Returns a vc status, which is used to determine whether conflicts need
348 (vc-do-command nil
0 vc-svn-program-name file
"update")
350 ;; This file may not have changed in the revisions which were
351 ;; merged, which means that its mtime on disk will not have been
352 ;; updated. However, the workfile version may still have been
353 ;; updated, and we want that to be shown correctly in the
356 ;; vc-cvs does something like this
357 (vc-file-setprop file
'vc-checkout-time
0)
358 (vc-file-setprop file
'vc-workfile-version
359 (vc-svn-workfile-version file
))))
362 (defun vc-svn-print-log (file)
363 "Insert the revision log of FILE into the *vc* buffer."
364 (vc-do-command nil
'async vc-svn-program-name file
"log"))
367 (defun vc-svn-show-log-entry (version)
368 "Search the log entry for VERSION in the current buffer.
369 Make sure it is displayed in the buffer's window."
370 (when (re-search-forward (concat "^-+\n\\(rev\\) "
371 (regexp-quote version
)
372 ":[^|]+|[^|]+| [0-9]+ lines?"))
373 (goto-char (match-beginning 1))
377 (defun vc-svn-diff (file &optional rev1 rev2
)
378 "Insert the diff for FILE into the *vc-diff* buffer.
379 If REV1 and REV2 are non-nil, report differences from REV1 to REV2.
380 If REV1 is nil, use the current workfile version (as found in the
381 repository) as the older version; if REV2 is nil, use the current
382 workfile contents as the newer version.
383 This function returns a status of either 0 (no differences found), or
384 1 (either non-empty diff or the diff is run asynchronously)."
385 (let* ((diff-switches-list
386 ;; In Emacs 21.3.50 or so, the `vc-diff-switches-list' macro
387 ;; started requiring its symbol argument to be quoted.
389 (vc-diff-switches-list svn
)
390 (void-variable (vc-diff-switches-list 'SVN
))))
391 (status (vc-svn-run-status file
))
392 (local (elt status
1))
393 (changed (elt status
2))
395 ;; If rev1 is the default (the base revision) set it to nil.
396 ;; This is nice because it lets us recognize when the diff
397 ;; will run locally, and thus when we shouldn't bother to run
398 ;; it asynchronously. But it's also necessary, since a diff
399 ;; for vc-default-workfile-unchanged-p *must* run
400 ;; synchronously, or else you'll end up with two diffs in the
401 ;; *vc-diff* buffer. `vc-diff-workfile-unchanged-p' passes
402 ;; the base revision explicitly, but this kludge lets us
403 ;; recognize that we can run the diff synchronously anyway.
405 (rev1 (if (and rev1
(not (equal rev1 local
))) rev1
))
409 ;; Given base rev against given rev.
410 ((and rev1 rev2
) (list "-r" (format "%s:%s" rev1 rev2
)))
411 ;; Given base rev against working copy.
412 (rev1 (list "-r" rev1
))
413 ;; Working copy base against given rev.
414 (rev2 (list "-r" (format "%s:%s" local rev2
)))
415 ;; Working copy base against working copy.
418 ;; Run diff asynchronously if we're going to have to go
419 ;; across the network.
420 (async (or rev1 rev2
)))
422 (let ((status (apply 'vc-do-command
"*vc-diff*" (if async
'async
0)
423 vc-svn-program-name file
424 (append '("diff") rev-switches-list
))))
425 (if (or async
(> (buffer-size (get-buffer "*vc-diff*")) 0))
428 (defun vc-svn-find-version (file rev buffer
)
429 (vc-do-command buffer
0 vc-svn-program-name file
432 (defun vc-svn-annotate-command (file buffer
&optional version
)
433 "Execute \"svn annotate\" on FILE, inserting the contents in BUFFER.
434 Optional arg VERSION is a revision to annotate from."
435 (vc-do-command buffer
0 vc-svn-program-name file
"annotate"
436 (if version
(concat "-r" version
))))
438 (defun vc-svn-annotate-difference (point)
439 "Difference between the time of the line and the current time.
440 Return values are as defined for `current-time'."
441 nil
) ; don't bother with differences