Subject: 25.4.1: vc-hg-ignore implementation is missing

From: Wolfgang Scherer <[email-masked]>
Date: Mon, 26 Aug 2019 02:21:34 +0200

Using `vc-ignore' in a *vc-dir* buffer for mercurial does not provide a correct entry in `.hgignore.'.

The function `vc-hg-ignore'' below autodetects the correct syntax and adds a correctly quoted entry to `.hgignore'.

Feel free to incorporate it in `vc-hg.el'.

(defvar vc-hg--py-regexp-special-chars
  (mapcar
   (function
    (lambda (_c)
      (cons _c (concat "\\" (char-to-string _c)))))
   (append "()[]{}?*+-|^$\\.&~# \t\n\r\v\f" nil))
  "Characters that have special meaning in Python regular expressions.")

(defun vc-hg--py-regexp-quote (string)
  "Return a Python regexp string which matches exactly STRING and nothing else.
Ported from Python v3.7"
  (mapconcat
   (function
    (lambda (_c)
      (or (cdr (assq _c vc-hg--py-regexp-special-chars))
          (char-to-string _c))))
   string ""))

(defvar vc-hg-ignore-detect-wildcard "[*^$]"
  "Regular expresssion to detect wildcards in an ignored file specification.")

(defun vc-hg-ignore (file &optional directory remove)
  "Ignore FILE of DIRECTORY (default is `default-directory').

FILE is a file wildcard, relative to the root directory of DIRECTORY.

If FILE matches the regular expression
`vc-hg-ignore-detect-wildcard', it is appended to .hgignore as
is. Otherwise, FILE is escaped/expanded according to the active
syntax in .hgignore. If the syntax is `regexp', FILE is quoted as
anchored literal Python regexp and if FILE is a directory, the
trailing `$' is omitted.  Otherwise, if the syntax is `glob',
FILE is used unquoted and if FILE is a directory, a `*' is
appended.

When called from Lisp code, if DIRECTORY is non-nil, the
repository to use will be deduced by DIRECTORY; if REMOVE is
non-nil, remove FILE from ignored files."
  (let ((ignore (vc-hg-find-ignore-file (or directory default-directory)))
        (pattern file)
        root-dir file-path syntax)
    (unless (string-match vc-hg-ignore-detect-wildcard pattern)
      (setq root-dir (file-name-directory ignore))
      (setq file-path (expand-file-name file directory))
      (setq pattern (substring file-path (length root-dir)))
      (save-match-data
        (with-current-buffer (find-file-noselect ignore)
          (goto-char (point-max))
          (setq syntax
                (if (re-search-backward "^ *syntax: *\\(regexp\\|glob\\)$" nil t)
                    (match-string 1)
                  "regexp")))
        (setq pattern
              (if (string= syntax "regexp")
                  (concat "^" (vc-hg--py-regexp-quote pattern)
                          (and (not (file-directory-p file-path)) "$"))
                (concat pattern (and (file-directory-p file-path) "*"))))))
    (if remove
        (vc--remove-regexp pattern ignore)
      (vc--add-line pattern ignore))))