From 08d888a903796ef65fa0fe733ecfd71e3b367c26 Mon Sep 17 00:00:00 2001 From: Wolfgang Scherer Date: Sun, 9 Feb 2020 21:50:34 +0100 Subject: [PATCH] vc ignore feature repair MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete implementation of ignore feature with proper filename escaping and anchoring for all applicable supported backends: CVS, SVN, SRC, Bzr, Git, Hg, Mtn. * lisp/vc/vc.el: (vc-ignore-param-none, vc-ignore-param-glob) (vc-ignore-param-glob-anchored, vc-ignore-param-regexp) (vc-default-ignore-param, vc-glob-escape) (vc--py-regexp-special-chars, vc-py-regexp-quote): new vc ignore parameters (vc-ignore-pattern, vc-ignore-file): new user interface commands (vc-ignore-fileset): new frontend command (vc-ignore): thin wrapper around ‘vc-default-ignore’ (vc-default-ignore): thin wrapper around ‘vc-default-get-ignore-file-and-pattern’ and ‘vc-default-modify-ignores’ (vc-default-get-ignore-file-and-pattern): workhorse for preparing pattern and file ignore parameters (vc-default-modify-ignores): default ignore ignore file manipulator (vc-file-name-directory, vc-file-relative-name): (vc-no-final-slash, vc-has-final-slash): utilities * lisp/vc/vc-bzr.el: (vc-bzr-ignore-param-regexp) (vc-bzr-ignore-param): new vc ignore parameters * lisp/vc/vc-cvs.el: (vc-cvs-find-ignore-file) (vc-cvs-ignore-param-glob, vc-cvs-ignore-param) (vc-cvs-glob-escape): new vc ignore parameters (vc-cvs-ignore, vc-cvs-append-to-ignore): removed * lisp/vc/vc-git.el: (vc-git-ignore-param): new vc ignore parameters * lisp/vc/vc-hg.el: (vc-hg-ignore-param-regexp) (vc-hg-ignore-param-glob, vc-hg-ignore-param): new vc ignore parameters * lisp/vc/vc-mtn.el: (vc-mtn-ignore-param-regexp) (vc-mtn-ignore-param): new vc ignore parameters * lisp/vc/vc-src.el: (vc-src-find-ignore-file, vc-src-glob-escape) (vc-src-ignore-param-glob, vc-src-ignore-param): new vc ignore parameters * lisp/vc/vc-svn.el: (vc-svn-find-ignore-file) (vc-svn-ignore-param-glob, vc-svn-ignore-param) (vc-svn-modify-ignores): new vc ignore parameters, (vc-svn-ignore): removed * lisp/vc/vc-dir.el: (vc-dir-mode-map) new binding "F" => 'vc-ignore-file new binding "G" => 'vc-ignore-pattern * lisp/vc/vc-hooks.el: (vc-prefix-map) new binding "F" => 'vc-ignore-file new binding "G" => 'vc-ignore-pattern (vc-menu-map): new menu item "Ignore Pattern..." --- lisp/vc/vc-bzr.el | 8 ++ lisp/vc/vc-cvs.el | 38 +++---- lisp/vc/vc-dir.el | 3 +- lisp/vc/vc-git.el | 4 + lisp/vc/vc-hg.el | 23 +++++ lisp/vc/vc-hooks.el | 8 +- lisp/vc/vc-mtn.el | 8 ++ lisp/vc/vc-src.el | 26 +++++ lisp/vc/vc-svn.el | 26 +++-- lisp/vc/vc.el | 278 +++++++++++++++++++++++++++++++++++++++++++++------- 10 files changed, 352 insertions(+), 70 deletions(-) diff --git a/lisp/vc/vc-bzr.el b/lisp/vc/vc-bzr.el index e5d307e..1546aba 100644 --- a/lisp/vc/vc-bzr.el +++ b/lisp/vc/vc-bzr.el @@ -683,6 +683,14 @@ or a superior directory.") (expand-file-name ".bzrignore" (vc-bzr-root file))) +(defvar vc-bzr-ignore-param-regexp + '(:escape: vc-py-regexp-quote :anchor: "RE:^" :trailer: "$" :dir-trailer: "/.*") + "Ignore parameters for Bzr anchored regular expressions.") + +(defun vc-bzr-ignore-param (&optional _ignore-file) + "Appropriate Bzr ignore parameters for IGNORE-FILE." + vc-bzr-ignore-param-regexp) + (defun vc-bzr-checkout (_file &optional rev) (if rev (error "Operation not supported") ;; Else, there's nothing to do. diff --git a/lisp/vc/vc-cvs.el b/lisp/vc/vc-cvs.el index 16566a8..ae60c6c 100644 --- a/lisp/vc/vc-cvs.el +++ b/lisp/vc/vc-cvs.el @@ -1220,29 +1220,21 @@ is non-nil." "Return the administrative directory of FILE." (vc-find-root file "CVS")) -(defun vc-cvs-ignore (file &optional _directory _remove) - "Ignore FILE under CVS." - (vc-cvs-append-to-ignore (file-name-directory file) file)) - -(defun vc-cvs-append-to-ignore (dir str &optional old-dir) - "In DIR, add STR to the .cvsignore file. -If OLD-DIR is non-nil, then this is a directory that we don't want -to hear about anymore." - (with-current-buffer - (find-file-noselect (expand-file-name ".cvsignore" dir)) - (when (ignore-errors - (and buffer-read-only - (eq 'CVS (vc-backend buffer-file-name)) - (not (vc-editable-p buffer-file-name)))) - ;; CVSREAD=on special case - (vc-checkout buffer-file-name t)) - (goto-char (point-max)) - (unless (bolp) (insert "\n")) - (insert str (if old-dir "/\n" "\n")) - ;; FIXME this is a pcvs variable. - (if (bound-and-true-p cvs-sort-ignore-file) - (sort-lines nil (point-min) (point-max))) - (save-buffer))) +(defun vc-cvs-find-ignore-file (file) + "Return the ignore file for FILE." + (expand-file-name ".cvsignore" (if file (file-name-directory file)))) + +(defvar vc-cvs-ignore-param-glob + '(:escape: vc-cvs-glob-escape :anchor: "" :trailer: "" :dir-trailer: "/") + "Ignore parameters for CVS partially anchored glob wildcards.") + +(defun vc-cvs-ignore-param (&optional _ignore-file) + "Appropriate CVS ignore parameters for IGNORE-FILE." + vc-cvs-ignore-param-glob) + +(defun vc-cvs-glob-escape (string) + "Escape special glob characters and spaces in STRING." + (replace-regexp-in-string " " "?" (vc-glob-escape string) t)) (provide 'vc-cvs) diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index 0c29352..0a1deeb 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -302,7 +302,8 @@ See `run-hooks'." (define-key map "Q" 'vc-dir-query-replace-regexp) (define-key map (kbd "M-s a C-s") 'vc-dir-isearch) (define-key map (kbd "M-s a M-C-s") 'vc-dir-isearch-regexp) - (define-key map "G" 'vc-dir-ignore) + (define-key map "F" 'vc-ignore-file) + (define-key map "G" 'vc-ignore-pattern) (let ((branch-map (make-sparse-keymap))) (define-key map "B" branch-map) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 2caa287..5292f21 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -976,6 +976,10 @@ It is based on `log-edit-mode', and has Git-specific extensions.") (expand-file-name ".gitignore" (vc-git-root file))) +(defun vc-git-ignore-param (&optional _ignore-file) + "Appropriate Git ignore parameters for IGNORE-FILE." + vc-ignore-param-glob-anchored) + (defun vc-git-checkout (file &optional rev) (vc-git-command nil 0 file "checkout" (or rev "HEAD"))) diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el index eac9a6f..3d22ae2 100644 --- a/lisp/vc/vc-hg.el +++ b/lisp/vc/vc-hg.el @@ -1212,6 +1212,29 @@ REV is ignored." (expand-file-name ".hgignore" (vc-hg-root file))) +(defvar vc-hg-ignore-param-regexp + '(:escape: vc-py-regexp-quote :anchor: "^" :trailer: "$" :dir-trailer: "/") + "Ignore parameters for Hg anchored regular expressions.") + +(defvar vc-hg-ignore-param-glob + '(:escape: vc-glob-escape :anchor: "" :trailer: "" :dir-trailer: "/*") + "Ignore parameters for Hg anchored regular expressions.") + +(defun vc-hg-ignore-param (&optional ignore-file) + "Appropriate Hg ignore parameters for IGNORE-FILE." + (let ((syntax "regexp")) + (if (not ignore-file) + (setq ignore-file (vc-hg-find-ignore-file default-directory))) + (if (file-exists-p ignore-file) + (with-current-buffer (find-file-noselect ignore-file) + (save-match-data + (goto-char (point-max)) + (if (re-search-backward "^ *syntax: *\\(regexp\\|glob\\)$" nil t) + (setq syntax (match-string 1)))))) + (if (string= syntax "regexp") + vc-hg-ignore-param-regexp + vc-hg-ignore-param-glob))) + ;; Modeled after the similar function in vc-bzr.el (defun vc-hg-checkout (file &optional rev) "Retrieve a revision of FILE. diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index 345a28d..3492dc1 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -883,7 +883,8 @@ In the latter case, VC mode is deactivated for this buffer." (define-key map "b" 'vc-switch-backend) (define-key map "d" 'vc-dir) (define-key map "g" 'vc-annotate) - (define-key map "G" 'vc-ignore) + (define-key map "F" 'vc-ignore-file) + (define-key map "G" 'vc-ignore-pattern) (define-key map "h" 'vc-region-history) (define-key map "i" 'vc-register) (define-key map "l" 'vc-print-log) @@ -970,8 +971,11 @@ In the latter case, VC mode is deactivated for this buffer." '(menu-item "Register" vc-register :help "Register file set into a version control system")) (bindings--define-key map [vc-ignore] - '(menu-item "Ignore File..." vc-ignore + '(menu-item "Ignore File..." vc-ignore-file :help "Ignore a file under current version control system")) + (bindings--define-key vc-menu-map [vc-ignore-pattern] + '(menu-item "Ignore Pattern..." vc-ignore-pattern + :help "Ignore a pattern under current version control system")) (bindings--define-key map [vc-dir] '(menu-item "VC Dir" vc-dir :help "Show the VC status of files in a directory")) diff --git a/lisp/vc/vc-mtn.el b/lisp/vc/vc-mtn.el index 092d8b5..af42d3f 100644 --- a/lisp/vc/vc-mtn.el +++ b/lisp/vc/vc-mtn.el @@ -106,6 +106,14 @@ switches." "Return the mtn ignore file that controls FILE." (expand-file-name ".mtnignore" (vc-mtn-root file))) +(defvar vc-mtn-ignore-param-regexp + '(:escape: vc-py-regexp-quote :anchor: "^" :trailer: "$" :dir-trailer: "/") + "Ignore parameters for Mtn anchored regular expressions.") + +(defun vc-mtn-ignore-param (&optional _ignore-file) + "Appropriate Mtn ignore parameters for IGNORE-FILE." + vc-mtn-ignore-param-regexp) + (defun vc-mtn-registered (file) (let ((root (vc-mtn-root file))) (when root diff --git a/lisp/vc/vc-src.el b/lisp/vc/vc-src.el index db127ee..cd9f032 100644 --- a/lisp/vc/vc-src.el +++ b/lisp/vc/vc-src.el @@ -310,6 +310,32 @@ If LIMIT is non-nil, show no more than this many entries." "Rename file from OLD to NEW using `src mv'." (vc-src-command nil 0 new "mv" old)) +(defun vc-src-find-ignore-file (file) + "Return the ignore file for FILE." + (expand-file-name ".srcignore" (if file (file-name-directory file)))) + +(defun vc-src-glob-escape (string) + "Escape special glob characters in STRING." + (save-match-data + (if (string-match "[?*[]" string) + (mapconcat (lambda (c) + (pcase c + (?? "[?]") + (?* "[*]") + (?\[ "[[]") + (_ (char-to-string c)))) + string "") + string))) +;; (vc-src-glob-escape "full[glo]?\\b*") + +(defvar vc-src-ignore-param-glob + '(:escape: vc-src-glob-escape :anchor: "" :trailer: "" :dir-trailer: "") + "Ignore parameters for SRC unanchored glob wildcards.") + +(defun vc-src-ignore-param (&optional _ignore-file) + "Appropriate SRC ignore parameters for IGNORE-FILE." + vc-src-ignore-param-glob) + (provide 'vc-src) ;;; vc-src.el ends here diff --git a/lisp/vc/vc-svn.el b/lisp/vc/vc-svn.el index d039bf3..9d35b75 100644 --- a/lisp/vc/vc-svn.el +++ b/lisp/vc/vc-svn.el @@ -352,17 +352,25 @@ to the SVN command." (concat "-r" rev)) (vc-switches 'SVN 'checkout)))) -(defun vc-svn-ignore (file &optional directory remove) - "Ignore FILE under Subversion. -FILE is a wildcard specification, either relative to -DIRECTORY or absolute." - (let* ((path (directory-file-name (expand-file-name file directory))) - (directory (file-name-directory path)) - (file (file-name-nondirectory path)) +(defun vc-svn-find-ignore-file (file) + "Return the virtual ignore file for FILE." + (expand-file-name ".svnignore" (if file (file-name-directory file)))) + +(defvar vc-svn-ignore-param-glob + '(:escape: vc-glob-escape :anchor: "" :trailer: "" :dir-trailer: "") + "Ignore parameters for SVN unanchored glob wildcards.") + +(defun vc-svn-ignore-param (&optional _ignore-file) + "Appropriate SVN ignore parameters for IGNORE-FILE." + vc-svn-ignore-param-glob) + +(defun vc-svn-modify-ignores (pattern ignore-file remove) + ;; implements ‘vc-default-modify-ignores’ for SVN + (let* ((directory (file-name-directory ignore-file)) (ignores (vc-svn-ignore-completion-table directory)) (ignores (if remove - (delete file ignores) - (push file ignores)))) + (delete pattern ignores) + (push pattern ignores)))) (vc-svn-command nil 0 nil nil "propset" "svn:ignore" (mapconcat #'identity ignores "\n") directory))) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index ec252b7..d3e5537 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -1392,54 +1392,262 @@ first backend that could register the file is used." (let ((vc-handled-backends (list backend))) (call-interactively 'vc-register))) -(defun vc-ignore (file &optional directory remove) - "Ignore FILE under the VCS of DIRECTORY. - -Normally, FILE is a wildcard specification that matches the files -to be ignored. When REMOVE is non-nil, remove FILE from the list -of ignored files. +(defvar vc-ignore-param-none + '(:escape: identity :anchor: "" :trailer: "" :dir-trailer: "") + "Property list of ignore parameters for plain strings. + +All properties are optional. + +Property :escape: is a function that takes a pattern string as parameter +and returns an escaped pattern (default is ‘identity’). + +Property :anchor: is a string that is prepended to the ignore +pattern (default is an empty string). + +Property :trailer: is a string that is appended to non-directory +ignore patterns (default is an empty string). + +Property :dir-trailer: is a string that is appended to directory +ignore patterns (default is an empty string).") + +(defvar vc-ignore-param-glob + '(:escape: vc-glob-escape :anchor: "" :trailer: "" :dir-trailer: "") + "Ignore parameters for unanchored glob wildcards.") + +(defvar vc-ignore-param-glob-anchored + '(:escape: vc-glob-escape :anchor: "/" :trailer: "" :dir-trailer: "/") + "Ignore parameters for anchored glob wildcards.") + +(defvar vc-ignore-param-regexp + '(:escape: regexp-quote :anchor: "^" :trailer: "$" :dir-trailer: "/") + "Ignore parameters for anchored regular expressions.") + +(defun vc-default-ignore-param (_backend &optional _ignore-file) + "Default ignore parameters for IGNORE-FILE." + vc-ignore-param-glob) + +(defun vc-glob-escape (string) + "Escape special glob characters in STRING." + (save-match-data + (if (string-match "[\\?*[]" string) + (mapconcat (lambda (c) + (pcase c + (?\\ "\\\\") + (?? "\\?") + (?* "\\*") + (?\[ "\\[") + (_ (char-to-string c)))) + string "") + string))) + +(defvar vc--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-py-regexp-quote (string) + "Python regexp to match exactly STRING and nothing else. +Ported from Python v3.7" + (mapconcat + (function + (lambda (c) + (or (cdr (assq c vc--py-regexp-special-chars)) + (char-to-string c)))) + string "")) + +(defun vc-ignore-pattern (pattern &optional directory remove) + "Ignore PATTERN under VCS of DIRECTORY. DIRECTORY defaults to `default-directory' and is used to determine the responsible VC backend. -When called interactively, prompt for a FILE to ignore, unless a -prefix argument is given, in which case prompt for a file FILE to -remove from the list of ignored files." +PATTERN is an expression following the rules of the backend +pattern syntax, matching the files to be ignored. When REMOVE is +non-nil, remove PATTERN from the list of ignored files. + +When called interactively, prompt for a PATTERN to ignore, unless +a prefix argument is given, in which case prompt for a PATTERN to +remove from the list of ignored files offering currently defined +patterns for completion." (interactive (list (if (not current-prefix-arg) - (read-file-name "File to ignore: ") + (read-string "Pattern to ignore: ") (completing-read - "File to remove: " + "Pattern to remove: " (vc-call-backend (or (vc-responsible-backend default-directory) (error "Unknown backend")) 'ignore-completion-table default-directory))) nil current-prefix-arg)) - (let* ((directory (or directory default-directory)) - (backend (or (vc-responsible-backend default-directory) - (error "Unknown backend")))) - (vc-call-backend backend 'ignore file directory remove))) - -(defun vc-default-ignore (backend file &optional directory remove) - "Ignore FILE under the VCS of DIRECTORY (default is `default-directory'). -FILE is a wildcard specification, either relative to -DIRECTORY or absolute. -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. -Argument BACKEND is the backend you are using." - (let ((ignore - (vc-call-backend backend 'find-ignore-file (or directory default-directory))) - file-path root-dir pattern) - (setq file-path (expand-file-name file directory)) - (setq root-dir (file-name-directory ignore)) - (when (not (string= (substring file-path 0 (length root-dir)) root-dir)) - (error "Ignore spec %s is not below project root %s" file-path root-dir)) - (setq pattern (substring file-path (length root-dir))) - (if remove - (vc--remove-regexp (concat "^" (regexp-quote pattern ) "\\(\n\\|$\\)") ignore) - (vc--add-line pattern ignore)))) + (vc-ignore pattern directory remove t)) + +(defun vc-ignore-file (file &optional directory remove) + "Ignore FILE under VCS of DIRECTORY. + +DIRECTORY defaults to `default-directory' and is used to +determine the responsible VC backend. + +If FILE is nil, ‘vc-ignore-fileset’ is called. + +Otherwise, FILE is a file path that must be escaped and anchored. +The directory name of FILE expanded against DIRECTORY is used to +determine the ignore file. The effective pattern consists of the +file path relative to the directory of the ignore file, properly +escaped and anchored by the VC backend. + +The effective pattern is added to the list of ignored files, +unless REMOVE is non-nil, in which case it is removed. + +When called interactively and the mode is neither ‘vc-dir-mode’ +nor ‘dired-mode’, prompt for a FILE to ignore, unless a prefix +argument is given, in which case prompt for a FILE to remove from +the list of ignored files." + (interactive + (list + (unless (or (derived-mode-p 'vc-dir-mode) (derived-mode-p 'dired-mode)) + (read-file-name + (concat "File to " + (if (not current-prefix-arg) "ignore" "remove") ": "))) + nil current-prefix-arg)) + (if file + (vc-ignore file directory remove nil) + (vc-ignore-fileset nil remove))) + +(defun vc-ignore-fileset (&optional vc-fileset remove) + "Ignore file set under a version control system.. + +If VC-FILESET is not given, it is deduced with +‘vc-deduce-fileset’. + +When REMOVE is non-nil (prefix arg, if interactive), remove the +files from the list of ignored files." + (interactive (list nil current-prefix-arg)) + (let* ((fileset-arg (or vc-fileset (vc-deduce-fileset t t))) + (backend (car fileset-arg)) + (files (delq nil (nth 1 fileset-arg)))) + (when files + (message "Ignoring %s... " files) + (mapc + (lambda (file) + (vc-call-backend backend 'ignore file nil remove nil) + (vc-dir-resynch-file file)) + files)) + (when (derived-mode-p 'vc-dir-mode) + (vc-dir-move-to-goal-column)) + (when files (message "Ignoring %s... done" files)))) + +(defun vc-ignore (file-or-pattern &optional directory remove as-is) + "Ignore FILE-OR-PATTERN under VCS of DIRECTORY. + +DIRECTORY defaults to `default-directory' and is used to +determine the responsible VC backend. + +When REMOVE is non-nil, remove FILE-OR-PATTERN from the list of +ignored files. + +If AS-IS is nil, FILE-OR-PATTERN is considered a file path that +must be escaped and anchored. The directory name of +FILE-OR-PATTERN expanded against DIRECTORY is used to determine +the ignore file. The effective pattern consists of the file path +relative to the directory of the ignore file, properly escaped +and anchored by the VC backend. + +If AS-IS is non-nil, FILE-OR-PATTERN is considered a pattern that +should not be modified. DIRECTORY is used to determine the +ignore file." + (setq directory (or directory default-directory)) + (vc-call-backend (or (vc-responsible-backend directory) + (error "Unknown backend")) + 'ignore file-or-pattern directory remove as-is)) + +(defun vc-default-ignore (backend file-or-pattern &optional directory remove as-is) + ;; implements ‘vc-ignore’ generically + (apply #'vc-call-backend backend 'modify-ignores + (vc-call-backend backend 'get-ignore-file-and-pattern + file-or-pattern directory as-is remove))) + +(defun vc-default-get-ignore-file-and-pattern (backend file-or-pattern &optional directory as-is remove) + "Determine ignore file and pattern for BACKEND from FILE-OR-PATTERN. +Implements API of ‘vc-ignore’ for FILE-OR-PATTERN, DIRECTORY and AS-IS. +REMOVE is passed through without evaluation. +Returns (pattern ignore-file remove) suitable for calling +‘vc-default-modify-ignores’." + + (setq directory (or directory default-directory)) + (when (not as-is) + (setq file-or-pattern (expand-file-name file-or-pattern directory)) + ;; apply directory-as-file-name, otherwise, if file-or-pattern was + ;; a sub-repository, find-ignore-file would return the wrong + ;; ignore file: + ;; (vc-cvs-find-ignore-file "/re/po/dir/") => /re/po/dir/.cvsignore + ;; (vc-cvs-find-ignore-file "/re/po/dir") => /re/po/.cvsignore + (setq directory (file-name-directory (vc-no-final-slash file-or-pattern)))) + + (let* ((ignore-file (vc-call-backend backend 'find-ignore-file directory)) + (ignore-dir (file-name-directory ignore-file)) + is-dir ignore-param pattern) + (if as-is + (setq ignore-param vc-ignore-param-none) + (when (not (string= (substring file-or-pattern 0 (length ignore-dir)) + ignore-dir)) + (error "Ignore spec %s is not below project root %s" + file-or-pattern ignore-dir)) + ;; directory may not yet exist + (setq is-dir (or (file-directory-p file-or-pattern) + (vc-has-final-slash file-or-pattern))) + (setq file-or-pattern (vc-no-final-slash + (substring file-or-pattern (length ignore-dir)))) + (setq ignore-param (vc-call-backend backend 'ignore-param ignore-file))) + + (setq pattern + (concat + (plist-get ignore-param :anchor:) + (funcall (or (plist-get ignore-param :escape:) #'identity) + file-or-pattern) + (or (and is-dir (plist-get ignore-param :dir-trailer:)) + (plist-get ignore-param :trailer:)))) + (list pattern ignore-file remove))) + +(defun vc-default-modify-ignores (_backend pattern ignore-file remove) + "Add PATTERN to IGNORE-FILE, if REMOVE is nil.. +Otherwise remove PATTERN from IGNORE-FILE." + (if remove + (vc--remove-regexp + (concat "^" (regexp-quote pattern) "\\(\n\\|$\\)") ignore-file) + (vc--add-line pattern ignore-file))) + +(defun vc-file-name-directory (file &optional dir dir-as-file) + "Get directory name for FILE. +FILE is expanded against DIR. If FILE is a directory and DIR-AS-FILE +is non-nil, its parent directory is returned." + (and file + (let* ((path (expand-file-name file dir))) + (file-name-directory + (if dir-as-file + (vc-no-final-slash path) + path))))) + +(defun vc-file-relative-name (file &optional dir dir-is-empty) + "Get relative file name for FILE against DIR. +If FILE is a directory and DIR-IS-EMPTY is non-nil, nil is returned. +Otherwise, if FILE is a directory, the final slash is removed." + (and (not (and dir-is-empty (file-directory-p file))) + (vc-no-final-slash (file-relative-name file dir)))) + +(defun vc-no-final-slash (s) + "Remove optional final slash from string S." + ;; based on ‘ido-no-final-slash’ + (let ((l (vc-has-final-slash s))) + (if l (substring s 0 l) s))) + +(defun vc-has-final-slash (s) + ;"Return index of final slash in string S or nil." + (let ((l (1- (length s)))) + (and (> l 0) (eq (aref s l) ?/) l))) (defun vc-default-ignore-completion-table (backend file) "Return the list of ignored files under BACKEND." -- 2.7.4