能不能让projectile总是识别gitignore文件啊?

只有一个孤零零的 .gitignore 时,项目的根目录无法确定:

$ cd ~/Downloads/test-orphan-gitignore && ls -a
./  ../  .gitignore  foo.el  bar.el

$ cat .gitignore
*~
bar.el

$ emacsq.sh -P projectile -M projectile-mode -nw --eval "(print (projectile-project-root))" --batch
"/Users/john/"

当执行 -find-file 或获取文件列表时就会卡住,因为 ~ 底下文件太多了:

$ time emacsq.sh -P projectile -M projectile-mode -nw --eval "(print (projectile-project-files (projectile-project-root)))" --batch
^C
________________________________________________________
Executed in   90.21 secs   fish           external
   usr time    2.22 secs  192.00 micros    2.22 secs
   sys time    0.63 secs  1692.00 micros   0.63 secs

所以,

首先是解决项目根的问题

在缺失 .git 的情况下,让 projectile 认 .gitignore 为根:

$ emacsq.sh -P projectile -M projectile-mode -nw --eval \
  "(progn
     (add-to-list 'projectile-project-root-files-bottom-up \".gitignore\")
     (print (list :project-root (projectile-project-root)))
     (print (list :project-files (projectile-project-files (projectile-project-root)))))" --batch

(:project-root "/Users/johon/Downloads/test-orphan-gitignore/")

(:project-files ("./.gitignore" "./.gitignore~" "./bar.el" "./foo.el"))

然后再解决如何应用 .gitignore 规则

然而 projectile 并不直接处理 .gitignore, 而是直接从 git ls-files 获得文件列表。所幸它和 .projectile 差别不是很大,所以这里又有 3 种可能:

  1. git ls-files 可否应用于一个非项目文件夹?
  2. 实现一个 projectile-gitignore-to-dirconfig 转换函数?
  3. 参考 projectile-parse-dirconfig-file 实现一个 projectile-parse-gitignore-file

如果 1 可行的话就最简单,23 差不多。

我试了一下直接把 .gitignore 当作 .projectile 用(暂不考虑 .gitignore 文件里存在注释的情况)是可行的:

$ emacs -Q -nw -l /path/to/test-projectile-orphan-gitignore.el --batch

(:project-root "/Users/johon/Downloads/test-orphan-gitignore/")

(:project-files (".gitignore" "foo.el"))
test-project-orphan-gitignore.el
;;; Usage: /path/to/emacs -nw -Q -l /path/to/test-projectile-orphan-gitignore.el
;;; Date: 2021-08-22_22.43.53
(toggle-debug-on-error)
(global-set-key (kbd "C-h") 'delete-backward-char)
(global-set-key (kbd "M-h") 'backward-kill-word)
(global-set-key (kbd "<f1>") 'help-command)
(define-key isearch-mode-map "\C-h" 'isearch-delete-char)



(require 'seq)
(setq user-emacs-directory
      (car (seq-filter
            #'file-exists-p
            (list (format "~/.emacs.d/%s/" emacs-version)
                  (format "~/.emacs.d/%s/" emacs-major-version)
                  "~/.emacs.d/"))))
(setq package-user-dir (concat user-emacs-directory "elpa/"))
(package-initialize)

(require 'projectile)
(projectile-mode 1)
(require 'ido)
(ido-mode 1)



(progn
  
  ;; (setq projectile-indexing-method 'native)
  (global-set-key (kbd "C-x C-f") #'projectile-find-file)

  ;; 1. 确定项目的根
  
  (add-to-list 'projectile-project-root-files-bottom-up ".gitignore")

  (defun projectile-orphan-gitignore-p (project-root)
    (let ((default-directory project-root))
      (and (file-exists-p ".gitignore") (not (file-exists-p ".git")))))

  (define-advice projectile-project-vcs (:filter-return (return) +orphan-gitignore)
    (or (if (equal return 'none)
            (when (projectile-orphan-gitignore-p (projectile-project-root))
              'orphan-gitignore))
        return))

  ;; 2. 应用 .gitnore 规则

  (define-advice projectile-dirconfig-file (:filter-return (return) +orphan-gitignore)
    (if (file-exists-p return) return
      (let ((default-directory (projectile-project-root)))
        (when (projectile-orphan-gitignore-p default-directory)
          (expand-file-name ".gitignore" default-directory)))))

  ;; --- 强制修改 projectile-indexing-method 为 native
  ;; (define-advice projectile-project-files (:around (orig-fn project-root) +orphan-gitignore)
  ;;   (let ((projectile-indexing-method
  ;;          (if (projectile-orphan-gitignore-p project-root)
  ;;              'native
  ;;            projectile-indexing-method)))
  ;;     (funcall orig-fn project-root)))
  ;; --- 或在 `projectile-dir-files-alien' 内增加 orphan-gitignore 类型
  (define-advice projectile-dir-files-alien (:override (directory) +orphan-gitignore)
    (let ((vcs (projectile-project-vcs directory)))
      (cond
       ((eq vcs 'orphan-gitignore)
        (projectile-dir-files-native directory))
       ((eq vcs 'git)
        (nconc (projectile-files-via-ext-command directory (projectile-get-ext-command vcs))
               (projectile-get-sub-projects-files directory vcs)))
       (t (projectile-files-via-ext-command directory (projectile-get-ext-command vcs))))))

  (print (list :project-root (projectile-project-root)))
  (print (list :project-files (projectile-project-files (projectile-project-root)))))

;;; test-projectile-orphan-gitignore.el ends here
1 个赞