vc-cvs.el 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. ;;; vc-cvs.el --- non-resident support for CVS version-control
  2. ;; Copyright (C) 1995, 1998-2012 Free Software Foundation, Inc.
  3. ;; Author: FSF (see vc.el for full credits)
  4. ;; Maintainer: Andre Spiegel <spiegel@gnu.org>
  5. ;; Package: vc
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;; Code:
  19. (eval-when-compile (require 'cl) (require 'vc))
  20. ;; Clear up the cache to force vc-call to check again and discover
  21. ;; new functions when we reload this file.
  22. (put 'CVS 'vc-functions nil)
  23. ;;; Properties of the backend.
  24. (defun vc-cvs-revision-granularity () 'file)
  25. (defun vc-cvs-checkout-model (files)
  26. "CVS-specific version of `vc-checkout-model'."
  27. (if (getenv "CVSREAD")
  28. 'announce
  29. (let* ((file (if (consp files) (car files) files))
  30. (attrib (file-attributes file)))
  31. (or (vc-file-getprop file 'vc-checkout-model)
  32. (vc-file-setprop
  33. file 'vc-checkout-model
  34. (if (and attrib ;; don't check further if FILE doesn't exist
  35. ;; If the file is not writable (despite CVSREAD being
  36. ;; undefined), this is probably because the file is being
  37. ;; "watched" by other developers.
  38. ;; (If vc-mistrust-permissions was t, we actually shouldn't
  39. ;; trust this, but there is no other way to learn this from
  40. ;; CVS at the moment (version 1.9).)
  41. (string-match "r-..-..-." (nth 8 attrib)))
  42. 'announce
  43. 'implicit))))))
  44. ;;;
  45. ;;; Customization options
  46. ;;;
  47. (defgroup vc-cvs nil
  48. "VC CVS backend."
  49. :version "24.1"
  50. :group 'vc)
  51. (defcustom vc-cvs-global-switches nil
  52. "Global switches to pass to any CVS command."
  53. :type '(choice (const :tag "None" nil)
  54. (string :tag "Argument String")
  55. (repeat :tag "Argument List"
  56. :value ("")
  57. string))
  58. :version "22.1"
  59. :group 'vc-cvs)
  60. (defcustom vc-cvs-register-switches nil
  61. "Switches for registering a file into CVS.
  62. A string or list of strings passed to the checkin program by
  63. \\[vc-register]. If nil, use the value of `vc-register-switches'.
  64. If t, use no switches."
  65. :type '(choice (const :tag "Unspecified" nil)
  66. (const :tag "None" t)
  67. (string :tag "Argument String")
  68. (repeat :tag "Argument List" :value ("") string))
  69. :version "21.1"
  70. :group 'vc-cvs)
  71. (defcustom vc-cvs-diff-switches nil
  72. "String or list of strings specifying switches for CVS diff under VC.
  73. If nil, use the value of `vc-diff-switches'. If t, use no switches."
  74. :type '(choice (const :tag "Unspecified" nil)
  75. (const :tag "None" t)
  76. (string :tag "Argument String")
  77. (repeat :tag "Argument List" :value ("") string))
  78. :version "21.1"
  79. :group 'vc-cvs)
  80. (defcustom vc-cvs-header '("\$Id\$")
  81. "Header keywords to be inserted by `vc-insert-headers'."
  82. :version "24.1" ; no longer consult the obsolete vc-header-alist
  83. :type '(repeat string)
  84. :group 'vc-cvs)
  85. (defcustom vc-cvs-use-edit t
  86. "Non-nil means to use `cvs edit' to \"check out\" a file.
  87. This is only meaningful if you don't use the implicit checkout model
  88. \(i.e. if you have $CVSREAD set)."
  89. :type 'boolean
  90. :version "21.1"
  91. :group 'vc-cvs)
  92. (defcustom vc-cvs-stay-local 'only-file
  93. "Non-nil means use local operations when possible for remote repositories.
  94. This avoids slow queries over the network and instead uses heuristics
  95. and past information to determine the current status of a file.
  96. If value is the symbol `only-file' `vc-dir' will connect to the
  97. server, but heuristics will be used to determine the status for
  98. all other VC operations.
  99. The value can also be a regular expression or list of regular
  100. expressions to match against the host name of a repository; then VC
  101. only stays local for hosts that match it. Alternatively, the value
  102. can be a list of regular expressions where the first element is the
  103. symbol `except'; then VC always stays local except for hosts matched
  104. by these regular expressions."
  105. :type '(choice (const :tag "Always stay local" t)
  106. (const :tag "Only for file operations" only-file)
  107. (const :tag "Don't stay local" nil)
  108. (list :format "\nExamine hostname and %v"
  109. :tag "Examine hostname ..."
  110. (set :format "%v" :inline t
  111. (const :format "%t" :tag "don't" except))
  112. (regexp :format " stay local,\n%t: %v"
  113. :tag "if it matches")
  114. (repeat :format "%v%i\n" :inline t (regexp :tag "or"))))
  115. :version "23.1"
  116. :group 'vc-cvs)
  117. (defcustom vc-cvs-sticky-date-format-string "%c"
  118. "Format string for mode-line display of sticky date.
  119. Format is according to `format-time-string'. Only used if
  120. `vc-cvs-sticky-tag-display' is t."
  121. :type '(string)
  122. :version "22.1"
  123. :group 'vc-cvs)
  124. (defcustom vc-cvs-sticky-tag-display t
  125. "Specify the mode-line display of sticky tags.
  126. Value t means default display, nil means no display at all. If the
  127. value is a function or macro, it is called with the sticky tag and
  128. its' type as parameters, in that order. TYPE can have three different
  129. values: `symbolic-name' (TAG is a string), `revision-number' (TAG is a
  130. string) and `date' (TAG is a date as returned by `encode-time'). The
  131. return value of the function or macro will be displayed as a string.
  132. Here's an example that will display the formatted date for sticky
  133. dates and the word \"Sticky\" for sticky tag names and revisions.
  134. (lambda (tag type)
  135. (cond ((eq type 'date) (format-time-string
  136. vc-cvs-sticky-date-format-string tag))
  137. ((eq type 'revision-number) \"Sticky\")
  138. ((eq type 'symbolic-name) \"Sticky\")))
  139. Here's an example that will abbreviate to the first character only,
  140. any text before the first occurrence of `-' for sticky symbolic tags.
  141. If the sticky tag is a revision number, the word \"Sticky\" is
  142. displayed. Date and time is displayed for sticky dates.
  143. (lambda (tag type)
  144. (cond ((eq type 'date) (format-time-string \"%Y%m%d %H:%M\" tag))
  145. ((eq type 'revision-number) \"Sticky\")
  146. ((eq type 'symbolic-name)
  147. (condition-case nil
  148. (progn
  149. (string-match \"\\\\([^-]*\\\\)\\\\(.*\\\\)\" tag)
  150. (concat (substring (match-string 1 tag) 0 1) \":\"
  151. (substring (match-string 2 tag) 1 nil)))
  152. (error tag))))) ; Fall-back to given tag name.
  153. See also variable `vc-cvs-sticky-date-format-string'."
  154. :type '(choice boolean function)
  155. :version "22.1"
  156. :group 'vc-cvs)
  157. ;;;
  158. ;;; Internal variables
  159. ;;;
  160. ;;;
  161. ;;; State-querying functions
  162. ;;;
  163. ;;;###autoload(defun vc-cvs-registered (f)
  164. ;;;###autoload "Return non-nil if file F is registered with CVS."
  165. ;;;###autoload (when (file-readable-p (expand-file-name
  166. ;;;###autoload "CVS/Entries" (file-name-directory f)))
  167. ;;;###autoload (load "vc-cvs")
  168. ;;;###autoload (vc-cvs-registered f)))
  169. (defun vc-cvs-registered (file)
  170. "Check if FILE is CVS registered."
  171. (let ((dirname (or (file-name-directory file) ""))
  172. (basename (file-name-nondirectory file))
  173. ;; make sure that the file name is searched case-sensitively
  174. (case-fold-search nil))
  175. (if (file-readable-p (expand-file-name "CVS/Entries" dirname))
  176. (or (string= basename "")
  177. (with-temp-buffer
  178. (vc-cvs-get-entries dirname)
  179. (goto-char (point-min))
  180. (cond ((re-search-forward
  181. (concat "^/" (regexp-quote basename) "/[^/]") nil t)
  182. (beginning-of-line)
  183. (vc-cvs-parse-entry file)
  184. t)
  185. (t nil))))
  186. nil)))
  187. (defun vc-cvs-state (file)
  188. "CVS-specific version of `vc-state'."
  189. (if (vc-stay-local-p file 'CVS)
  190. (let ((state (vc-file-getprop file 'vc-state)))
  191. ;; If we should stay local, use the heuristic but only if
  192. ;; we don't have a more precise state already available.
  193. (if (memq state '(up-to-date edited nil))
  194. (vc-cvs-state-heuristic file)
  195. state))
  196. (with-temp-buffer
  197. (cd (file-name-directory file))
  198. (let (process-file-side-effects)
  199. (vc-cvs-command t 0 file "status"))
  200. (vc-cvs-parse-status t))))
  201. (defun vc-cvs-state-heuristic (file)
  202. "CVS-specific state heuristic."
  203. ;; If the file has not changed since checkout, consider it `up-to-date'.
  204. ;; Otherwise consider it `edited'.
  205. (let ((checkout-time (vc-file-getprop file 'vc-checkout-time))
  206. (lastmod (nth 5 (file-attributes file))))
  207. (cond
  208. ((equal checkout-time lastmod) 'up-to-date)
  209. ((string= (vc-working-revision file) "0") 'added)
  210. ((null checkout-time) 'unregistered)
  211. (t 'edited))))
  212. (defun vc-cvs-working-revision (file)
  213. "CVS-specific version of `vc-working-revision'."
  214. ;; There is no need to consult RCS headers under CVS, because we
  215. ;; get the workfile version for free when we recognize that a file
  216. ;; is registered in CVS.
  217. (vc-cvs-registered file)
  218. (vc-file-getprop file 'vc-working-revision))
  219. (defun vc-cvs-mode-line-string (file)
  220. "Return string for placement into the modeline for FILE.
  221. Compared to the default implementation, this function does two things:
  222. Handle the special case of a CVS file that is added but not yet
  223. committed and support display of sticky tags."
  224. (let* ((sticky-tag (vc-file-getprop file 'vc-cvs-sticky-tag))
  225. help-echo
  226. (string
  227. (let ((def-ml (vc-default-mode-line-string 'CVS file)))
  228. (setq help-echo
  229. (get-text-property 0 'help-echo def-ml))
  230. def-ml)))
  231. (propertize
  232. (if (zerop (length sticky-tag))
  233. string
  234. (setq help-echo (format "%s on the '%s' branch"
  235. help-echo sticky-tag))
  236. (concat string "[" sticky-tag "]"))
  237. 'help-echo help-echo)))
  238. ;;;
  239. ;;; State-changing functions
  240. ;;;
  241. (defun vc-cvs-register (files &optional rev comment)
  242. "Register FILES into the CVS version-control system.
  243. COMMENT can be used to provide an initial description of FILES.
  244. Passes either `vc-cvs-register-switches' or `vc-register-switches'
  245. to the CVS command."
  246. ;; Register the directories if needed.
  247. (let (dirs)
  248. (dolist (file files)
  249. (and (not (vc-cvs-responsible-p file))
  250. (vc-cvs-could-register file)
  251. (push (directory-file-name (file-name-directory file)) dirs)))
  252. (if dirs (vc-cvs-register dirs)))
  253. (apply 'vc-cvs-command nil 0 files
  254. "add"
  255. (and comment (string-match "[^\t\n ]" comment)
  256. (concat "-m" comment))
  257. (vc-switches 'CVS 'register)))
  258. (defun vc-cvs-responsible-p (file)
  259. "Return non-nil if CVS thinks it is responsible for FILE."
  260. (file-directory-p (expand-file-name "CVS"
  261. (if (file-directory-p file)
  262. file
  263. (file-name-directory file)))))
  264. (defun vc-cvs-could-register (file)
  265. "Return non-nil if FILE could be registered in CVS.
  266. This is only possible if CVS is managing FILE's directory or one of
  267. its parents."
  268. (let ((dir file))
  269. (while (and (stringp dir)
  270. (not (equal dir (setq dir (file-name-directory dir))))
  271. dir)
  272. (setq dir (if (file-exists-p
  273. (expand-file-name "CVS/Entries" dir))
  274. t
  275. (directory-file-name dir))))
  276. (eq dir t)))
  277. (defun vc-cvs-checkin (files rev comment)
  278. "CVS-specific version of `vc-backend-checkin'."
  279. (unless (or (not rev) (vc-cvs-valid-revision-number-p rev))
  280. (if (not (vc-cvs-valid-symbolic-tag-name-p rev))
  281. (error "%s is not a valid symbolic tag name" rev)
  282. ;; If the input revision is a valid symbolic tag name, we create it
  283. ;; as a branch, commit and switch to it.
  284. (apply 'vc-cvs-command nil 0 files "tag" "-b" (list rev))
  285. (apply 'vc-cvs-command nil 0 files "update" "-r" (list rev))
  286. (mapc (lambda (file) (vc-file-setprop file 'vc-cvs-sticky-tag rev))
  287. files)))
  288. (let ((status (apply 'vc-cvs-command nil 1 files
  289. "ci" (if rev (concat "-r" rev))
  290. (concat "-m" comment)
  291. (vc-switches 'CVS 'checkin))))
  292. (set-buffer "*vc*")
  293. (goto-char (point-min))
  294. (when (not (zerop status))
  295. ;; Check checkin problem.
  296. (cond
  297. ((re-search-forward "Up-to-date check failed" nil t)
  298. (mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge))
  299. files)
  300. (error "%s" (substitute-command-keys
  301. (concat "Up-to-date check failed: "
  302. "type \\[vc-next-action] to merge in changes"))))
  303. (t
  304. (pop-to-buffer (current-buffer))
  305. (goto-char (point-min))
  306. (shrink-window-if-larger-than-buffer)
  307. (error "Check-in failed"))))
  308. ;; Single-file commit? Then update the revision by parsing the buffer.
  309. ;; Otherwise we can't necessarily tell what goes with what; clear
  310. ;; its properties so they have to be refetched.
  311. (if (= (length files) 1)
  312. (vc-file-setprop
  313. (car files) 'vc-working-revision
  314. (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2))
  315. (mapc 'vc-file-clearprops files))
  316. ;; Anyway, forget the checkout model of the file, because we might have
  317. ;; guessed wrong when we found the file. After commit, we can
  318. ;; tell it from the permissions of the file (see
  319. ;; vc-cvs-checkout-model).
  320. (mapc (lambda (file) (vc-file-setprop file 'vc-checkout-model nil))
  321. files)
  322. ;; if this was an explicit check-in (does not include creation of
  323. ;; a branch), remove the sticky tag.
  324. (if (and rev (not (vc-cvs-valid-symbolic-tag-name-p rev)))
  325. (vc-cvs-command nil 0 files "update" "-A"))))
  326. (defun vc-cvs-find-revision (file rev buffer)
  327. (apply 'vc-cvs-command
  328. buffer 0 file
  329. "-Q" ; suppress diagnostic output
  330. "update"
  331. (and rev (not (string= rev ""))
  332. (concat "-r" rev))
  333. "-p"
  334. (vc-switches 'CVS 'checkout)))
  335. (defun vc-cvs-checkout (file &optional editable rev)
  336. "Checkout a revision of FILE into the working area.
  337. EDITABLE non-nil means that the file should be writable.
  338. REV is the revision to check out."
  339. (message "Checking out %s..." file)
  340. ;; Change buffers to get local value of vc-checkout-switches.
  341. (with-current-buffer (or (get-file-buffer file) (current-buffer))
  342. (if (and (file-exists-p file) (not rev))
  343. ;; If no revision was specified, just make the file writable
  344. ;; if necessary (using `cvs-edit' if requested).
  345. (and editable (not (eq (vc-cvs-checkout-model (list file)) 'implicit))
  346. (if vc-cvs-use-edit
  347. (vc-cvs-command nil 0 file "edit")
  348. (set-file-modes file (logior (file-modes file) 128))
  349. (if (equal file buffer-file-name) (toggle-read-only -1))))
  350. ;; Check out a particular revision (or recreate the file).
  351. (vc-file-setprop file 'vc-working-revision nil)
  352. (apply 'vc-cvs-command nil 0 file
  353. (and editable "-w")
  354. "update"
  355. (when rev
  356. (unless (eq rev t)
  357. ;; default for verbose checkout: clear the
  358. ;; sticky tag so that the actual update will
  359. ;; get the head of the trunk
  360. (if (string= rev "")
  361. "-A"
  362. (concat "-r" rev))))
  363. (vc-switches 'CVS 'checkout)))
  364. (vc-mode-line file 'CVS))
  365. (message "Checking out %s...done" file))
  366. (defun vc-cvs-delete-file (file)
  367. (vc-cvs-command nil 0 file "remove" "-f"))
  368. (defun vc-cvs-revert (file &optional contents-done)
  369. "Revert FILE to the working revision on which it was based."
  370. (vc-default-revert 'CVS file contents-done)
  371. (unless (eq (vc-cvs-checkout-model (list file)) 'implicit)
  372. (if vc-cvs-use-edit
  373. (vc-cvs-command nil 0 file "unedit")
  374. ;; Make the file read-only by switching off all w-bits
  375. (set-file-modes file (logand (file-modes file) 3950)))))
  376. (defun vc-cvs-merge (file first-revision &optional second-revision)
  377. "Merge changes into current working copy of FILE.
  378. The changes are between FIRST-REVISION and SECOND-REVISION."
  379. (vc-cvs-command nil 0 file
  380. "update" "-kk"
  381. (concat "-j" first-revision)
  382. (concat "-j" second-revision))
  383. (vc-file-setprop file 'vc-state 'edited)
  384. (with-current-buffer (get-buffer "*vc*")
  385. (goto-char (point-min))
  386. (if (re-search-forward "conflicts during merge" nil t)
  387. (progn
  388. (vc-file-setprop file 'vc-state 'conflict)
  389. ;; signal error
  390. 1)
  391. (vc-file-setprop file 'vc-state 'edited)
  392. ;; signal success
  393. 0)))
  394. (defun vc-cvs-merge-news (file)
  395. "Merge in any new changes made to FILE."
  396. (message "Merging changes into %s..." file)
  397. ;; (vc-file-setprop file 'vc-working-revision nil)
  398. (vc-file-setprop file 'vc-checkout-time 0)
  399. (vc-cvs-command nil nil file "update")
  400. ;; Analyze the merge result reported by CVS, and set
  401. ;; file properties accordingly.
  402. (with-current-buffer (get-buffer "*vc*")
  403. (goto-char (point-min))
  404. ;; get new working revision
  405. (if (re-search-forward
  406. "^Merging differences between [0-9.]* and \\([0-9.]*\\) into" nil t)
  407. (vc-file-setprop file 'vc-working-revision (match-string 1))
  408. (vc-file-setprop file 'vc-working-revision nil))
  409. ;; get file status
  410. (prog1
  411. (if (eq (buffer-size) 0)
  412. 0 ;; there were no news; indicate success
  413. (if (re-search-forward
  414. (concat "^\\([CMUP] \\)?"
  415. (regexp-quote
  416. (substring file (1+ (length (expand-file-name
  417. "." default-directory)))))
  418. "\\( already contains the differences between \\)?")
  419. nil t)
  420. (cond
  421. ;; Merge successful, we are in sync with repository now
  422. ((or (match-string 2)
  423. (string= (match-string 1) "U ")
  424. (string= (match-string 1) "P "))
  425. (vc-file-setprop file 'vc-state 'up-to-date)
  426. (vc-file-setprop file 'vc-checkout-time
  427. (nth 5 (file-attributes file)))
  428. 0);; indicate success to the caller
  429. ;; Merge successful, but our own changes are still in the file
  430. ((string= (match-string 1) "M ")
  431. (vc-file-setprop file 'vc-state 'edited)
  432. 0);; indicate success to the caller
  433. ;; Conflicts detected!
  434. (t
  435. (vc-file-setprop file 'vc-state 'conflict)
  436. 1);; signal the error to the caller
  437. )
  438. (pop-to-buffer "*vc*")
  439. (error "Couldn't analyze cvs update result")))
  440. (message "Merging changes into %s...done" file))))
  441. (defun vc-cvs-modify-change-comment (files rev comment)
  442. "Modify the change comments for FILES on a specified REV.
  443. Will fail unless you have administrative privileges on the repo."
  444. (vc-cvs-command nil 0 files "admin" (concat "-m" rev ":" comment)))
  445. ;;;
  446. ;;; History functions
  447. ;;;
  448. (declare-function vc-rcs-print-log-cleanup "vc-rcs" ())
  449. (defun vc-cvs-print-log (files buffer &optional shortlog start-revision-ignored limit)
  450. "Get change logs associated with FILES."
  451. (require 'vc-rcs)
  452. ;; It's just the catenation of the individual logs.
  453. (vc-cvs-command
  454. buffer
  455. (if (vc-stay-local-p files 'CVS) 'async 0)
  456. files "log")
  457. (with-current-buffer buffer
  458. (vc-exec-after (vc-rcs-print-log-cleanup)))
  459. (when limit 'limit-unsupported))
  460. (defun vc-cvs-comment-history (file)
  461. "Get comment history of a file."
  462. (vc-call-backend 'RCS 'comment-history file))
  463. (defun vc-cvs-diff (files &optional oldvers newvers buffer)
  464. "Get a difference report using CVS between two revisions of FILE."
  465. (let* (process-file-side-effects
  466. (async (and (not vc-disable-async-diff)
  467. (vc-stay-local-p files 'CVS)))
  468. (invoke-cvs-diff-list nil)
  469. status)
  470. ;; Look through the file list and see if any files have backups
  471. ;; that can be used to do a plain "diff" instead of "cvs diff".
  472. (dolist (file files)
  473. (let ((ov oldvers)
  474. (nv newvers))
  475. (when (or (not ov) (string-equal ov ""))
  476. (setq ov (vc-working-revision file)))
  477. (when (string-equal nv "")
  478. (setq nv nil))
  479. (let ((file-oldvers (vc-version-backup-file file ov))
  480. (file-newvers (if (not nv)
  481. file
  482. (vc-version-backup-file file nv)))
  483. (coding-system-for-read (vc-coding-system-for-diff file)))
  484. (if (and file-oldvers file-newvers)
  485. (progn
  486. ;; This used to append diff-switches and vc-diff-switches,
  487. ;; which was consistent with the vc-diff-switches doc at that
  488. ;; time, but not with the actual behavior of any other VC diff.
  489. (apply 'vc-do-command (or buffer "*vc-diff*") 1 "diff" nil
  490. ;; Not a CVS diff, does not use vc-cvs-diff-switches.
  491. (append (vc-switches nil 'diff)
  492. (list (file-relative-name file-oldvers)
  493. (file-relative-name file-newvers))))
  494. (setq status 0))
  495. (push file invoke-cvs-diff-list)))))
  496. (when invoke-cvs-diff-list
  497. (setq status (apply 'vc-cvs-command (or buffer "*vc-diff*")
  498. (if async 'async 1)
  499. invoke-cvs-diff-list "diff"
  500. (and oldvers (concat "-r" oldvers))
  501. (and newvers (concat "-r" newvers))
  502. (vc-switches 'CVS 'diff))))
  503. (if async 1 status))) ; async diff, pessimistic assumption
  504. (defconst vc-cvs-annotate-first-line-re "^[0-9]")
  505. (defun vc-cvs-annotate-process-filter (process string)
  506. (setq string (concat (process-get process 'output) string))
  507. (if (not (string-match vc-cvs-annotate-first-line-re string))
  508. ;; Still waiting for the first real line.
  509. (process-put process 'output string)
  510. (let ((vc-filter (process-get process 'vc-filter)))
  511. (set-process-filter process vc-filter)
  512. (funcall vc-filter process (substring string (match-beginning 0))))))
  513. (defun vc-cvs-annotate-command (file buffer &optional revision)
  514. "Execute \"cvs annotate\" on FILE, inserting the contents in BUFFER.
  515. Optional arg REVISION is a revision to annotate from."
  516. (vc-cvs-command buffer
  517. (if (vc-stay-local-p file 'CVS)
  518. 'async 0)
  519. file "annotate"
  520. (if revision (concat "-r" revision)))
  521. ;; Strip the leading few lines.
  522. (let ((proc (get-buffer-process buffer)))
  523. (if proc
  524. ;; If running asynchronously, use a process filter.
  525. (progn
  526. (process-put proc 'vc-filter (process-filter proc))
  527. (set-process-filter proc 'vc-cvs-annotate-process-filter))
  528. (with-current-buffer buffer
  529. (goto-char (point-min))
  530. (re-search-forward vc-cvs-annotate-first-line-re)
  531. (delete-region (point-min) (1- (point)))))))
  532. (declare-function vc-annotate-convert-time "vc-annotate" (time))
  533. (defun vc-cvs-annotate-current-time ()
  534. "Return the current time, based at midnight of the current day, and
  535. encoded as fractional days."
  536. (vc-annotate-convert-time
  537. (apply 'encode-time 0 0 0 (nthcdr 3 (decode-time (current-time))))))
  538. (defun vc-cvs-annotate-time ()
  539. "Return the time of the next annotation (as fraction of days)
  540. systime, or nil if there is none."
  541. (let* ((bol (point))
  542. (cache (get-text-property bol 'vc-cvs-annotate-time))
  543. (inhibit-read-only t)
  544. (inhibit-modification-hooks t))
  545. (cond
  546. (cache)
  547. ((looking-at
  548. "^\\S-+\\s-+\\S-+\\s-+\\([0-9]+\\)-\\(\\sw+\\)-\\([0-9]+\\)): ")
  549. (let ((day (string-to-number (match-string 1)))
  550. (month (cdr (assq (intern (match-string 2))
  551. '((Jan . 1) (Feb . 2) (Mar . 3)
  552. (Apr . 4) (May . 5) (Jun . 6)
  553. (Jul . 7) (Aug . 8) (Sep . 9)
  554. (Oct . 10) (Nov . 11) (Dec . 12)))))
  555. (year (let ((tmp (string-to-number (match-string 3))))
  556. ;; Years 0..68 are 2000..2068.
  557. ;; Years 69..99 are 1969..1999.
  558. (+ (cond ((> 69 tmp) 2000)
  559. ((> 100 tmp) 1900)
  560. (t 0))
  561. tmp))))
  562. (put-text-property
  563. bol (1+ bol) 'vc-cvs-annotate-time
  564. (setq cache (cons
  565. ;; Position at end makes for nicer overlay result.
  566. ;; Don't put actual buffer pos here, but only relative
  567. ;; distance, so we don't ever move backward in the
  568. ;; goto-char below, even if the text is moved.
  569. (- (match-end 0) (match-beginning 0))
  570. (vc-annotate-convert-time
  571. (encode-time 0 0 0 day month year))))))))
  572. (when cache
  573. (goto-char (+ bol (car cache))) ; Fontify from here to eol.
  574. (cdr cache)))) ; days (float)
  575. (defun vc-cvs-annotate-extract-revision-at-line ()
  576. (save-excursion
  577. (beginning-of-line)
  578. (if (re-search-forward "^\\([0-9]+\\.[0-9]+\\(\\.[0-9]+\\)*\\) +("
  579. (line-end-position) t)
  580. (match-string-no-properties 1)
  581. nil)))
  582. (defun vc-cvs-previous-revision (file rev)
  583. (vc-call-backend 'RCS 'previous-revision file rev))
  584. (defun vc-cvs-next-revision (file rev)
  585. (vc-call-backend 'RCS 'next-revision file rev))
  586. ;; FIXME: This should probably be replaced by code using cvs2cl.
  587. (defun vc-cvs-update-changelog (files)
  588. (vc-call-backend 'RCS 'update-changelog files))
  589. ;;;
  590. ;;; Tag system
  591. ;;;
  592. (defun vc-cvs-create-tag (dir name branchp)
  593. "Assign to DIR's current revision a given NAME.
  594. If BRANCHP is non-nil, the name is created as a branch (and the current
  595. workspace is immediately moved to that new branch)."
  596. (vc-cvs-command nil 0 dir "tag" "-c" (if branchp "-b") name)
  597. (when branchp (vc-cvs-command nil 0 dir "update" "-r" name)))
  598. (defun vc-cvs-retrieve-tag (dir name update)
  599. "Retrieve a tag at and below DIR.
  600. NAME is the name of the tag; if it is empty, do a `cvs update'.
  601. If UPDATE is non-nil, then update (resynch) any affected buffers."
  602. (with-current-buffer (get-buffer-create "*vc*")
  603. (let ((default-directory dir)
  604. (sticky-tag))
  605. (erase-buffer)
  606. (if (or (not name) (string= name ""))
  607. (vc-cvs-command t 0 nil "update")
  608. (vc-cvs-command t 0 nil "update" "-r" name)
  609. (setq sticky-tag name))
  610. (when update
  611. (goto-char (point-min))
  612. (while (not (eobp))
  613. (if (looking-at "\\([CMUP]\\) \\(.*\\)")
  614. (let* ((file (expand-file-name (match-string 2) dir))
  615. (state (match-string 1))
  616. (buffer (find-buffer-visiting file)))
  617. (when buffer
  618. (cond
  619. ((or (string= state "U")
  620. (string= state "P"))
  621. (vc-file-setprop file 'vc-state 'up-to-date)
  622. (vc-file-setprop file 'vc-working-revision nil)
  623. (vc-file-setprop file 'vc-checkout-time
  624. (nth 5 (file-attributes file))))
  625. ((or (string= state "M")
  626. (string= state "C"))
  627. (vc-file-setprop file 'vc-state 'edited)
  628. (vc-file-setprop file 'vc-working-revision nil)
  629. (vc-file-setprop file 'vc-checkout-time 0)))
  630. (vc-file-setprop file 'vc-cvs-sticky-tag sticky-tag)
  631. (vc-resynch-buffer file t t))))
  632. (forward-line 1))))))
  633. ;;;
  634. ;;; Miscellaneous
  635. ;;;
  636. (defun vc-cvs-make-version-backups-p (file)
  637. "Return non-nil if version backups should be made for FILE."
  638. (vc-stay-local-p file 'CVS))
  639. (defun vc-cvs-check-headers ()
  640. "Check if the current file has any headers in it."
  641. (save-excursion
  642. (goto-char (point-min))
  643. (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
  644. \\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
  645. ;;;
  646. ;;; Internal functions
  647. ;;;
  648. (defun vc-cvs-command (buffer okstatus files &rest flags)
  649. "A wrapper around `vc-do-command' for use in vc-cvs.el.
  650. The difference to vc-do-command is that this function always invokes `cvs',
  651. and that it passes `vc-cvs-global-switches' to it before FLAGS."
  652. (apply 'vc-do-command (or buffer "*vc*") okstatus "cvs" files
  653. (if (stringp vc-cvs-global-switches)
  654. (cons vc-cvs-global-switches flags)
  655. (append vc-cvs-global-switches
  656. flags))))
  657. (defun vc-cvs-stay-local-p (file) ;Back-compatibility.
  658. (vc-stay-local-p file 'CVS))
  659. (defun vc-cvs-repository-hostname (dirname)
  660. "Hostname of the CVS server associated to workarea DIRNAME."
  661. (let ((rootname (expand-file-name "CVS/Root" dirname)))
  662. (when (file-readable-p rootname)
  663. (with-temp-buffer
  664. (let ((coding-system-for-read
  665. (or file-name-coding-system
  666. default-file-name-coding-system)))
  667. (vc-insert-file rootname))
  668. (goto-char (point-min))
  669. (nth 2 (vc-cvs-parse-root
  670. (buffer-substring (point)
  671. (line-end-position))))))))
  672. (defun vc-cvs-parse-uhp (path)
  673. "parse user@host/path into (user@host /path)"
  674. (if (string-match "\\([^/]+\\)\\(/.*\\)" path)
  675. (list (match-string 1 path) (match-string 2 path))
  676. (list nil path)))
  677. (defun vc-cvs-parse-root (root)
  678. "Split CVS ROOT specification string into a list of fields.
  679. A CVS root specification of the form
  680. [:METHOD:][[USER@]HOSTNAME]:?/path/to/repository
  681. is converted to a normalized record with the following structure:
  682. \(METHOD USER HOSTNAME CVS-ROOT).
  683. The default METHOD for a CVS root of the form
  684. /path/to/repository
  685. is `local'.
  686. The default METHOD for a CVS root of the form
  687. [USER@]HOSTNAME:/path/to/repository
  688. is `ext'.
  689. For an empty string, nil is returned (invalid CVS root)."
  690. ;; Split CVS root into colon separated fields (0-4).
  691. ;; The `x:' makes sure, that leading colons are not lost;
  692. ;; `HOST:/PATH' is then different from `:METHOD:/PATH'.
  693. (let* ((root-list (cdr (split-string (concat "x:" root) ":")))
  694. (len (length root-list))
  695. ;; All syntactic varieties will get a proper METHOD.
  696. (root-list
  697. (cond
  698. ((= len 0)
  699. ;; Invalid CVS root
  700. nil)
  701. ((= len 1)
  702. (let ((uhp (vc-cvs-parse-uhp (car root-list))))
  703. (cons (if (car uhp) "ext" "local") uhp)))
  704. ((= len 2)
  705. ;; [USER@]HOST:PATH => method `ext'
  706. (and (not (equal (car root-list) ""))
  707. (cons "ext" root-list)))
  708. ((= len 3)
  709. ;; :METHOD:PATH or :METHOD:USER@HOSTNAME/PATH
  710. (cons (cadr root-list)
  711. (vc-cvs-parse-uhp (caddr root-list))))
  712. (t
  713. ;; :METHOD:[USER@]HOST:PATH
  714. (cdr root-list)))))
  715. (if root-list
  716. (let ((method (car root-list))
  717. (uhost (or (cadr root-list) ""))
  718. (root (nth 2 root-list))
  719. user host)
  720. ;; Split USER@HOST
  721. (if (string-match "\\(.*\\)@\\(.*\\)" uhost)
  722. (setq user (match-string 1 uhost)
  723. host (match-string 2 uhost))
  724. (setq host uhost))
  725. ;; Remove empty HOST
  726. (and (equal host "")
  727. (setq host))
  728. ;; Fix windows style CVS root `:local:C:\\project\\cvs\\some\\dir'
  729. (and host
  730. (equal method "local")
  731. (setq root (concat host ":" root) host))
  732. ;; Normalize CVS root record
  733. (list method user host root)))))
  734. ;; XXX: This does not work correctly for subdirectories. "cvs status"
  735. ;; information is context sensitive, it contains lines like:
  736. ;; cvs status: Examining DIRNAME
  737. ;; and the file entries after that don't show the full path.
  738. ;; Because of this VC directory listings only show changed files
  739. ;; at the top level for CVS.
  740. (defun vc-cvs-parse-status (&optional full)
  741. "Parse output of \"cvs status\" command in the current buffer.
  742. Set file properties accordingly. Unless FULL is t, parse only
  743. essential information. Note that this can never set the 'ignored
  744. state."
  745. (let (file status missing)
  746. (goto-char (point-min))
  747. (while (looking-at "? \\(.*\\)")
  748. (setq file (expand-file-name (match-string 1)))
  749. (vc-file-setprop file 'vc-state 'unregistered)
  750. (forward-line 1))
  751. (when (re-search-forward "^File: " nil t)
  752. (when (setq missing (looking-at "no file "))
  753. (goto-char (match-end 0)))
  754. (cond
  755. ((re-search-forward "\\=\\([^ \t]+\\)" nil t)
  756. (setq file (expand-file-name (match-string 1)))
  757. (setq status(if (re-search-forward "\\=[ \t]+Status: \\(.*\\)" nil t)
  758. (match-string 1) "Unknown"))
  759. (when (and full
  760. (re-search-forward
  761. "\\(RCS Version\\|RCS Revision\\|Repository revision\\):\
  762. \[\t ]+\\([0-9.]+\\)"
  763. nil t))
  764. (vc-file-setprop file 'vc-latest-revision (match-string 2)))
  765. (vc-file-setprop
  766. file 'vc-state
  767. (cond
  768. ((string-match "Up-to-date" status)
  769. (vc-file-setprop file 'vc-checkout-time
  770. (nth 5 (file-attributes file)))
  771. 'up-to-date)
  772. ((string-match "Locally Modified" status) 'edited)
  773. ((string-match "Needs Merge" status) 'needs-merge)
  774. ((string-match "Needs \\(Checkout\\|Patch\\)" status)
  775. (if missing 'missing 'needs-update))
  776. ((string-match "Locally Added" status) 'added)
  777. ((string-match "Locally Removed" status) 'removed)
  778. ((string-match "File had conflicts " status) 'conflict)
  779. ((string-match "Unknown" status) 'unregistered)
  780. (t 'edited))))))))
  781. (defun vc-cvs-after-dir-status (update-function)
  782. ;; Heavily inspired by vc-cvs-parse-status. AKA a quick hack.
  783. ;; This needs a lot of testing.
  784. (let ((status nil)
  785. (status-str nil)
  786. (file nil)
  787. (result nil)
  788. (missing nil)
  789. (ignore-next nil)
  790. (subdir default-directory))
  791. (goto-char (point-min))
  792. (while
  793. ;; Look for either a file entry, an unregistered file, or a
  794. ;; directory change.
  795. (re-search-forward
  796. "\\(^=+\n\\([^=c?\n].*\n\\|\n\\)+\\)\\|\\(\\(^?? .*\n\\)+\\)\\|\\(^cvs status: \\(Examining\\|nothing\\) .*\n\\)"
  797. nil t)
  798. ;; FIXME: get rid of narrowing here.
  799. (narrow-to-region (match-beginning 0) (match-end 0))
  800. (goto-char (point-min))
  801. ;; The subdir
  802. (when (looking-at "cvs status: Examining \\(.+\\)")
  803. (setq subdir (expand-file-name (match-string 1))))
  804. ;; Unregistered files
  805. (while (looking-at "? \\(.*\\)")
  806. (setq file (file-relative-name
  807. (expand-file-name (match-string 1) subdir)))
  808. (push (list file 'unregistered) result)
  809. (forward-line 1))
  810. (when (looking-at "cvs status: nothing known about")
  811. ;; We asked about a non existent file. The output looks like this:
  812. ;; cvs status: nothing known about `lisp/v.diff'
  813. ;; ===================================================================
  814. ;; File: no file v.diff Status: Unknown
  815. ;;
  816. ;; Working revision: No entry for v.diff
  817. ;; Repository revision: No revision control file
  818. ;;
  819. ;; Due to narrowing in this iteration we only see the "cvs
  820. ;; status:" line, so just set a flag so that we can ignore the
  821. ;; file in the next iteration.
  822. (setq ignore-next t))
  823. ;; A file entry.
  824. (when (re-search-forward "^File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: \\(.*\\)" nil t)
  825. (setq missing (match-string 1))
  826. (setq file (file-relative-name
  827. (expand-file-name (match-string 2) subdir)))
  828. (setq status-str (match-string 3))
  829. (setq status
  830. (cond
  831. ((string-match "Up-to-date" status-str) 'up-to-date)
  832. ((string-match "Locally Modified" status-str) 'edited)
  833. ((string-match "Needs Merge" status-str) 'needs-merge)
  834. ((string-match "Needs \\(Checkout\\|Patch\\)" status-str)
  835. (if missing 'missing 'needs-update))
  836. ((string-match "Locally Added" status-str) 'added)
  837. ((string-match "Locally Removed" status-str) 'removed)
  838. ((string-match "File had conflicts " status-str) 'conflict)
  839. ((string-match "Unknown" status-str) 'unregistered)
  840. (t 'edited)))
  841. (if ignore-next
  842. (setq ignore-next nil)
  843. (unless (eq status 'up-to-date)
  844. (push (list file status) result))))
  845. (goto-char (point-max))
  846. (widen))
  847. (funcall update-function result))
  848. ;; Alternative implementation: use the "update" command instead of
  849. ;; the "status" command.
  850. ;; (let ((result nil)
  851. ;; (translation '((?? . unregistered)
  852. ;; (?A . added)
  853. ;; (?C . conflict)
  854. ;; (?M . edited)
  855. ;; (?P . needs-merge)
  856. ;; (?R . removed)
  857. ;; (?U . needs-update))))
  858. ;; (goto-char (point-min))
  859. ;; (while (not (eobp))
  860. ;; (if (looking-at "^[ACMPRU?] \\(.*\\)$")
  861. ;; (push (list (match-string 1)
  862. ;; (cdr (assoc (char-after) translation)))
  863. ;; result)
  864. ;; (cond
  865. ;; ((looking-at "cvs update: warning: \\(.*\\) was lost")
  866. ;; ;; Format is:
  867. ;; ;; cvs update: warning: FILENAME was lost
  868. ;; ;; U FILENAME
  869. ;; (push (list (match-string 1) 'missing) result)
  870. ;; ;; Skip the "U" line
  871. ;; (forward-line 1))
  872. ;; ((looking-at "cvs update: New directory `\\(.*\\)' -- ignored")
  873. ;; (push (list (match-string 1) 'unregistered) result))))
  874. ;; (forward-line 1))
  875. ;; (funcall update-function result)))
  876. )
  877. ;; Based on vc-cvs-dir-state-heuristic from Emacs 22.
  878. ;; FIXME does not mention unregistered files.
  879. (defun vc-cvs-dir-status-heuristic (dir update-function &optional basedir)
  880. "Find the CVS state of all files in DIR, using only local information."
  881. (let (file basename status result dirlist)
  882. (with-temp-buffer
  883. (vc-cvs-get-entries dir)
  884. (goto-char (point-min))
  885. (while (not (eobp))
  886. (if (looking-at "D/\\([^/]*\\)////")
  887. (push (expand-file-name (match-string 1) dir) dirlist)
  888. ;; CVS-removed files are not taken under VC control.
  889. (when (looking-at "/\\([^/]*\\)/[^/-]")
  890. (setq basename (match-string 1)
  891. file (expand-file-name basename dir)
  892. status (or (vc-file-getprop file 'vc-state)
  893. (vc-cvs-parse-entry file t)))
  894. (unless (eq status 'up-to-date)
  895. (push (list (if basedir
  896. (file-relative-name file basedir)
  897. basename)
  898. status) result))))
  899. (forward-line 1)))
  900. (dolist (subdir dirlist)
  901. (setq result (append result
  902. (vc-cvs-dir-status-heuristic subdir nil
  903. (or basedir dir)))))
  904. (if basedir result
  905. (funcall update-function result))))
  906. (defun vc-cvs-dir-status (dir update-function)
  907. "Create a list of conses (file . state) for DIR."
  908. ;; FIXME check all files in DIR instead?
  909. (let ((local (vc-stay-local-p dir 'CVS)))
  910. (if (and local (not (eq local 'only-file)))
  911. (vc-cvs-dir-status-heuristic dir update-function)
  912. (vc-cvs-command (current-buffer) 'async dir "-f" "status")
  913. ;; Alternative implementation: use the "update" command instead of
  914. ;; the "status" command.
  915. ;; (vc-cvs-command (current-buffer) 'async
  916. ;; (file-relative-name dir)
  917. ;; "-f" "-n" "update" "-d" "-P")
  918. (vc-exec-after
  919. `(vc-cvs-after-dir-status (quote ,update-function))))))
  920. (defun vc-cvs-dir-status-files (dir files default-state update-function)
  921. "Create a list of conses (file . state) for DIR."
  922. (apply 'vc-cvs-command (current-buffer) 'async dir "-f" "status" files)
  923. (vc-exec-after
  924. `(vc-cvs-after-dir-status (quote ,update-function))))
  925. (defun vc-cvs-file-to-string (file)
  926. "Read the content of FILE and return it as a string."
  927. (condition-case nil
  928. (with-temp-buffer
  929. (insert-file-contents file)
  930. (goto-char (point-min))
  931. (buffer-substring (point) (point-max)))
  932. (file-error nil)))
  933. (defun vc-cvs-dir-extra-headers (dir)
  934. "Extract and represent per-directory properties of a CVS working copy."
  935. (let ((repo
  936. (condition-case nil
  937. (with-temp-buffer
  938. (insert-file-contents "CVS/Root")
  939. (goto-char (point-min))
  940. (and (looking-at ":ext:") (delete-char 5))
  941. (concat (buffer-substring (point) (1- (point-max))) "\n"))
  942. (file-error nil)))
  943. (module
  944. (condition-case nil
  945. (with-temp-buffer
  946. (insert-file-contents "CVS/Repository")
  947. (goto-char (point-min))
  948. (skip-chars-forward "^\n")
  949. (concat (buffer-substring (point-min) (point)) "\n"))
  950. (file-error nil))))
  951. (concat
  952. (cond (repo
  953. (concat (propertize "Repository : " 'face 'font-lock-type-face)
  954. (propertize repo 'face 'font-lock-variable-name-face)))
  955. (t ""))
  956. (cond (module
  957. (concat (propertize "Module : " 'face 'font-lock-type-face)
  958. (propertize module 'face 'font-lock-variable-name-face)))
  959. (t ""))
  960. (if (file-readable-p "CVS/Tag")
  961. (let ((tag (vc-cvs-file-to-string "CVS/Tag")))
  962. (cond
  963. ((string-match "\\`T" tag)
  964. (concat (propertize "Tag : " 'face 'font-lock-type-face)
  965. (propertize (substring tag 1)
  966. 'face 'font-lock-variable-name-face)))
  967. ((string-match "\\`D" tag)
  968. (concat (propertize "Date : " 'face 'font-lock-type-face)
  969. (propertize (substring tag 1)
  970. 'face 'font-lock-variable-name-face)))
  971. (t ""))))
  972. ;; In CVS, branch is a per-file property, not a per-directory property.
  973. ;; We can't really do this here without making dangerous assumptions.
  974. ;;(propertize "Branch: " 'face 'font-lock-type-face)
  975. ;;(propertize "ADD CODE TO PRINT THE BRANCH NAME\n"
  976. ;; 'face 'font-lock-warning-face)
  977. )))
  978. (defun vc-cvs-get-entries (dir)
  979. "Insert the CVS/Entries file from below DIR into the current buffer.
  980. This function ensures that the correct coding system is used for that,
  981. which may not be the one that is used for the files' contents.
  982. CVS/Entries should only be accessed through this function."
  983. (let ((coding-system-for-read (or file-name-coding-system
  984. default-file-name-coding-system)))
  985. (vc-insert-file (expand-file-name "CVS/Entries" dir))))
  986. (defun vc-cvs-valid-symbolic-tag-name-p (tag)
  987. "Return non-nil if TAG is a valid symbolic tag name."
  988. ;; According to the CVS manual, a valid symbolic tag must start with
  989. ;; an uppercase or lowercase letter and can contain uppercase and
  990. ;; lowercase letters, digits, `-', and `_'.
  991. (and (string-match "^[a-zA-Z]" tag)
  992. (not (string-match "[^a-z0-9A-Z-_]" tag))))
  993. (defun vc-cvs-valid-revision-number-p (tag)
  994. "Return non-nil if TAG is a valid revision number."
  995. (and (string-match "^[0-9]" tag)
  996. (not (string-match "[^0-9.]" tag))))
  997. (defun vc-cvs-parse-sticky-tag (match-type match-tag)
  998. "Parse and return the sticky tag as a string.
  999. `match-data' is protected."
  1000. (let ((data (match-data))
  1001. (tag)
  1002. (type (cond ((string= match-type "D") 'date)
  1003. ((string= match-type "T")
  1004. (if (vc-cvs-valid-symbolic-tag-name-p match-tag)
  1005. 'symbolic-name
  1006. 'revision-number))
  1007. (t nil))))
  1008. (unwind-protect
  1009. (progn
  1010. (cond
  1011. ;; Sticky Date tag. Convert to a proper date value (`encode-time')
  1012. ((eq type 'date)
  1013. (string-match
  1014. "\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)"
  1015. match-tag)
  1016. (let* ((year-tmp (string-to-number (match-string 1 match-tag)))
  1017. (month (string-to-number (match-string 2 match-tag)))
  1018. (day (string-to-number (match-string 3 match-tag)))
  1019. (hour (string-to-number (match-string 4 match-tag)))
  1020. (min (string-to-number (match-string 5 match-tag)))
  1021. (sec (string-to-number (match-string 6 match-tag)))
  1022. ;; Years 0..68 are 2000..2068.
  1023. ;; Years 69..99 are 1969..1999.
  1024. (year (+ (cond ((> 69 year-tmp) 2000)
  1025. ((> 100 year-tmp) 1900)
  1026. (t 0))
  1027. year-tmp)))
  1028. (setq tag (encode-time sec min hour day month year))))
  1029. ;; Sticky Tag name or revision number
  1030. ((eq type 'symbolic-name) (setq tag match-tag))
  1031. ((eq type 'revision-number) (setq tag match-tag))
  1032. ;; Default is no sticky tag at all
  1033. (t nil))
  1034. (cond ((eq vc-cvs-sticky-tag-display nil) nil)
  1035. ((eq vc-cvs-sticky-tag-display t)
  1036. (cond ((eq type 'date) (format-time-string
  1037. vc-cvs-sticky-date-format-string
  1038. tag))
  1039. ((eq type 'symbolic-name) tag)
  1040. ((eq type 'revision-number) tag)
  1041. (t nil)))
  1042. ((functionp vc-cvs-sticky-tag-display)
  1043. (funcall vc-cvs-sticky-tag-display tag type))
  1044. (t nil)))
  1045. (set-match-data data))))
  1046. (defun vc-cvs-parse-entry (file &optional set-state)
  1047. "Parse a line from CVS/Entries.
  1048. Compare modification time to that of the FILE, set file properties
  1049. accordingly. However, `vc-state' is set only if optional arg SET-STATE
  1050. is non-nil."
  1051. (cond
  1052. ;; entry for a "locally added" file (not yet committed)
  1053. ((looking-at "/[^/]+/0/")
  1054. (vc-file-setprop file 'vc-checkout-time 0)
  1055. (vc-file-setprop file 'vc-working-revision "0")
  1056. (if set-state (vc-file-setprop file 'vc-state 'added)))
  1057. ;; normal entry
  1058. ((looking-at
  1059. (concat "/[^/]+"
  1060. ;; revision
  1061. "/\\([^/]*\\)"
  1062. ;; timestamp and optional conflict field
  1063. "/\\([^/]*\\)/"
  1064. ;; options
  1065. "\\([^/]*\\)/"
  1066. ;; sticky tag
  1067. "\\(.\\|\\)" ;Sticky tag type (date or tag name, could be empty)
  1068. "\\(.*\\)")) ;Sticky tag
  1069. (vc-file-setprop file 'vc-working-revision (match-string 1))
  1070. (vc-file-setprop file 'vc-cvs-sticky-tag
  1071. (vc-cvs-parse-sticky-tag (match-string 4)
  1072. (match-string 5)))
  1073. ;; Compare checkout time and modification time.
  1074. ;; This is intentionally different from the algorithm that CVS uses
  1075. ;; (which is based on textual comparison), because there can be problems
  1076. ;; generating a time string that looks exactly like the one from CVS.
  1077. (let* ((time (match-string 2))
  1078. (mtime (nth 5 (file-attributes file)))
  1079. (parsed-time (progn (require 'parse-time)
  1080. (parse-time-string (concat time " +0000")))))
  1081. (cond ((and (not (string-match "\\+" time))
  1082. (car parsed-time)
  1083. (equal mtime (apply 'encode-time parsed-time)))
  1084. (vc-file-setprop file 'vc-checkout-time mtime)
  1085. (if set-state (vc-file-setprop file 'vc-state 'up-to-date)))
  1086. (t
  1087. (vc-file-setprop file 'vc-checkout-time 0)
  1088. (if set-state (vc-file-setprop file 'vc-state 'edited))))))))
  1089. ;; Completion of revision names.
  1090. ;; Just so I don't feel like I'm duplicating code from pcl-cvs, I'll use
  1091. ;; `cvs log' so I can list all the revision numbers rather than only
  1092. ;; tag names.
  1093. (defun vc-cvs-revision-table (file)
  1094. (let (process-file-side-effects
  1095. (default-directory (file-name-directory file))
  1096. (res nil))
  1097. (with-temp-buffer
  1098. (vc-cvs-command t nil file "log")
  1099. (goto-char (point-min))
  1100. (when (re-search-forward "^symbolic names:\n" nil t)
  1101. (while (looking-at "^ \\(.*\\): \\(.*\\)")
  1102. (push (cons (match-string 1) (match-string 2)) res)
  1103. (forward-line 1)))
  1104. (while (re-search-forward "^revision \\([0-9.]+\\)" nil t)
  1105. (push (match-string 1) res))
  1106. res)))
  1107. (defun vc-cvs-revision-completion-table (files)
  1108. (lexical-let ((files files)
  1109. table)
  1110. (setq table (lazy-completion-table
  1111. table (lambda () (vc-cvs-revision-table (car files)))))
  1112. table))
  1113. (provide 'vc-cvs)
  1114. ;;; vc-cvs.el ends here