vc-arch.el 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. ;;; vc-arch.el --- VC backend for the Arch version-control system
  2. ;; Copyright (C) 2004-2012 Free Software Foundation, Inc.
  3. ;; Author: FSF (see vc.el for full credits)
  4. ;; Maintainer: Stefan Monnier <monnier@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. ;; The home page of the Arch version control system is at
  19. ;;
  20. ;; http://www.gnuarch.org/
  21. ;;
  22. ;; This is derived from vc-mcvs.el as follows:
  23. ;; - cp vc-mcvs.el vc-arch.el and then M-% mcvs RET arch RET
  24. ;;
  25. ;; Then of course started the hacking.
  26. ;;
  27. ;; What has been partly tested:
  28. ;; - Open a file.
  29. ;; - C-x v = without any prefix arg.
  30. ;; - C-x v v to commit a change to a single file.
  31. ;; Bugs:
  32. ;; - *vc-log*'s initial content lacks the `Summary:' lines.
  33. ;; - All files under the tree are considered as "under Arch's control"
  34. ;; without regards to =tagging-method and such.
  35. ;; - Files are always considered as `edited'.
  36. ;; - C-x v l does not work.
  37. ;; - C-x v i does not work.
  38. ;; - C-x v ~ does not work.
  39. ;; - C-x v u does not work.
  40. ;; - C-x v s does not work.
  41. ;; - C-x v r does not work.
  42. ;; - VC directory listings do not work.
  43. ;; - And more...
  44. ;;; Code:
  45. (eval-when-compile (require 'vc) (require 'cl))
  46. ;;; Properties of the backend
  47. (defun vc-arch-revision-granularity () 'repository)
  48. (defun vc-arch-checkout-model (files) 'implicit)
  49. ;;;
  50. ;;; Customization options
  51. ;;;
  52. (defgroup vc-arch nil
  53. "VC Arch backend."
  54. :version "24.1"
  55. :group 'vc)
  56. ;; It seems Arch diff does not accept many options, so this is not
  57. ;; very useful. It exists mainly so that the VC backends are all
  58. ;; consistent with regards to their treatment of diff switches.
  59. (defcustom vc-arch-diff-switches t
  60. "String or list of strings specifying switches for Arch diff under VC.
  61. If nil, use the value of `vc-diff-switches'. If t, use no switches."
  62. :type '(choice (const :tag "Unspecified" nil)
  63. (const :tag "None" t)
  64. (string :tag "Argument String")
  65. (repeat :tag "Argument List" :value ("") string))
  66. :version "23.1"
  67. :group 'vc-arch)
  68. (define-obsolete-variable-alias 'vc-arch-command 'vc-arch-program "23.1")
  69. (defcustom vc-arch-program
  70. (let ((candidates '("tla" "baz")))
  71. (while (and candidates (not (executable-find (car candidates))))
  72. (setq candidates (cdr candidates)))
  73. (or (car candidates) "tla"))
  74. "Name of the Arch executable."
  75. :type 'string
  76. :group 'vc-arch)
  77. ;; Clear up the cache to force vc-call to check again and discover
  78. ;; new functions when we reload this file.
  79. (put 'Arch 'vc-functions nil)
  80. ;;;###autoload (defun vc-arch-registered (file)
  81. ;;;###autoload (if (vc-find-root file "{arch}/=tagging-method")
  82. ;;;###autoload (progn
  83. ;;;###autoload (load "vc-arch")
  84. ;;;###autoload (vc-arch-registered file))))
  85. (defun vc-arch-add-tagline ()
  86. "Add an `arch-tag' to the end of the current file."
  87. (interactive)
  88. (comment-normalize-vars)
  89. (goto-char (point-max))
  90. (forward-comment -1)
  91. (skip-chars-forward " \t\n")
  92. (cond
  93. ((not (bolp)) (insert "\n\n"))
  94. ((not (eq ?\n (char-before (1- (point))))) (insert "\n")))
  95. (let ((beg (point))
  96. (idfile (and buffer-file-name
  97. (expand-file-name
  98. (concat ".arch-ids/"
  99. (file-name-nondirectory buffer-file-name)
  100. ".id")
  101. (file-name-directory buffer-file-name)))))
  102. (insert "arch-tag: ")
  103. (if (and idfile (file-exists-p idfile))
  104. ;; If the file is unreadable, we do want to get an error here.
  105. (progn
  106. (insert-file-contents idfile)
  107. (forward-line 1)
  108. (delete-file idfile))
  109. (condition-case nil
  110. (call-process "uuidgen" nil t)
  111. (file-error (insert (format "%s <%s> %s"
  112. (current-time-string)
  113. user-mail-address
  114. (+ (nth 2 (current-time))
  115. (buffer-size)))))))
  116. (comment-region beg (point))))
  117. (defconst vc-arch-tagline-re "^\\W*arch-tag:[ \t]*\\(.*[^ \t\n]\\)")
  118. (defmacro vc-with-current-file-buffer (file &rest body)
  119. (declare (indent 2) (debug t))
  120. `(let ((-kill-buf- nil)
  121. (-file- ,file))
  122. (with-current-buffer (or (find-buffer-visiting -file-)
  123. (setq -kill-buf- (generate-new-buffer " temp")))
  124. ;; Avoid find-file-literally since it can do many undesirable extra
  125. ;; things (among which, call us back into an infinite loop).
  126. (if -kill-buf- (insert-file-contents -file-))
  127. (unwind-protect
  128. (progn ,@body)
  129. (if (buffer-live-p -kill-buf-) (kill-buffer -kill-buf-))))))
  130. (defun vc-arch-file-source-p (file)
  131. "Can return nil, `maybe' or a non-nil value.
  132. Only the value `maybe' can be trusted :-(."
  133. ;; FIXME: Check the tag and name of parent dirs.
  134. (unless (string-match "\\`[,+]" (file-name-nondirectory file))
  135. (or (string-match "\\`{arch}/"
  136. (file-relative-name file (vc-arch-root file)))
  137. (file-exists-p
  138. ;; Check the presence of an ID file.
  139. (expand-file-name
  140. (concat ".arch-ids/" (file-name-nondirectory file) ".id")
  141. (file-name-directory file)))
  142. ;; Check the presence of a tagline.
  143. (vc-with-current-file-buffer file
  144. (save-excursion
  145. (goto-char (point-max))
  146. (or (re-search-backward vc-arch-tagline-re (- (point) 1000) t)
  147. (progn
  148. (goto-char (point-min))
  149. (re-search-forward vc-arch-tagline-re (+ (point) 1000) t)))))
  150. ;; FIXME: check =tagging-method to see whether untagged files might
  151. ;; be source or not.
  152. (with-current-buffer
  153. (find-file-noselect (expand-file-name "{arch}/=tagging-method"
  154. (vc-arch-root file)))
  155. (let ((untagged-source t)) ;Default is `names'.
  156. (save-excursion
  157. (goto-char (point-min))
  158. (if (re-search-forward "^[ \t]*\\(\\(tagline\\|implicit\\|names\\)\\|explicit\\)" nil t)
  159. (setq untagged-source (match-end 2)))
  160. (if (re-search-forward "^[ \t]*untagged-source[ \t]+\\(\\(source\\)\\|precious\\|backup\\|junk\\|unrecognized\\)" nil t)
  161. (setq untagged-source (match-end 2))))
  162. (if untagged-source 'maybe))))))
  163. (defun vc-arch-file-id (file)
  164. ;; Don't include the kind of ID this is because it seems to be too messy.
  165. (let ((idfile (expand-file-name
  166. (concat ".arch-ids/" (file-name-nondirectory file) ".id")
  167. (file-name-directory file))))
  168. (if (file-exists-p idfile)
  169. (with-temp-buffer
  170. (insert-file-contents idfile)
  171. (looking-at ".*[^ \n\t]")
  172. (match-string 0))
  173. (with-current-buffer (find-file-noselect file)
  174. (save-excursion
  175. (goto-char (point-max))
  176. (if (or (re-search-backward vc-arch-tagline-re (- (point) 1000) t)
  177. (progn
  178. (goto-char (point-min))
  179. (re-search-forward vc-arch-tagline-re (+ (point) 1000) t)))
  180. (match-string 1)
  181. (concat "./" (file-relative-name file (vc-arch-root file)))))))))
  182. (defun vc-arch-tagging-method (file)
  183. (with-current-buffer
  184. (find-file-noselect
  185. (expand-file-name "{arch}/=tagging-method" (vc-arch-root file)))
  186. (save-excursion
  187. (goto-char (point-min))
  188. (if (re-search-forward
  189. "^[ \t]*\\(tagline\\|implicit\\|names\\|explicit\\)" nil t)
  190. (intern (match-string 1))
  191. 'names))))
  192. (defun vc-arch-root (file)
  193. "Return the root directory of an Arch project, if any."
  194. (or (vc-file-getprop file 'arch-root)
  195. ;; Check the =tagging-method, in case someone naively manually
  196. ;; creates a {arch} directory somewhere.
  197. (let ((root (vc-find-root file "{arch}/=tagging-method")))
  198. (when root
  199. (vc-file-setprop
  200. file 'arch-root root)))))
  201. (defun vc-arch-register (files &optional rev comment)
  202. (if rev (error "Explicit initial revision not supported for Arch"))
  203. (dolist (file files)
  204. (let ((tagmet (vc-arch-tagging-method file)))
  205. (if (and (memq tagmet '(tagline implicit)) comment-start)
  206. (with-current-buffer (find-file-noselect file)
  207. (if (buffer-modified-p)
  208. (error "Save %s first" (buffer-name)))
  209. (vc-arch-add-tagline)
  210. (save-buffer)))))
  211. (vc-arch-command nil 0 files "add"))
  212. (defun vc-arch-registered (file)
  213. ;; Don't seriously check whether it's source or not. Checking would
  214. ;; require running TLA, so it's better to not do it, so it also works if
  215. ;; TLA is not installed.
  216. (and (vc-arch-root file)
  217. (vc-arch-file-source-p file)))
  218. (defun vc-arch-default-version (file)
  219. (or (vc-file-getprop (vc-arch-root file) 'arch-default-version)
  220. (let* ((root (vc-arch-root file))
  221. (f (expand-file-name "{arch}/++default-version" root)))
  222. (if (file-readable-p f)
  223. (vc-file-setprop
  224. root 'arch-default-version
  225. (with-temp-buffer
  226. (insert-file-contents f)
  227. ;; Strip the terminating newline.
  228. (buffer-substring (point-min) (1- (point-max)))))))))
  229. (defun vc-arch-workfile-unchanged-p (file)
  230. "Stub: arch workfiles are always considered to be in a changed state,"
  231. nil)
  232. (defun vc-arch-state (file)
  233. ;; There's no checkout operation and merging is not done from VC
  234. ;; so the only operation that's state dependent that VC supports is commit
  235. ;; which is only activated if the file is `edited'.
  236. (let* ((root (vc-arch-root file))
  237. (ver (vc-arch-default-version file))
  238. (pat (concat "\\`" (subst-char-in-string ?/ ?% ver)))
  239. (dir (expand-file-name ",,inode-sigs/"
  240. (expand-file-name "{arch}" root)))
  241. (sigfile nil))
  242. (dolist (f (if (file-directory-p dir) (directory-files dir t pat)))
  243. (if (or (not sigfile) (file-newer-than-file-p f sigfile))
  244. (setq sigfile f)))
  245. (if (not sigfile)
  246. 'edited ;We know nothing.
  247. (let ((id (vc-arch-file-id file)))
  248. (setq id (replace-regexp-in-string "[ \t]" "_" id))
  249. (with-current-buffer (find-file-noselect sigfile)
  250. (goto-char (point-min))
  251. (while (and (search-forward id nil 'move)
  252. (save-excursion
  253. (goto-char (- (match-beginning 0) 2))
  254. ;; For `names', the lines start with `?./foo/bar'.
  255. ;; For others there's 2 chars before the ./foo/bar.
  256. (or (not (or (bolp) (looking-at "\n?")))
  257. ;; Ignore E_ entries used for foo.id files.
  258. (looking-at "E_")))))
  259. (if (eobp)
  260. ;; ID not found.
  261. (if (equal (file-name-nondirectory sigfile)
  262. (subst-char-in-string
  263. ?/ ?% (vc-arch-working-revision file)))
  264. 'added
  265. ;; Might be `added' or `up-to-date' as well.
  266. ;; FIXME: Check in the patch logs to find out.
  267. 'edited)
  268. ;; Found the ID, let's check the inode.
  269. (if (not (re-search-forward
  270. "\t.*mtime=\\([0-9]+\\):size=\\([0-9]+\\)"
  271. (line-end-position) t))
  272. ;; Buh? Unexpected format.
  273. 'edited
  274. (let ((ats (file-attributes file)))
  275. (if (and (eq (nth 7 ats) (string-to-number (match-string 2)))
  276. (equal (format-time-string "%s" (nth 5 ats))
  277. (match-string 1)))
  278. 'up-to-date
  279. 'edited)))))))))
  280. (defun vc-arch-dir-status (dir callback)
  281. "Run 'tla inventory' for DIR and pass results to CALLBACK.
  282. CALLBACK expects (ENTRIES &optional MORE-TO-COME); see
  283. `vc-dir-refresh'."
  284. (let ((default-directory dir))
  285. (vc-arch-command t 'async nil "changes"))
  286. ;; The updating could be done asynchronously.
  287. (vc-exec-after
  288. `(vc-arch-after-dir-status ',callback)))
  289. (defun vc-arch-after-dir-status (callback)
  290. (let* ((state-map '(("M " . edited)
  291. ("Mb" . edited) ;binary
  292. ("D " . removed)
  293. ("D/" . removed) ;directory
  294. ("A " . added)
  295. ("A/" . added) ;directory
  296. ("=>" . renamed)
  297. ("/>" . renamed) ;directory
  298. ("lf" . symlink-to-file)
  299. ("fl" . file-to-symlink)
  300. ("--" . permissions-changed)
  301. ("-/" . permissions-changed) ;directory
  302. ))
  303. (state-map-regexp (regexp-opt (mapcar 'car state-map) t))
  304. (entry-regexp (concat "^" state-map-regexp " \\(.*\\)$"))
  305. result)
  306. (goto-char (point-min))
  307. ;;(message "Got %s" (buffer-string))
  308. (while (re-search-forward entry-regexp nil t)
  309. (let* ((state-string (match-string 1))
  310. (state (cdr (assoc state-string state-map)))
  311. (filename (match-string 2)))
  312. (push (list filename state) result)))
  313. (funcall callback result nil)))
  314. (defun vc-arch-working-revision (file)
  315. (let* ((root (expand-file-name "{arch}" (vc-arch-root file)))
  316. (defbranch (vc-arch-default-version file)))
  317. (when (and defbranch (string-match "\\`\\(.+@[^/\n]+\\)/\\(\\(\\(.*?\\)\\(?:--.*\\)?\\)--.*\\)\\'" defbranch))
  318. (let* ((archive (match-string 1 defbranch))
  319. (category (match-string 4 defbranch))
  320. (branch (match-string 3 defbranch))
  321. (version (match-string 2 defbranch))
  322. (sealed nil) (rev-nb 0)
  323. (rev nil)
  324. logdir tmp)
  325. (setq logdir (expand-file-name category root))
  326. (setq logdir (expand-file-name branch logdir))
  327. (setq logdir (expand-file-name version logdir))
  328. (setq logdir (expand-file-name archive logdir))
  329. (setq logdir (expand-file-name "patch-log" logdir))
  330. (dolist (file (if (file-directory-p logdir) (directory-files logdir)))
  331. ;; Revision names go: base-0, patch-N, version-0, versionfix-M.
  332. (when (and (eq (aref file 0) ?v) (not sealed))
  333. (setq sealed t rev-nb 0))
  334. (if (and (string-match "-\\([0-9]+\\)\\'" file)
  335. (setq tmp (string-to-number (match-string 1 file)))
  336. (or (not sealed) (eq (aref file 0) ?v))
  337. (>= tmp rev-nb))
  338. (setq rev-nb tmp rev file)))
  339. ;; Use "none-000" if the tree hasn't yet been committed on the
  340. ;; default branch. We'll then get "Arch:000[branch]" on the mode-line.
  341. (concat defbranch "--" (or rev "none-000"))))))
  342. (defcustom vc-arch-mode-line-rewrite
  343. '(("\\`.*--\\(.*--.*\\)--\\(v?\\).*-\\([0-9]+\\)\\'" . "\\2\\3[\\1]"))
  344. "Rewrite rules to shorten Arch's revision names on the mode-line."
  345. :type '(repeat (cons regexp string))
  346. :group 'vc-arch)
  347. (defun vc-arch-mode-line-string (file)
  348. "Return string for placement in modeline by `vc-mode-line' for FILE."
  349. (let ((rev (vc-working-revision file)))
  350. (dolist (rule vc-arch-mode-line-rewrite)
  351. (if (string-match (car rule) rev)
  352. (setq rev (replace-match (cdr rule) t nil rev))))
  353. (format "Arch%c%s"
  354. (case (vc-state file)
  355. ((up-to-date needs-update) ?-)
  356. (added ?@)
  357. (t ?:))
  358. rev)))
  359. (defun vc-arch-diff3-rej-p (rej)
  360. (let ((attrs (file-attributes rej)))
  361. (and attrs (< (nth 7 attrs) 60)
  362. (with-temp-buffer
  363. (insert-file-contents rej)
  364. (goto-char (point-min))
  365. (looking-at "Conflicts occurred, diff3 conflict markers left in file\\.")))))
  366. (defun vc-arch-delete-rej-if-obsolete ()
  367. "For use in `after-save-hook'."
  368. (save-excursion
  369. (let ((rej (concat buffer-file-name ".rej")))
  370. (when (and buffer-file-name (vc-arch-diff3-rej-p rej))
  371. (unless (re-search-forward "^<<<<<<< " nil t)
  372. ;; The .rej file is obsolete.
  373. (condition-case nil (delete-file rej) (error nil))
  374. ;; Remove the hook so that it is not called multiple times.
  375. (remove-hook 'after-save-hook 'vc-arch-delete-rej-if-obsolete t))))))
  376. (defun vc-arch-find-file-hook ()
  377. (let ((rej (concat buffer-file-name ".rej")))
  378. (when (and buffer-file-name (file-exists-p rej))
  379. (if (vc-arch-diff3-rej-p rej)
  380. (save-excursion
  381. (goto-char (point-min))
  382. (if (not (re-search-forward "^<<<<<<< " nil t))
  383. ;; The .rej file is obsolete.
  384. (condition-case nil (delete-file rej) (error nil))
  385. (smerge-mode 1)
  386. (add-hook 'after-save-hook
  387. 'vc-arch-delete-rej-if-obsolete nil t)
  388. (message "There are unresolved conflicts in this file")))
  389. (message "There are unresolved conflicts in %s"
  390. (file-name-nondirectory rej))))))
  391. (defun vc-arch-checkin (files rev comment)
  392. (if rev (error "Committing to a specific revision is unsupported"))
  393. ;; FIXME: This implementation probably only works for singleton filesets
  394. (let ((summary (file-relative-name (car files) (vc-arch-root (car files)))))
  395. ;; Extract a summary from the comment.
  396. (when (or (string-match "\\`Summary:[ \t]*\\(.*[^ \t\n]\\)\\([ \t]*\n\\)*" comment)
  397. (string-match "\\`[ \t]*\\(.*[^ \t\n]\\)[ \t]*\\(\n?\\'\\|\n\\([ \t]*\n\\)+\\)" comment))
  398. (setq summary (match-string 1 comment))
  399. (setq comment (substring comment (match-end 0))))
  400. (vc-arch-command nil 0 files "commit" "-s" summary "-L" comment "--"
  401. (vc-switches 'Arch 'checkin))))
  402. (defun vc-arch-diff (files &optional oldvers newvers buffer)
  403. "Get a difference report using Arch between two versions of FILES."
  404. ;; FIXME: This implementation only works for singleton filesets. To make
  405. ;; it work for more cases, we have to either call `file-diffs' manually on
  406. ;; each and every `file' in the fileset, or use `changes --diffs' (and
  407. ;; variants) and maybe filter the output with `filterdiff' to only include
  408. ;; the files in which we're interested.
  409. (let ((file (car files)))
  410. (if (and newvers
  411. (vc-up-to-date-p file)
  412. (equal newvers (vc-working-revision file)))
  413. ;; Newvers is the base revision and the current file is unchanged,
  414. ;; so we can diff with the current file.
  415. (setq newvers nil))
  416. (if newvers
  417. (error "Diffing specific revisions not implemented")
  418. (let* (process-file-side-effects
  419. (async (not vc-disable-async-diff))
  420. ;; Run the command from the root dir.
  421. (default-directory (vc-arch-root file))
  422. (status
  423. (vc-arch-command
  424. (or buffer "*vc-diff*")
  425. (if async 'async 1)
  426. nil "file-diffs"
  427. (vc-switches 'Arch 'diff)
  428. (file-relative-name file)
  429. (if (equal oldvers (vc-working-revision file))
  430. nil
  431. oldvers))))
  432. (if async 1 status))))) ; async diff, pessimistic assumption.
  433. (defun vc-arch-delete-file (file)
  434. (vc-arch-command nil 0 file "rm"))
  435. (defun vc-arch-rename-file (old new)
  436. (vc-arch-command nil 0 new "mv" (file-relative-name old)))
  437. (defalias 'vc-arch-responsible-p 'vc-arch-root)
  438. (defun vc-arch-command (buffer okstatus file &rest flags)
  439. "A wrapper around `vc-do-command' for use in vc-arch.el."
  440. (apply 'vc-do-command (or buffer "*vc*") okstatus vc-arch-program file flags))
  441. (defun vc-arch-init-revision () nil)
  442. ;;; Completion of versions and revisions.
  443. (defun vc-arch--version-completion-table (root string)
  444. (delq nil
  445. (mapcar
  446. (lambda (d)
  447. (when (string-match "/\\([^/]+\\)/\\([^/]+\\)\\'" d)
  448. (concat (match-string 2 d) "/" (match-string 1 d))))
  449. (let ((default-directory root))
  450. (file-expand-wildcards
  451. (concat "*/*/"
  452. (if (string-match "/" string)
  453. (concat (substring string (match-end 0))
  454. "*/" (substring string 0 (match-beginning 0)))
  455. (concat "*/" string))
  456. "*"))))))
  457. (defun vc-arch-revision-completion-table (files)
  458. (lexical-let ((files files))
  459. (lambda (string pred action)
  460. ;; FIXME: complete revision patches as well.
  461. (let* ((root (expand-file-name "{arch}" (vc-arch-root (car files))))
  462. (table (vc-arch--version-completion-table root string)))
  463. (complete-with-action action table string pred)))))
  464. ;;; Trimming revision libraries.
  465. ;; This code is not directly related to VC and there are many variants of
  466. ;; this functionality available as scripts, but I like this version better,
  467. ;; so maybe others will like it too.
  468. (defun vc-arch-trim-find-least-useful-rev (revs)
  469. (let* ((first (pop revs))
  470. (second (pop revs))
  471. (third (pop revs))
  472. ;; We try to give more importance to recent revisions. The idea is
  473. ;; that it's OK if checking out a revision 1000-patch-old is ten
  474. ;; times slower than checking out a revision 100-patch-old. But at
  475. ;; the same time a 2-patch-old rev isn't really ten times more
  476. ;; important than a 20-patch-old, so we use an arbitrary constant
  477. ;; "100" to reduce this effect for recent revisions. Making this
  478. ;; constant a float has the side effect of causing the subsequent
  479. ;; computations to be done as floats as well.
  480. (max (+ 100.0 (car (or (car (last revs)) third))))
  481. (cost (lambda () (/ (- (car third) (car first)) (- max (car second)))))
  482. (minrev second)
  483. (mincost (funcall cost)))
  484. (while revs
  485. (setq first second)
  486. (setq second third)
  487. (setq third (pop revs))
  488. (when (< (funcall cost) mincost)
  489. (setq minrev second)
  490. (setq mincost (funcall cost))))
  491. minrev))
  492. (defun vc-arch-trim-make-sentinel (revs)
  493. (if (null revs) (lambda (proc msg) (message "VC-Arch trimming ... done"))
  494. (lexical-let ((revs revs))
  495. (lambda (proc msg)
  496. (message "VC-Arch trimming %s..." (file-name-nondirectory (car revs)))
  497. (rename-file (car revs) (concat (car revs) "*rm*"))
  498. (setq proc (start-process "vc-arch-trim" nil
  499. "rm" "-rf" (concat (car revs) "*rm*")))
  500. (set-process-sentinel proc (vc-arch-trim-make-sentinel (cdr revs)))))))
  501. (defun vc-arch-trim-one-revlib (dir)
  502. "Delete half of the revisions in the revision library."
  503. (interactive "Ddirectory: ")
  504. (let ((garbage (directory-files dir 'full "\\`,," 'nosort)))
  505. (when garbage
  506. (funcall (vc-arch-trim-make-sentinel garbage) nil nil)))
  507. (let ((revs
  508. (sort (delq nil
  509. (mapcar
  510. (lambda (f)
  511. (when (string-match "-\\([0-9]+\\)\\'" f)
  512. (cons (string-to-number (match-string 1 f)) f)))
  513. (directory-files dir nil nil 'nosort)))
  514. 'car-less-than-car))
  515. (subdirs nil))
  516. (when (cddr revs)
  517. (dotimes (i (/ (length revs) 2))
  518. (let ((minrev (vc-arch-trim-find-least-useful-rev revs)))
  519. (setq revs (delq minrev revs))
  520. (push minrev subdirs)))
  521. (funcall (vc-arch-trim-make-sentinel
  522. (mapcar (lambda (x) (expand-file-name (cdr x) dir)) subdirs))
  523. nil nil))))
  524. (defun vc-arch-trim-revlib ()
  525. "Delete half of the revisions in the revision library."
  526. (interactive)
  527. (let ((rl-dir (with-output-to-string
  528. (call-process vc-arch-program nil standard-output nil
  529. "my-revision-library"))))
  530. (while (string-match "\\(.*\\)\n" rl-dir)
  531. (let ((dir (match-string 1 rl-dir)))
  532. (setq rl-dir
  533. (if (and (file-directory-p dir) (file-writable-p dir))
  534. dir
  535. (substring rl-dir (match-end 0))))))
  536. (unless (file-writable-p rl-dir)
  537. (error "No writable revlib directory found"))
  538. (message "Revlib at %s" rl-dir)
  539. (let* ((archives (directory-files rl-dir 'full "[^.]\\|..."))
  540. (categories
  541. (apply 'append
  542. (mapcar (lambda (dir)
  543. (when (file-directory-p dir)
  544. (directory-files dir 'full "[^.]\\|...")))
  545. archives)))
  546. (branches
  547. (apply 'append
  548. (mapcar (lambda (dir)
  549. (when (file-directory-p dir)
  550. (directory-files dir 'full "[^.]\\|...")))
  551. categories)))
  552. (versions
  553. (apply 'append
  554. (mapcar (lambda (dir)
  555. (when (file-directory-p dir)
  556. (directory-files dir 'full "--.*--")))
  557. branches))))
  558. (mapc 'vc-arch-trim-one-revlib versions))
  559. ))
  560. (defvar vc-arch-extra-menu-map
  561. (let ((map (make-sparse-keymap)))
  562. (define-key map [add-tagline]
  563. '(menu-item "Add tagline" vc-arch-add-tagline))
  564. map))
  565. (defun vc-arch-extra-menu () vc-arch-extra-menu-map)
  566. ;;; Less obvious implementations.
  567. (defun vc-arch-find-revision (file rev buffer)
  568. (let ((out (make-temp-file "vc-out")))
  569. (unwind-protect
  570. (progn
  571. (with-temp-buffer
  572. (vc-arch-command (current-buffer) 1 nil "file-diffs" file rev)
  573. (call-process-region (point-min) (point-max)
  574. "patch" nil nil nil "-R" "-o" out file))
  575. (with-current-buffer buffer
  576. (insert-file-contents out)))
  577. (delete-file out))))
  578. (provide 'vc-arch)
  579. ;;; vc-arch.el ends here