只有一个孤零零的 .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 种可能:
-
git ls-files
可否应用于一个非项目文件夹? - 实现一个
projectile-gitignore-to-dirconfig
转换函数? - 参考
projectile-parse-dirconfig-file
实现一个projectile-parse-gitignore-file
?
如果 1
可行的话就最简单,2
和 3
差不多。
我试了一下直接把 .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