Magit在Windows下无法stage hunk的问题

下面是在Windows上使用Magit碰到的一个问题, 已经解决并在GitHub上提交了issue:https://github.com/magit/magit/issues/2403.

因为文档实在太缺乏了, 不得不去读Magit的elisp代码, 下面是涉及到的代码, 以及插入调试语句的地方.

Magit如何调用外部程序git, 以及所传递的参数

 
(defun magit-start-process (program &optional input &rest args)
  "Start PROGRAM, prepare for refresh, and return the process object.
 
If optional argument INPUT is non-nil, it has to be a buffer or
the name of an existing buffer.  The buffer content becomes the
processes standard input.
 
The process is started using `start-file-process' and then setup
to use the sentinel `magit-process-sentinel' and the filter
`magit-process-filter'.  Information required by these functions
is stored in the process object.  When this function returns the
process has not started to run yet so it is possible to override
the sentinel and filter.
 
After the process returns, `magit-process-sentinel' refreshes the
buffer that was current when `magit-start-process' was called (if
it is a Magit buffer and still alive), as well as the respective
Magit status buffer.  Unmodified buffers visiting files that are
tracked in the current repository are reverted if
`magit-revert-buffers' is non-nil."
  (-let* (((process-buf . section)
           (magit-process-setup program args))
          (process
           (let ((process-connection-type
                  ;; Don't use a pty, because it would set icrnl
                  ;; which would modify the input (issue #20).
                  (and (not input) magit-process-connection-type))
                 (process-environment (append (magit-cygwin-env-vars)
                                              process-environment)))
             (print (format "before apply start-file-process --%s-- --%s-- --%s-- --%s--" program args (file-name-nondirectory program) process-buf))
             (apply #'start-file-process
                    (file-name-nondirectory program)
                    process-buf program args))))
    (with-editor-set-process-filter process #'magit-process-filter)
    (set-process-sentinel process #'magit-process-sentinel)
    (set-process-buffer   process process-buf)
    (process-put process 'section section)
    (process-put process 'command-buf (current-buffer))
    (process-put process 'default-dir default-directory)
    (when inhibit-magit-refresh
      (process-put process 'inhibit-refresh t))
    (when inhibit-magit-revert
      (process-put process 'inhibit-revert t))
    (setf (magit-section-process section) process)
    (with-current-buffer process-buf
      (set-marker (process-mark process) (point)))
    (when input
      (with-current-buffer input
        (message "did it send the region to process")
        ;(message (format "and the buffer string is --%s--" (buffer-substring (mark) (point))))
        (process-send-region process (point-min) (point-max))
        (process-send-eof    process)))
    (setq magit-this-process process)
    (setf (magit-section-value section) process)
    (magit-process-display-buffer process)
    process))
 

Magit如何提取diff并发送给git

 
(defun magit-apply-hunk (section &rest args)
  (when (string-match "^diff --cc" (magit-section-parent-value section))
    (user-error "Cannot un-/stage resolution hunks.  Stage the whole file"))
  (message (format "region of the hunk --%s--" (concat (magit-diff-file-header section) (buffer-substring (magit-section-start section)
                                               (magit-section-end section))) ) )
  (magit-apply-patch (magit-section-parent section) args
                     (concat (magit-diff-file-header section)
                             (buffer-substring (magit-section-start section)
                                               (magit-section-end section)))))
 

Magit会先将diff写入一个临时buffer, 然后将buffer的内容发送给git, 下面的语句会输出buffer中的内容

 
(defun magit-apply-patch (section:s args patch)
  (let* ((files (if (atom section:s)
                    (list (magit-section-value section:s))
                  (mapcar 'magit-section-value section:s)))
         (command (symbol-name this-command))
         (command (if (and command (string-match "^magit-\\([^-]+\\)" command))
                      (match-string 1 command)
                    "apply")))
    (when (and magit-wip-before-change-mode (not inhibit-magit-refresh))
      (magit-wip-commit-before-change files (concat " before " command)))
    (with-temp-buffer
      (insert patch)
      (message "after insert patch")
      (mark-whole-buffer)
      (append-to-file 1 150 "c:\\tmp\\patch.txt")
      (magit-run-git-with-input nil
        "apply" args "-p0 --summary"
        (unless (magit-diff-context-p) "--unidiff-zero")
        "--ignore-space-change" "-"))
    (unless inhibit-magit-refresh
      (when magit-wip-after-apply-mode
        (magit-wip-commit-after-apply files (concat " after " command)))
      (magit-refresh))))
 

下面两个函数是具体调用git的入口

 
(defun magit-run-git-with-input (input &rest args)
  "Call Git in a separate process.
ARGS is flattened and then used as arguments to Git.
 
The first argument, INPUT, should be a buffer or the name of
an existing buffer.  The content of that buffer is used as the
process' standard input.  It may also be nil in which case the
current buffer is used.
 
Option `magit-git-executable' specifies the Git executable and
option `magit-git-global-arguments' specifies constant arguments.
The remaining arguments ARGS specify arguments to Git, they are
flattened before use.
 
After Git returns, the current buffer (if it is a Magit buffer)
as well as the current repository's status buffer are refreshed.
Unmodified buffers visiting files that are tracked in the current
repository are reverted if `magit-revert-buffers' is non-nil.
When INPUT is nil then do not refresh any buffers.
 
This function actually starts a asynchronous process, but it then
waits for that process to return."
  (declare (indent 1))
  (magit-start-git (or input (current-buffer)) args)
  (magit-process-wait)
  (when input (magit-refresh)))
 
(defun magit-start-git (input &rest args)
  "Start Git, prepare for refresh, and return the process object.
 
If INPUT is non-nil, it has to be a buffer or the name of an
existing buffer.  The buffer content becomes the processes
standard input.
 
Option `magit-git-executable' specifies the Git executable and
option `magit-git-global-arguments' specifies constant arguments.
The remaining arguments ARGS specify arguments to Git, they are
flattened before use.
 
After Git returns some buffers are refreshed: the buffer that was
current when this function was called (if it is a Magit buffer
and still alive), as well as the respective Magit status buffer.
Unmodified buffers visiting files that are tracked in the current
repository are reverted if `magit-revert-buffers' is non-nil.
 
See `magit-start-process' for more information."
  (run-hooks 'magit-pre-start-git-hook)
  (apply #'magit-start-process magit-git-executable input
         (magit-process-git-arguments args)))  
 

结论

最后找到问题的源头

 
default-process-coding-system is a variable defined in `C source code'.
Its value is (utf-8-dos . utf-8-unix)
 
Documentation:
Cons of coding systems used for process I/O by default.
The car part is used for decoding a process output,
the cdr part is used for encoding a text to be sent to a process.
 
[back]    [forward]
 
 

因为第二个编码用于对发送给进程的内容进行编码, 而unix编码换行符是lf, windows的是crlf, 实际发送的时候cr会被删除.

因此解决的方法是

 
(setq default-process-coding-system (cons 'utf-8-dos 'utf-8-dos))