magit-gitignore.el (7606B)
1 ;;; magit-gitignore.el --- Intentionally untracked files -*- lexical-binding:t -*- 2 3 ;; Copyright (C) 2008-2024 The Magit Project Contributors 4 5 ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> 6 ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> 7 8 ;; SPDX-License-Identifier: GPL-3.0-or-later 9 10 ;; Magit is free software: you can redistribute it and/or modify it 11 ;; under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 14 ;; 15 ;; Magit is distributed in the hope that it will be useful, but WITHOUT 16 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 17 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 18 ;; License for more details. 19 ;; 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with Magit. If not, see <https://www.gnu.org/licenses/>. 22 23 ;;; Commentary: 24 25 ;; This library implements gitignore commands. 26 27 ;;; Code: 28 29 (require 'magit) 30 31 ;;; Transient 32 33 ;;;###autoload (autoload 'magit-gitignore "magit-gitignore" nil t) 34 (transient-define-prefix magit-gitignore () 35 "Instruct Git to ignore a file or pattern." 36 :man-page "gitignore" 37 ["Gitignore" 38 ("t" "shared at toplevel (.gitignore)" 39 magit-gitignore-in-topdir) 40 ("s" "shared in subdirectory (path/to/.gitignore)" 41 magit-gitignore-in-subdir) 42 ("p" "privately (.git/info/exclude)" 43 magit-gitignore-in-gitdir) 44 ("g" magit-gitignore-on-system 45 :if (lambda () (magit-get "core.excludesfile")) 46 :description (lambda () 47 (format "privately for all repositories (%s)" 48 (magit-get "core.excludesfile"))))] 49 ["Skip worktree" 50 (7 "w" "do skip worktree" magit-skip-worktree) 51 (7 "W" "do not skip worktree" magit-no-skip-worktree)] 52 ["Assume unchanged" 53 (7 "u" "do assume unchanged" magit-assume-unchanged) 54 (7 "U" "do not assume unchanged" magit-no-assume-unchanged)]) 55 56 ;;; Gitignore Commands 57 58 ;;;###autoload 59 (defun magit-gitignore-in-topdir (rule) 60 "Add the Git ignore RULE to the top-level \".gitignore\" file. 61 Since this file is tracked, it is shared with other clones of the 62 repository. Also stage the file." 63 (interactive (list (magit-gitignore-read-pattern))) 64 (magit-with-toplevel 65 (magit--gitignore rule ".gitignore") 66 (magit-run-git "add" ".gitignore"))) 67 68 ;;;###autoload 69 (defun magit-gitignore-in-subdir (rule directory) 70 "Add the Git ignore RULE to a \".gitignore\" file in DIRECTORY. 71 Prompt the user for a directory and add the rule to the 72 \".gitignore\" file in that directory. Since such files are 73 tracked, they are shared with other clones of the repository. 74 Also stage the file." 75 (interactive (list (magit-gitignore-read-pattern) 76 (read-directory-name "Limit rule to files in: "))) 77 (magit-with-toplevel 78 (let ((file (expand-file-name ".gitignore" directory))) 79 (magit--gitignore rule file) 80 (magit-run-git "add" (magit-convert-filename-for-git file))))) 81 82 ;;;###autoload 83 (defun magit-gitignore-in-gitdir (rule) 84 "Add the Git ignore RULE to \"$GIT_DIR/info/exclude\". 85 Rules in that file only affects this clone of the repository." 86 (interactive (list (magit-gitignore-read-pattern))) 87 (magit--gitignore rule (expand-file-name "info/exclude" (magit-gitdir))) 88 (magit-refresh)) 89 90 ;;;###autoload 91 (defun magit-gitignore-on-system (rule) 92 "Add the Git ignore RULE to the file specified by `core.excludesFile'. 93 Rules that are defined in that file affect all local repositories." 94 (interactive (list (magit-gitignore-read-pattern))) 95 (magit--gitignore rule 96 (or (magit-get "core.excludesFile") 97 (error "Variable `core.excludesFile' isn't set"))) 98 (magit-refresh)) 99 100 (defun magit--gitignore (rule file) 101 (when-let ((directory (file-name-directory file))) 102 (make-directory directory t)) 103 (with-temp-buffer 104 (when (file-exists-p file) 105 (insert-file-contents file)) 106 (goto-char (point-max)) 107 (unless (bolp) 108 (insert "\n")) 109 (insert (replace-regexp-in-string "\\(\\\\*\\)" "\\1\\1" rule)) 110 (insert "\n") 111 (write-region nil nil file))) 112 113 (defun magit-gitignore-read-pattern () 114 (let* ((default (magit-current-file)) 115 (base (car magit-buffer-diff-files)) 116 (base (and base (file-directory-p base) base)) 117 (choices 118 (delete-dups 119 (--mapcat 120 (cons (concat "/" it) 121 (and-let* ((ext (file-name-extension it))) 122 (list (concat "/" (file-name-directory it) "*." ext) 123 (concat "*." ext)))) 124 (sort (nconc 125 (magit-untracked-files nil base) 126 ;; The untracked section of the status buffer lists 127 ;; directories containing only untracked files. 128 ;; Add those as candidates. 129 (seq-filter #'directory-name-p 130 (magit-list-files 131 "--other" "--exclude-standard" "--directory" 132 "--no-empty-directory" "--" base))) 133 #'string-lessp))))) 134 (when default 135 (setq default (concat "/" default)) 136 (unless (member default choices) 137 (setq default (concat "*." (file-name-extension default))) 138 (unless (member default choices) 139 (setq default nil)))) 140 (magit-completing-read "File or pattern to ignore" 141 choices nil nil nil nil default))) 142 143 ;;; Skip Worktree Commands 144 145 ;;;###autoload 146 (defun magit-skip-worktree (file) 147 "Call \"git update-index --skip-worktree -- FILE\"." 148 (interactive 149 (list (magit-read-file-choice "Skip worktree for" 150 (magit-with-toplevel 151 (cl-set-difference 152 (magit-list-files) 153 (magit-skip-worktree-files) 154 :test #'equal))))) 155 (magit-with-toplevel 156 (magit-run-git "update-index" "--skip-worktree" "--" file))) 157 158 ;;;###autoload 159 (defun magit-no-skip-worktree (file) 160 "Call \"git update-index --no-skip-worktree -- FILE\"." 161 (interactive 162 (list (magit-read-file-choice "Do not skip worktree for" 163 (magit-with-toplevel 164 (magit-skip-worktree-files))))) 165 (magit-with-toplevel 166 (magit-run-git "update-index" "--no-skip-worktree" "--" file))) 167 168 ;;; Assume Unchanged Commands 169 170 ;;;###autoload 171 (defun magit-assume-unchanged (file) 172 "Call \"git update-index --assume-unchanged -- FILE\"." 173 (interactive 174 (list (magit-read-file-choice "Assume file to be unchanged" 175 (magit-with-toplevel 176 (cl-set-difference 177 (magit-list-files) 178 (magit-assume-unchanged-files) 179 :test #'equal))))) 180 (magit-with-toplevel 181 (magit-run-git "update-index" "--assume-unchanged" "--" file))) 182 183 ;;;###autoload 184 (defun magit-no-assume-unchanged (file) 185 "Call \"git update-index --no-assume-unchanged -- FILE\"." 186 (interactive 187 (list (magit-read-file-choice "Do not assume file to be unchanged" 188 (magit-with-toplevel 189 (magit-assume-unchanged-files))))) 190 (magit-with-toplevel 191 (magit-run-git "update-index" "--no-assume-unchanged" "--" file))) 192 193 ;;; _ 194 (provide 'magit-gitignore) 195 ;;; magit-gitignore.el ends here