aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Colascione2016-02-08 10:52:54 -0800
committerDaniel Colascione2016-02-08 10:52:54 -0800
commitde76a167dc09dc695a5acebabb7ab354a6bf556e (patch)
tree7a01b94d12d60528904924f64aeaaff47a42be9d
parent4c629d8a8d640354690ce207dff09c7fda070d2a (diff)
downloademacs-de76a167dc09dc695a5acebabb7ab354a6bf556e.tar.gz
emacs-de76a167dc09dc695a5acebabb7ab354a6bf556e.zip
Performance improvements for vc-hg
Teach vc-hg how to read some Mercurial internal data structures, allowing us to avoid the need to run hg status -A, which is very slow for large repositories. Fall back to running hg if anything looks funny. vc-hg now puts the _working directory_ revision in the modeline instead of the file revision, which greatly improves performance and which allows us to again skip running hg in the case that we have an active bookmark. * lisp/vc/vc-hg.el (vc-hg-state): Try calling `vc-hg-statefast' (vc-hg-symbolic-revision-styles) (vc-hg-use-file-version-for-mode-line-version) (vc-hg-parse-hg-data-structures): New user preferences (vc-hg--active-bookmark-internal, vc-hg--run-log) (vc-hg--symbolic-revision, vc-hg-mode-line-string) (vc-hg--read-u8, vc-hg--read-u32-be) (vc-hg--raw-dirstate-search, vc-hg--cached-dirstate-search) (vc-hg--parts-to-string, vc-hg--pcre-to-elisp-re) (vc-hg--glob-to-pcre, vc-hg--hgignore-add-pcre) (vc-hg--hgignore-add-glob, vc-hg--hgignore-add-path) (vc-hg--slurp-hgignore-1, vc-hg--slurp-hgignore) (vc-hg--ignore-patterns-valid-p) (vc-hg--ignore-patterns-ignored-p, vc-hg--time-to-fixnum) (vc-hg--file-ignored-p, vc-hg--read-repo-requirements) (vc-hg--requirements-understood-p, vc-hg--dirstate-scan-cache) (vc-hg-state-fast): New functions. (vc-hg--hgignore-patterns, vc-hg--hgignore-filenames) (vc-hg--cached-ignore-patterns, vc-hg--dirstate-scan-cache) (vc-hg--dirstate-scan-cache): New internal variables. * lisp/vc/vc-hooks.el (vc-refresh-state): Invoke vc find-file-hook before updating modeline.
-rw-r--r--lisp/vc/vc-hg.el613
-rw-r--r--lisp/vc/vc-hooks.el8
2 files changed, 616 insertions, 5 deletions
diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el
index 2d8bab70598..702772cf5ab 100644
--- a/lisp/vc/vc-hg.el
+++ b/lisp/vc/vc-hg.el
@@ -48,7 +48,7 @@
48;; - dir-printer (fileinfo) OK 48;; - dir-printer (fileinfo) OK
49;; * working-revision (file) OK 49;; * working-revision (file) OK
50;; * checkout-model (files) OK 50;; * checkout-model (files) OK
51;; - mode-line-string (file) NOT NEEDED 51;; - mode-line-string (file) OK
52;; STATE-CHANGING FUNCTIONS 52;; STATE-CHANGING FUNCTIONS
53;; * register (files &optional rev comment) OK 53;; * register (files &optional rev comment) OK
54;; * create-repo () OK 54;; * create-repo () OK
@@ -197,6 +197,11 @@ highlighting the Log View buffer."
197 197
198(defun vc-hg-state (file) 198(defun vc-hg-state (file)
199 "Hg-specific version of `vc-state'." 199 "Hg-specific version of `vc-state'."
200 (let ((state (vc-hg-state-fast file)))
201 (if (eq state 'unsupported) (vc-hg-state-slow file) state)))
202
203(defun vc-hg-state-slow (file)
204 "Determine status of FILE by running hg."
200 (setq file (expand-file-name file)) 205 (setq file (expand-file-name file))
201 (let* 206 (let*
202 ((status nil) 207 ((status nil)
@@ -245,6 +250,130 @@ highlighting the Log View buffer."
245 "parent" "--template" "{rev}"))) 250 "parent" "--template" "{rev}")))
246 "0")) 251 "0"))
247 252
253(defcustom vc-hg-symbolic-revision-styles
254 '(builtin-active-bookmark
255 "{if(bookmarks,sub(' ',',',bookmarks),if(phabdiff,phabdiff,shortest(node,6)))}")
256 "List of ways to present versions symbolically. The version
257that we use is the first one that successfully produces a
258non-empty string.
259
260Each entry in the list can be either:
261
262- The symbol `builtin-active-bookmark', which indicates that we
263should use the active bookmark if one exists. A template can
264supply this information as well, but `builtin-active-bookmark' is
265handled entirely inside Emacs and so is more efficient than using
266the generic Mercurial mechanism.
267
268- A string giving the Mercurial template to supply to \"hg
269parent\". \"hg help template\" may be useful reading.
270
271- A function to call; it should accept two arguments (a revision
272and an optional path to which to limit history) and produce a
273string. The function is called with `default-directory' set to
274within the repository.
275
276If no list entry produces a useful revision, return `nil'."
277 :type '(repeat (choice
278 (const :tag "Active bookmark" 'bookmark)
279 (string :tag "Hg template")
280 (function :tag "Custom")))
281 :version "25.2"
282 :group 'vc-hg)
283
284(defcustom vc-hg-use-file-version-for-mode-line-version nil
285 "When enabled, the modeline will contain revision informtion for the visited file.
286When not, the revision in the modeline is for the repository
287working copy. `nil' is the much faster setting for
288large repositories."
289 :type 'boolean
290 :version "25.2"
291 :group 'vc-hg)
292
293(defun vc-hg--active-bookmark-internal (rev)
294 (when (equal rev ".")
295 (let* ((current-bookmarks-file ".hg/bookmarks.current"))
296 (when (file-exists-p current-bookmarks-file)
297 (ignore-errors
298 (with-temp-buffer
299 (insert-file-contents current-bookmarks-file)
300 (buffer-substring-no-properties
301 (point-min) (point-max))))))))
302
303(defun vc-hg--run-log (template rev path)
304 (ignore-errors
305 (with-output-to-string
306 (if path
307 (vc-hg-command
308 standard-output 0 nil
309 "log" "-f" "-l1" "--template" template path)
310 (vc-hg-command
311 standard-output 0 nil
312 "log" "-r" rev "-l1" "--template" template)))))
313
314(defun vc-hg--symbolic-revision (rev &optional path)
315 "Make a Mercurial revision human-readable.
316REV is a Mercurial revision. `default-directory' is assumed to
317be in the repository root of interest. PATH, if set, is a
318specific file to query."
319 (let ((symbolic-revision nil)
320 (styles vc-hg-symbolic-revision-styles))
321 (while (and (not symbolic-revision) styles)
322 (let ((style (pop styles)))
323 (setf symbolic-revision
324 (cond ((and (null path) (eq style 'builtin-active-bookmark))
325 (vc-hg--active-bookmark-internal rev))
326 ((stringp style)
327 (vc-hg--run-log style rev path))
328 ((functionp style)
329 (funcall style rev path))))))
330 symbolic-revision))
331
332(defun vc-hg-mode-line-string (file)
333 "Hg-specific version of `vc-mode-line-string'."
334 (let* ((backend-name "Hg")
335 (truename (file-truename file))
336 (state (vc-state truename))
337 (state-echo nil)
338 (face nil)
339 (rev (and state
340 (let ((default-directory
341 (expand-file-name (vc-hg-root truename))))
342 (vc-hg--symbolic-revision
343 "."
344 (and vc-hg-use-file-version-for-mode-line-version
345 truename)))))
346 (rev (or rev "???")))
347 (propertize
348 (cond ((or (eq state 'up-to-date)
349 (eq state 'needs-update))
350 (setq state-echo "Up to date file")
351 (setq face 'vc-up-to-date-state)
352 (concat backend-name "-" rev))
353 ((eq state 'added)
354 (setq state-echo "Locally added file")
355 (setq face 'vc-locally-added-state)
356 (concat backend-name "@" rev))
357 ((eq state 'conflict)
358 (setq state-echo "File contains conflicts after the last merge")
359 (setq face 'vc-conflict-state)
360 (concat backend-name "!" rev))
361 ((eq state 'removed)
362 (setq state-echo "File removed from the VC system")
363 (setq face 'vc-removed-state)
364 (concat backend-name "!" rev))
365 ((eq state 'missing)
366 (setq state-echo "File tracked by the VC system, but missing from the file system")
367 (setq face 'vc-missing-state)
368 (concat backend-name "?" rev))
369 (t
370 (setq state-echo "Locally modified file")
371 (setq face 'vc-edited-state)
372 (concat backend-name ":" rev)))
373 'face face
374 'help-echo (concat state-echo " under the " backend-name
375 " version control system"))))
376
248;;; History functions 377;;; History functions
249 378
250(defcustom vc-hg-log-switches nil 379(defcustom vc-hg-log-switches nil
@@ -435,6 +564,488 @@ Optional arg REVISION is a revision to annotate from."
435 ;; TODO: update *vc-change-log* buffer so can see @ if --graph 564 ;; TODO: update *vc-change-log* buffer so can see @ if --graph
436 )) 565 ))
437 566
567;;; Native data structure reading
568
569(defcustom vc-hg-parse-hg-data-structures t
570 "If true, try directly parsing Mercurial data structures
571directly instead of always running Mercurial. We try to be safe
572against Mercurial data structure format changes and always fall
573back to running Mercurial directly."
574 :type 'boolean
575 :version "25.2"
576 :group 'vc-hg)
577
578(defsubst vc-hg--read-u8 ()
579 "Read and advance over an unsigned byte.
580Return a fixnum."
581 (prog1 (char-after)
582 (forward-char)))
583
584(defsubst vc-hg--read-u32-be ()
585 "Read and advance over a big-endian unsigned 32-bit integer.
586Return a fixnum; on overflow, result is undefined."
587 ;; Because elisp bytecode has an instruction for multiply and
588 ;; doesn't have one for lsh, it's somewhat counter-intuitively
589 ;; faster to multiply than to shift.
590 (+ (* (vc-hg--read-u8) (* 256 256 256))
591 (* (vc-hg--read-u8) (* 256 256))
592 (* (vc-hg--read-u8) 256)
593 (identity (vc-hg--read-u8))))
594
595(defun vc-hg--raw-dirstate-search (dirstate fname)
596 (with-temp-buffer
597 (set-buffer-multibyte nil)
598 (insert-file-contents-literally dirstate)
599 (let* ((result nil)
600 (flen (length fname))
601 (case-fold-search nil)
602 (inhibit-changing-match-data t)
603 ;; Find a conservative bound for the loop below by using
604 ;; Boyer-Moore on the raw dirstate without parsing it; we
605 ;; know we can't possibly find fname _after_ the last place
606 ;; it appears, so we can bail out early if we try to parse
607 ;; past it, which especially helps when the file we're
608 ;; trying to find isn't in dirstate at all. There's no way
609 ;; to similarly bound the starting search position, since
610 ;; the file format is such that we need to parse it from
611 ;; the beginning to find record boundaries.
612 (search-limit
613 (progn
614 (goto-char (point-max))
615 (or (search-backward fname (+ (point-min) 40) t)
616 (point-min)))))
617 ;; 40 is just after the header, which contains the working
618 ;; directory parents
619 (goto-char (+ (point-min) 40))
620 ;; Iterate over all dirstate entries; we might run this loop
621 ;; hundreds of thousands of times, so performance is important
622 ;; here
623 (while (< (point) search-limit)
624 ;; 1+4*4 is the length of the dirstate item header, which we
625 ;; spell as a literal for performance, since the elisp
626 ;; compiler lacks constant propagation
627 (forward-char (1+ (* 3 4)))
628 (let ((this-flen (vc-hg--read-u32-be)))
629 (if (and (or (eq this-flen flen)
630 (and (> this-flen flen)
631 (eq (char-after (+ (point) flen)) 0)))
632 (search-forward fname (+ (point) flen) t))
633 (progn
634 (backward-char (+ flen (1+ (* 4 4))))
635 (setf result
636 (list (vc-hg--read-u8) ; status
637 (vc-hg--read-u32-be) ; mode
638 (vc-hg--read-u32-be) ; size (of file)
639 (vc-hg--read-u32-be) ; mtime
640 ))
641 (goto-char (point-max)))
642 (forward-char this-flen))))
643 result)))
644
645(define-error 'vc-hg-unsupported-syntax "unsupported hgignore syntax")
646
647(defconst vc-hg--pcre-c-escapes
648 '((?a . ?\a)
649 (?b . ?\b)
650 (?f . ?\f)
651 (?n . ?\n)
652 (?r . ?\r)
653 (?t . ?\t)
654 (?n . ?\n)
655 (?r . ?\r)
656 (?t . ?\t)
657 (?v . ?\v)))
658
659(defconst vc-hg--pcre-metacharacters
660 '(?. ?^ ?$ ?* ?+ ?? ?{ ?\\ ?\[ ?\| ?\())
661
662(defconst vc-hg--elisp-metacharacters
663 '(?. ?* ?+ ?? ?\[ ?$ ?\\))
664
665(defun vc-hg--escape-for-pcre (c)
666 (if (memq c vc-hg--pcre-metacharacters)
667 (string ?\\ c)
668 c))
669
670(defun vc-hg--parts-to-string (parts)
671 "Build a string from list PARTS. Each element is a character or string."
672 (let ((parts2 nil))
673 (while parts
674 (let* ((partcell (prog1 parts (setf parts (cdr parts))))
675 (part (car partcell)))
676 (if (stringp part)
677 (setf parts2 (nconc (append part nil) parts2))
678 (setcdr partcell parts2)
679 (setf parts2 partcell))))
680 (apply #'string parts2)))
681
682(defun vc-hg--pcre-to-elisp-re (pcre prefix)
683 "Transform PCRE, a Mercurial file PCRE, into an elisp RE against PREFIX.
684PREFIX is the directory name of the directory against which these
685patterns are rooted. We understand only a subset of PCRE syntax;
686if we don't understand a construct, we signal
687`vc-hg-unsupported-syntax'."
688 (cl-assert (string-match "^/\\(.*/\\)?$" prefix))
689 (let ((parts nil)
690 (i 0)
691 (anchored nil)
692 (state 'normal)
693 (pcrelen (length pcre)))
694 (while (< i pcrelen)
695 (let ((c (aref pcre i)))
696 (cond ((eq state 'normal)
697 (cond ((string-match
698 (rx (| "}\\?" (: "(?" (not (any ":")))))
699 pcre i)
700 (signal 'vc-hg-unsupported-syntax (list pcre)))
701 ((eq c ?\\)
702 (setf state 'backslash))
703 ((eq c ?\[)
704 (setf state 'charclass-enter)
705 (push c parts))
706 ((eq c ?^)
707 (if (eq i 0) (setf anchored t)
708 (signal 'vc-hg-unsupported-syntax (list pcre))))
709 ((eq c ?$)
710 ;; Patterns can also match directories exactly,
711 ;; ignoring everything under a matched directory
712 (push "\\(?:$\\|/\\)" parts))
713 ((memq c '(?| ?\( ?\)))
714 (push ?\\ parts)
715 (push c parts))
716 (t (push c parts))))
717 ((eq state 'backslash)
718 (cond ((memq c '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
719 ?A ?b ?B ?d ?D ?s ?S ?w ?W ?Z ?x))
720 (signal 'vc-hg-unsupported-syntax (list pcre)))
721 ((memq c vc-hg--elisp-metacharacters)
722 (push ?\\ parts)
723 (push c parts))
724 (t (push (or (cdr (assq c vc-hg--pcre-c-escapes)) c) parts)))
725 (setf state 'normal))
726 ((eq state 'charclass-enter)
727 (push c parts)
728 (setf state
729 (if (eq c ?\\)
730 'charclass
731 'charclass-backslash)))
732 ((eq state 'charclass-backslash)
733 (if (memq c '(?0 ?x))
734 (signal 'vc-hg-unsupported-syntax (list pcre)))
735 (push (or (cdr (assq c vc-hg--pcre-c-escapes)) c) parts)
736 (setf state 'charclass))
737 ((eq state 'charclass)
738 (push c parts)
739 (cond ((eq c ?\\) (setf state 'charclass-backslash))
740 ((eq c ?\]) (setf state 'normal))))
741 (t (error "invalid state")))
742 (setf i (1+ i))))
743 (unless (eq state 'normal)
744 (signal 'vc-hg-unsupported-syntax (list pcre)))
745 (concat
746 "^"
747 prefix
748 (if anchored "" "\\(?:.*/\\)?")
749 (vc-hg--parts-to-string parts))))
750
751(defun vc-hg--glob-to-pcre (glob)
752 "Transform a glob pattern into a Mercurial file pattern regex."
753 (let ((parts nil) (i 0) (n (length glob)) (group 0) c)
754 (cl-macrolet ((peek () '(and (< i n) (aref glob i))))
755 (while (< i n)
756 (setf c (aref glob i))
757 (cl-incf i)
758 (cond ((not (memq c '(?* ?? ?\[ ?\{ ?\} ?, ?\\)))
759 (push (vc-hg--escape-for-pcre c) parts))
760 ((eq c ?*)
761 (cond ((eq (peek) ?*)
762 (cl-incf i)
763 (cond ((eq (peek) ?/)
764 (cl-incf i)
765 (push "(?:.*/)?" parts))
766 (t
767 (push ".*" parts))))
768 (t (push "[^/]*" parts))))
769 ((eq c ??)
770 (push ?. parts))
771 ((eq c ?\[)
772 (let ((j i))
773 (when (and (< j n) (memq (aref glob j) '(?! ?\])))
774 (cl-incf j))
775 (while (and (< j n) (not (eq (aref glob j) ?\])))
776 (cl-incf j))
777 (cond ((>= j n)
778 (push "\\[" parts))
779 (t
780 (let ((x (substring glob i j)))
781 (setf x (replace-regexp-in-string
782 "\\\\" "\\\\" x t t))
783 (setf i (1+ j))
784 (cond ((eq (aref x 0) ?!)
785 (setf (aref x 0) ?^))
786 ((eq (aref x 0) ?^)
787 (setf x (concat "\\" x))))
788 (push ?\[ parts)
789 (push x parts)
790 (push ?\] parts))))))
791 ((eq c ?\{)
792 (cl-incf group)
793 (push "(?:" parts))
794 ((eq c ?\})
795 (push ?\) parts)
796 (cl-decf group))
797 ((and (eq c ?,) (> group 0))
798 (push ?| parts))
799 ((eq c ?\\)
800 (if (eq i n)
801 (push "\\\\" parts)
802 (cl-incf i)
803 (push ?\\ parts)
804 (push c parts)))
805 (t
806 (push (vc-hg--escape-for-pcre c) parts)))))
807 (concat (vc-hg--parts-to-string parts) "$")))
808
809(defvar vc-hg--hgignore-patterns)
810(defvar vc-hg--hgignore-filenames)
811
812(defun vc-hg--hgignore-add-pcre (pcre prefix)
813 (push (vc-hg--pcre-to-elisp-re pcre prefix) vc-hg--hgignore-patterns))
814
815(defun vc-hg--hgignore-add-glob (glob prefix)
816 (push (vc-hg--pcre-to-elisp-re (vc-hg--glob-to-pcre glob) prefix)
817 vc-hg--hgignore-patterns))
818
819(defun vc-hg--hgignore-add-path (path prefix)
820 (let ((parts nil))
821 (dotimes (i (length path))
822 (push (vc-hg--escape-for-pcre (aref path i)) parts))
823 (vc-hg--hgignore-add-pcre
824 (concat "^" (vc-hg--parts-to-string parts) "$")
825 prefix)))
826
827(defun vc-hg--slurp-hgignore-1 (hgignore prefix)
828 (let ((default-syntax 'vc-hg--hgignore-add-glob))
829 (with-temp-buffer
830 (let ((attr (file-attributes hgignore)))
831 (when attr (insert-file-contents hgignore))
832 (push (list hgignore (nth 5 attr) (nth 7 attr))
833 vc-hg--hgignore-filenames))
834 (while (not (eobp))
835 ;; This list of pattern-file commands isn't complete, but it
836 ;; should cover the common cases. Remember that we fall back
837 ;; to regular hg commands if we see something we don't like.
838 (save-restriction
839 (narrow-to-region (point) (point-at-eol))
840 (cond ((looking-at "[ \t]*\\(?:#.*\\)?$"))
841 ((looking-at "syntax:[ \t]*re[ \t]*$")
842 (setf default-syntax 'vc-hg--hgignore-add-pcre))
843 ((looking-at "syntax:[ \t]*glob[ \t]*$")
844 (setf default-syntax 'vc-hg--hgignore-add-glob))
845 ((looking-at "path:\\(.+?\\)[ \t]*$")
846 (vc-hg--hgignore-add-path (match-string 1) prefix))
847 ((looking-at "glob:\\(.+?\\)[ \t]*$")
848 (vc-hg--hgignore-add-glob (match-string 1) prefix))
849 ((looking-at "re:\\(.+?\\)[ \t]*$")
850 (vc-hg--hgignore-add-pcre (match-string 1) prefix))
851 ((looking-at "\\(sub\\)?include:\\(.+?\\)[ \t]*$")
852 (let* ((sub (equal (match-string 1) "sub"))
853 (arg (match-string 2))
854 (included-file
855 (if (string-match "^/" arg) arg
856 (concat (file-name-directory hgignore) arg))))
857 (vc-hg--slurp-hgignore-1
858 included-file
859 (if sub (file-name-directory included-file) prefix))))
860 ((looking-at "[a-zA-Z0-9_]*:")
861 (signal 'vc-hg-unsupported-syntax (list (match-string 0))))
862 ((looking-at ".*$")
863 (funcall default-syntax (match-string 0) prefix))))
864 (forward-line 1)))))
865
866(cl-defstruct (vc-hg--ignore-patterns
867 (:copier nil)
868 (:constructor vc-hg--ignore-patterns-make))
869 repo
870 ignore-patterns
871 file-sources)
872
873(defun vc-hg--slurp-hgignore (repo)
874 "Read hg ignore patterns from REPO.
875REPO must be the directory name of an hg repository."
876 (cl-assert (string-match "^/\\(.*/\\)?$" repo))
877 (let* ((hgignore (concat repo ".hgignore"))
878 (vc-hg--hgignore-patterns nil)
879 (vc-hg--hgignore-filenames nil))
880 (vc-hg--slurp-hgignore-1 hgignore repo)
881 (vc-hg--ignore-patterns-make
882 :repo repo
883 :ignore-patterns (nreverse vc-hg--hgignore-patterns)
884 :file-sources (nreverse vc-hg--hgignore-filenames))))
885
886(defun vc-hg--ignore-patterns-valid-p (hgip)
887 "Return whether the cached ignore patterns in HGIP are still valid"
888 (let ((valid t)
889 (file-sources (vc-hg--ignore-patterns-file-sources hgip)))
890 (while (and file-sources valid)
891 (let* ((fs (pop file-sources))
892 (saved-mtime (nth 1 fs))
893 (saved-size (nth 2 fs))
894 (attr (file-attributes (nth 0 fs)))
895 (current-mtime (nth 5 attr))
896 (current-size (nth 7 attr)))
897 (unless (and (equal saved-mtime current-mtime)
898 (equal saved-size current-size))
899 (setf valid nil))))
900 valid))
901
902(defun vc-hg--ignore-patterns-ignored-p (hgip filename)
903 "Test whether the ignore pattern set HGIP says to ignore FILENAME.
904FILENAME must be the file's true absolute name."
905 (let ((patterns (vc-hg--ignore-patterns-ignore-patterns hgip))
906 (inhibit-changing-match-data t)
907 (ignored nil))
908 (while (and patterns (not ignored))
909 (setf ignored (string-match (pop patterns) filename)))
910 ignored))
911
912(defun vc-hg--time-to-fixnum (ts)
913 (+ (* 65536 (car ts)) (cadr ts)))
914
915(defvar vc-hg--cached-ignore-patterns nil
916 "Cached pre-parsed hg ignore patterns.")
917
918(defun vc-hg--file-ignored-p (repo repo-relative-filename)
919 (let ((hgip vc-hg--cached-ignore-patterns))
920 (unless (and hgip
921 (equal repo (vc-hg--ignore-patterns-repo hgip))
922 (vc-hg--ignore-patterns-valid-p hgip))
923 (setf vc-hg--cached-ignore-patterns nil)
924 (setf hgip (vc-hg--slurp-hgignore repo))
925 (setf vc-hg--cached-ignore-patterns hgip))
926 (vc-hg--ignore-patterns-ignored-p
927 hgip
928 (concat repo repo-relative-filename))))
929
930(defun vc-hg--read-repo-requirements (repo)
931 (cl-assert (string-match "^/\\(.*/\\)?$" repo))
932 (let* ((requires-filename (concat repo ".hg/requires")))
933 (and (file-exists-p requires-filename)
934 (with-temp-buffer
935 (set-buffer-multibyte nil)
936 (insert-file-contents-literally requires-filename)
937 (split-string (buffer-substring-no-properties
938 (point-min) (point-max)))))))
939
940(defconst vc-hg-supported-requirements
941 '("dotencode"
942 "fncache"
943 "generaldelta"
944 "lz4revlog"
945 "remotefilelog"
946 "revlogv1"
947 "store")
948 "List of Mercurial repository requirements we understand; if a
949repository requires features not present in this list, we avoid
950attempting to parse Mercurial data structures.")
951
952(defun vc-hg--requirements-understood-p (repo)
953 "Check that we understand the format of the given repository.
954REPO is the directory name of a Mercurial repository."
955 (null (cl-set-difference (vc-hg--read-repo-requirements repo)
956 vc-hg-supported-requirements
957 :test #'equal)))
958
959(defvar vc-hg--dirstate-scan-cache nil
960 "Cache of the last result of `vc-hg--raw-dirstate-search'.
961Avoids the need to repeatedly scan dirstate on repeated calls to
962`vc-hg-state', as we see during registration queries.")
963
964(defun vc-hg--cached-dirstate-search (dirstate dirstate-attr ascii-fname)
965 (let* ((mtime (nth 5 dirstate-attr))
966 (size (nth 7 dirstate-attr))
967 (cache vc-hg--dirstate-scan-cache)
968 )
969 (if (and cache
970 (equal dirstate (pop cache))
971 (equal mtime (pop cache))
972 (equal size (pop cache))
973 (equal ascii-fname (pop cache)))
974 (pop cache)
975 (let ((result (vc-hg--raw-dirstate-search dirstate ascii-fname)))
976 (setf vc-hg--dirstate-scan-cache
977 (list dirstate mtime size ascii-fname result))
978 result))))
979
980(defun vc-hg-state-fast (filename)
981 "Like `vc-hg-state', but parse internal data structures directly.
982Returns one of the usual `vc-state' enumeration values or
983`unsupported' if we need to take the slow path and run the
984hg binary."
985 (let* (truename
986 repo
987 dirstate
988 dirstate-attr
989 repo-relative-filename
990 ascii-fname)
991 (if (or
992 ;; Explicit user disable
993 (not vc-hg-parse-hg-data-structures)
994 ;; It'll probably be faster to run hg remotely
995 (file-remote-p filename)
996 (progn
997 (setf truename (file-truename filename))
998 (file-remote-p truename))
999 (not (setf repo (vc-hg-root truename)))
1000 ;; dirstate must exist
1001 (not (progn
1002 (setf repo (expand-file-name repo))
1003 (cl-assert (string-match "^/\\(.*/\\)?$" repo))
1004 (setf dirstate (concat repo ".hg/dirstate"))
1005 (setf dirstate-attr (file-attributes dirstate))))
1006 ;; Repository must be in an understood format
1007 (not (vc-hg--requirements-understood-p repo))
1008 ;; Dirstate too small to be valid
1009 (< (nth 7 dirstate-attr) 40)
1010 ;; We want to store 32-bit unsigned values in fixnums
1011 (< most-positive-fixnum 4294967295)
1012 (progn
1013 (setf repo-relative-filename
1014 (file-relative-name truename repo))
1015 (setf ascii-fname
1016 (string-as-unibyte
1017 (let (last-coding-system-used)
1018 (encode-coding-string
1019 repo-relative-filename
1020 'us-ascii t))))
1021 ;; We only try dealing with ASCII filenames
1022 (not (equal ascii-fname repo-relative-filename))))
1023 'unsupported
1024 (let* ((dirstate-entry
1025 (vc-hg--cached-dirstate-search
1026 dirstate dirstate-attr ascii-fname))
1027 (state (car dirstate-entry))
1028 (stat (file-attributes
1029 (concat repo repo-relative-filename))))
1030 (cond ((eq state ?r) 'removed)
1031 ((and (not state) stat)
1032 (condition-case nil
1033 (if (vc-hg--file-ignored-p repo repo-relative-filename)
1034 'ignored
1035 'unregistered)
1036 (vc-hg-unsupported-syntax 'unsupported)))
1037 ((and state (not stat)) 'missing)
1038 ((eq state ?n)
1039 (let ((vc-hg-size (nth 2 dirstate-entry))
1040 (vc-hg-mtime (nth 3 dirstate-entry))
1041 (fs-size (nth 7 stat))
1042 (fs-mtime (vc-hg--time-to-fixnum (nth 5 stat))))
1043 (if (and (eql vc-hg-size fs-size) (eql vc-hg-mtime fs-mtime))
1044 'up-to-date
1045 'edited)))
1046 ((eq state ?a) 'added)
1047 (state 'unsupported))))))
1048
438;;; Miscellaneous 1049;;; Miscellaneous
439 1050
440(defun vc-hg-previous-revision (_file rev) 1051(defun vc-hg-previous-revision (_file rev)
diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el
index 2be46c5fff4..0c1718e94cb 100644
--- a/lisp/vc/vc-hooks.el
+++ b/lisp/vc/vc-hooks.el
@@ -807,15 +807,15 @@ In the latter case, VC mode is deactivated for this buffer."
807 (add-hook 'vc-mode-line-hook 'vc-mode-line nil t) 807 (add-hook 'vc-mode-line-hook 'vc-mode-line nil t)
808 (let (backend) 808 (let (backend)
809 (cond 809 (cond
810 ((setq backend (with-demoted-errors (vc-backend buffer-file-name))) 810 ((setq backend (with-demoted-errors (vc-backend buffer-file-name)))
811 ;; Let the backend setup any buffer-local things he needs.
812 (vc-call-backend backend 'find-file-hook)
811 ;; Compute the state and put it in the mode line. 813 ;; Compute the state and put it in the mode line.
812 (vc-mode-line buffer-file-name backend) 814 (vc-mode-line buffer-file-name backend)
813 (unless vc-make-backup-files 815 (unless vc-make-backup-files
814 ;; Use this variable, not make-backup-files, 816 ;; Use this variable, not make-backup-files,
815 ;; because this is for things that depend on the file name. 817 ;; because this is for things that depend on the file name.
816 (set (make-local-variable 'backup-inhibited) t)) 818 (set (make-local-variable 'backup-inhibited) t)))
817 ;; Let the backend setup any buffer-local things he needs.
818 (vc-call-backend backend 'find-file-hook))
819 ((let* ((truename (and buffer-file-truename 819 ((let* ((truename (and buffer-file-truename
820 (expand-file-name buffer-file-truename))) 820 (expand-file-name buffer-file-truename)))
821 (link-type (and truename 821 (link-type (and truename