关于编码选择的问题

好吧,这次的锅是我的。

我在 .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))
1 个赞

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))
1 个赞

我知道怎么回事了,我因为没有用 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 其实已经生效了。

1 个赞

这是我昨天的测试,刚开始变量是正常的,即使打开多个文件,调用 (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")
test-dir-locals.el
;;; 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 ,不影响使用,先不管了 :slight_smile:

那你是用命令行嘛?

你指终端 emacs ?一直都是。

我是说命令行git?

同样,一直都是

1 个赞

厉害,我没用 emacs 时用命令行 git 一直无法顺畅使用,经常需要查 man 或 google ;用了 magit 之后才慢慢顺畅起来。

习惯了命令、管道这样的工作方式,而且 git 也有很多强大的交互式子命令,不知道 magit 有没有实现。第一次用 magit 的时候,有点不知所措,然后就放弃了;总感觉不如命令行踏实,不确定按一个下去到底会产生怎样的效果。