好吧,这次的锅是我的。
我在 .dir-local 里添加了一个钩子,会在保存 buffer 之前自动检查是否含有中文字符,如果有,则保存为 GBK。出乎我意料的是这个钩子居然对 commit message 也起作用(commit message 其实是保存在 .git
目录下的文本文件),显然 commit message 不能用 GBK,因而造成了乱码。
这次的教训是文件夹变量过于强大,不能随便用啊。
好吧,这次的锅是我的。
我在 .dir-local 里添加了一个钩子,会在保存 buffer 之前自动检查是否含有中文字符,如果有,则保存为 GBK。出乎我意料的是这个钩子居然对 commit message 也起作用(commit message 其实是保存在 .git
目录下的文本文件),显然 commit message 不能用 GBK,因而造成了乱码。
这次的教训是文件夹变量过于强大,不能随便用啊。
这个乱码不知道解决了没?
我这有不同的工程,源码文件编码不统一,用 GBK 和 UTF-8 的都有, commit message buffer 统一用 UTF-8 就可以。
文件编码不同导致需要用 .dir-locals.el
对不同的工程做设置,比如 GBK :
((nil . ((magit-git-output-coding-system . chinese-gbk)
(eval . (setq-local magit-git-global-arguments
(append magit-git-global-arguments '("-c" "i18n.logOutputEncoding=GBK"))))
))
)
另外有点出乎意料,但不影响使用的是,这样设置了以后, magit-git-global-arguments
变量不知道为何
会不断追加,我使用了一段时间之后 (message "%S" magit-git-global-arguments)
如下:
"-c\" \"core.preloadindex=true\" \"-c\" \"log.showSignature=false\" \"-c\" \"i18n.logOutputEncoding=UTF-8\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\" \"-c\" \"i18n.logOutputEncoding=GBK\")"
其它的不清楚,append 是会不断增加吧,用 add-to-list
应该可以避免重复吧
add-to-list
恐怕不行,这里需要多个 -c
。
本来我理解 .dir-locals.el
中的变量,在每次创建 buffer 的时候会重新以全局值初始化,但现状似乎不是这样。
你用的 eval 变量,每次都会运行 eval 对应的语句,你试试直接定义变量并指定 mode。nil 的话是对所有 mode 都运行
PS:
我记起来我的问题最终确实是通过 dir local 解决的,这个方法很强大,但是前提是要正确使用
我这里无法重现。建议你在 --batch
模式下做个测试,把完整脚本贴上来。
如何能无须确认地加载 .dir-locals.el
? 我看了 safe-local-variable-values
,没弄明白怎么用。
(setq enable-local-variables :all)
我的配置文件里摘出来两个例子:
(add-to-list 'safe-local-variable-values '(org-src-preserve-indentation))
注意上面变量的值设为了 nil
,所以后面省略了,如果是 t
应该这么写
'(org-src-preserve-indentation . t)
对于 eval,可以用下面的(只是举个例子,add-hook 可以换成任何语句):
(add-to-list 'safe-local-eval-forms
'(add-hook 'after-save-hook 'examples-clear-hash nil t))
emacs -Q --batch -l /tmp/magit-reproduce.el
, 我这 message 输出如下:
The value is: ("--no-pager" "--literal-pathspecs" "-c" "core.preloadindex=true" "-c" "log.showSignature=false" "-c" "i18n.logOutputEncoding=GBK" "-c" "i18n.logOutputEncoding=GBK" "-c" "i18n.logOutputEncoding=GBK")
Emacs version: GNU Emacs 26.1 (build 1, x86_64-redhat-linux-gnu, GTK+ Version 3.22.30)
of 2018-06-26
magit version: Magit 20180922.740, Git 2.17.1, Emacs 26.1, gnu/linux
magit-reproduce.el
如下:
;;; install magit
(require 'package)
(setq package-archives
'(("gnu" . "http://mirrors.163.com/elpa/gnu/")
("melpa" . "http://mirrors.163.com/elpa/melpa/")
("melpa-stable" . "http://mirrors.163.com/elpa/melpa-stable/")))
(setq package-user-dir "/tmp/elpa/")
(package-refresh-contents)
(package-initialize)
(package-install 'magit)
(require 'magit)
;;; prepare .dir-locals.el
(with-temp-buffer
(insert "
((nil . ((magit-git-output-coding-system . chinese-gbk)
(eval . (setq-local magit-git-global-arguments
(append magit-git-global-arguments '(\"-c\" \"i18n.logOutputEncoding=GBK\"))))
))
)")
(make-directory "/tmp/magit" t)
(shell-command "cd /tmp/magit; git init .")
(write-file "/tmp/magit/.dir-locals.el"))
;;; run magit
(let ((default-directory "/tmp/magit/")
(enable-local-variables :all)
(magit-version (with-temp-buffer (magit-version (current-buffer))
(buffer-string))))
(magit-status)
(message "The value is: %S" magit-git-global-arguments)
(message "Emacs version: %s" (version))
(message "magit version: %s" magit-version))
我知道怎么回事了,我因为没有用 magit,把它当作一个普遍的问题,所以没有重现。
原因在于 magit 强行调用了 Local variable 相关的函数,导致了二次赋值,造成参数叠加:
;; (magit-status)
;; -> (magit-status-internal default-directory)
;; --> (magit-mode-setup 'magit-status-mode)
;; ---> (magit-status-mode)
;; ----> (hack-dir-local-variables-non-file-buffer)
;; -----> (hack-local-variables-apply)
(defun hack-local-variables-apply ()
(when file-local-variables-alist
;; Any 'evals must run in the Right sequence.
(setq file-local-variables-alist
(nreverse file-local-variables-alist))
(run-hooks 'before-hack-local-variables-hook)
(dolist (elt file-local-variables-alist)
(hack-one-local-variable (car elt) (cdr elt)))) ;; <<--
(run-hooks 'hack-local-variables-hook))
关键在倒数第2行,它把 .dir-locals.el
里的 eval 又执行了一遍,然而在此之前 local variable 其实已经生效了。
这是我昨天的测试,刚开始变量是正常的,即使打开多个文件,调用 (hack-local-variables-apply)
之后也产生了叠加:
$ emacs --batch -l /path/to/test-dir-locals.el
--------------------------------- before visit testN.el
Variables alist: nil
test-args: ("-c" "foo")
--------------------------------- after visit testN.el
Variables alist: ((eval setq-local test-args (append test-args '("-c" "bar"))) (lexical-binding . t))
test-args: ("-c" "foo" "-c" "bar")
--------------------------------- after ‘hack-local-variables-apply’
test-args: ("-c" "foo" "-c" "bar" "-c" "bar")
;;; Usage: /path/to/emacs -batch -l /path/to/test-dir-locals.el
;;; Date: 2018-09-22_11.53.31
(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)
(defvar test-args '("-c" "foo"))
(defun test--make-project (specs)
"Make empty project and create directory & files specified by SPECS.
Example of SPECS:
'(\"src/\"
(\"src/.node-version\" . \"6.0.0\"))
Return project root."
(let ((root (make-temp-file "test-nodenv--" 'root "/")))
(dolist (spec specs)
(cond
((consp spec)
(let ((file-path (concat root (car spec)))
(file-content (cdr spec)))
(make-directory (file-name-directory file-path) t)
(with-temp-buffer
(insert file-content)
(write-region (point-min) (point-max) file-path))))
(t
(let ((folder-path (concat root spec)))
(make-directory (file-name-directory folder-path) t)))))
root))
(defun test--get-args (file-name)
"Open `FILE-NAME', return local variable `TEST-ARGS'."
(find-file file-name)
(emacs-lisp-mode)
test-args)
(add-hook 'after-init-hook
(lambda ()
;; 在 /tem/ 下创建一个项目,包含测试文件 `testN.el' 和 `.dir-locals.el'
(let ((root
(test--make-project
(list
(cons "test1.el" ";;; test1.el --- Test 1 -*- lexical-binding: t -*-")
(cons "test2.el" ";;; test1.el --- Test 2 -*- lexical-binding: t -*-")
(cons ".dir-locals.el"
"((nil . ((eval . (setq-local test-args (append test-args '(\"-c\" \"bar\")))))))")))))
;; 允许所有 local variables, 无需手动确认
(setq enable-local-variables :all)
;; 打开文件之前,`tst-args' 仍然为初始值
(message "\n--------------------------------- before visit testN.el")
(message "Variables alist: %S" file-local-variables-alist)
(message "test-args: %S" test-args)
;; 打开多个文件之后,`test-args' 已经改变,但并没产生有叠加
(message "\n--------------------------------- after visit testN.el")
(test--get-args (concat root "test1.el"))
(test--get-args (concat root "test2.el"))
(message "Variables alist: %S" file-local-variables-alist)
(message "test-args: %S" test-args)
;; 执行 (hack-local-variables-apply) 之后,`test-args' 内容产生叠加
(message "\n--------------------------------- after `hack-local-variables-apply'")
;; Simulate the issue of magit: https://emacs-china.org/t/topic/3511/22
;; (magit-status)
;; -> (magit-status-internal default-directory)
;; --> (magit-mode-setup 'magit-status-mode)
;; ---> (magit-status-mode)
;; ----> (hack-dir-local-variables-non-file-buffer)
;; -----> (hack-local-variables-apply)
(hack-local-variables-apply)
;; --- Defined as follows ---
;; (when file-local-variables-alist
;; (setq file-local-variables-alist
;; (nreverse file-local-variables-alist))
;; (run-hooks 'before-hack-local-variables-hook)
;; (dolist (elt file-local-variables-alist)
;; (hack-one-local-variable (car elt) (cdr elt)))) ;; <<--- re-eval here
(message "test-args: %S" test-args)
;; ;; 打印 `.dir-locals.el' 内容
;; (find-file (concat root ".dir-locals.el"))
;; (message (buffer-string))
)))
(run-hooks 'after-init-hook)
;;; test-dir-locals.el ends here
分析得很赞,难怪我 M-x magit-status
手动确认时选 y
需要确认两次。
不过看结果却是追加了三次,按道理应该两次。 Anyway ,不影响使用,先不管了
那你是用命令行嘛?
你指终端 emacs ?一直都是。
我是说命令行git?
同样,一直都是
厉害,我没用 emacs 时用命令行 git 一直无法顺畅使用,经常需要查 man 或 google ;用了 magit 之后才慢慢顺畅起来。
习惯了命令、管道这样的工作方式,而且 git 也有很多强大的交互式子命令,不知道 magit 有没有实现。第一次用 magit 的时候,有点不知所措,然后就放弃了;总感觉不如命令行踏实,不确定按一个下去到底会产生怎样的效果。