[インデックス 13338] ファイルの概要
このコミットは、EmacsのGoモード(go-mode.el
)におけるgofmt
のパッチ適用処理に関するバグ修正です。具体的には、gofmt
が生成する差分(diff)のヘッダーに含まれる一時ファイルパスの処理が原因で、/tmp
ディレクトリ内に存在するGoファイルに対してgofmt
を適用する際に発生していた問題を解決します。
コミット
commit 44a3a58e451bcabad67fbd31e203a4f9f1ba2eae
Author: Jean-Marc Eurin <jmeurin@google.com>
Date: Wed Jun 13 10:25:00 2012 -0400
misc/emacs: Fix a failure when /tmp/<file>.go exists.
R=sameer
CC=golang-dev
https://golang.org/cl/6296060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/44a3a58e451bcabad67fbd31e203a4f9f1ba2eae
元コミット内容
misc/emacs: Fix a failure when /tmp/<file>.go exists.
このコミットメッセージは、/tmp
ディレクトリに存在するGoファイルに対してgofmt
を適用する際に発生する不具合を修正することを明確に示しています。
変更の背景
Go言語の公式ツールであるgofmt
は、Goソースコードを標準的なスタイルに自動整形するツールです。Emacsのgo-mode.el
は、このgofmt
をEmacsエディタ内で利用するための機能を提供しています。通常、gofmt
はファイルの内容を標準入力から受け取り、整形された内容を標準出力に出力するか、-d
オプションを付けて差分(diff)を出力します。
go-mode.el
は、ユーザーがgofmt
を実行した際に、現在のバッファの内容をgofmt
に渡し、gofmt -d
が生成した差分を解析し、その差分を現在のバッファに適用することで、コードの整形を実現しています。
問題は、gofmt
が標準入力から内容を受け取って差分を生成する際、差分ヘッダー(--- a/path/to/file
や +++ b/path/to/file
の行)に一時ファイル名(例: /tmp/gofmtXXXXX
)を使用することがある点にありました。go-mode.el
のgofmt-apply-patch
関数は、この一時ファイル名を実際のファイルパスに置き換える処理を行っていましたが、そのロジックに不備があり、特に/tmp
ディレクトリに存在するGoファイルに対してgofmt
を適用しようとすると、パッチの適用に失敗する問題が発生していました。
具体的な失敗の原因は、元のコードが一時ファイルパスを「/tmp/
+ ファイル名」という形式に変換しようとしていたことにあります。もし編集中のファイル自体が/tmp/foo.go
のようなパスであった場合、gofmt
が生成する一時ファイルパス(例: /tmp/gofmt12345
)を、元のコードは「/tmp/
+ foo.go
」つまり/tmp/foo.go
に変換しようとします。しかし、この変換ロジックが、gofmt
が生成するdiffの実際の構造や、ファイルが/tmp
に存在する場合のパス解決と合致せず、パッチの適用が正しく行われない状況が生じていました。
前提知識の解説
- Emacs Lisp (Elisp): Emacsエディタの拡張機能や設定を記述するために使用されるプログラミング言語です。
go-mode.el
はこのElispで書かれています。 - gofmt: Go言語の公式ツールで、Goソースコードを自動的に整形します。
-d
オプションを付けると、整形前後の差分(diff形式)を出力します。 - Diff形式: ファイルの変更内容を示す標準的なテキスト形式です。通常、変更された行の前に
+
や-
が付き、変更されたファイルのパスは--- a/path/to/file
(変更前)と+++ b/path/to/file
(変更後)のようなヘッダーで示されます。 - 一時ファイル: プログラムが一時的にデータを保存するために作成するファイルです。
gofmt
が標準入力から処理を行う際、内部的に一時ファイルを使用することがあります。 replace-regexp
(Emacs Lisp関数): 指定された正規表現にマッチする文字列を、別の文字列に置換するEmacs Lispの関数です。file-name-nondirectory
(Emacs Lisp関数): ファイルパスからディレクトリ部分を除外し、ファイル名のみを返す関数です。例:(file-name-nondirectory "/path/to/file.go")
は"file.go"
を返します。point-min
/point-max
(Emacs Lisp関数): 現在のバッファの先頭(最小ポイント)と末尾(最大ポイント)を返します。replace-regexp
などの関数で置換範囲を指定する際に使用されます。
技術的詳細
このコミットの核心は、gofmt-apply-patch
関数におけるdiffヘッダーのパス置換ロジックの改善です。
gofmt-apply-patch
関数は、gofmt -d
の出力であるdiffをpatchbuf
(パッチが格納されたバッファ)から読み込み、それをsrcbuf
(元のGoコードが格納されたバッファ)に適用します。
元のコードでは、以下の2段階の置換を行っていました。
(replace-string gofmt-stdin-tag filename nil min (point-max))
gofmt-stdin-tag
という内部的なタグ(おそらくgofmt
が標準入力処理時にdiffヘッダーに埋め込む可能性のある文字列、例:<stdin>
や-
)を、filename
(ディレクトリ部分を除いたファイル名)に置換しようとしていました。
(replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- /tmp/" filename) nil min (point-max))
- 正規表現
^--- /tmp/gofmt[0-9]*
にマッチする文字列(gofmt
が生成する一時ファイルパスのヘッダー)を、--- /tmp/
にfilename
(ディレクトリ部分を除いたファイル名)を結合したパスに置換していました。
- 正規表現
この2番目の置換が問題でした。filename
変数はfile-name-nondirectory
によってファイル名のみに変換されていたため、例えば/home/user/project/main.go
というファイルの場合、filename
はmain.go
となります。このとき、置換後のパスは--- /tmp/main.go
となります。しかし、実際のファイルパスは/home/user/project/main.go
であり、gofmt
が生成するdiffは、一時ファイルパスを実際のファイルパスにマッピングする必要があります。--- /tmp/main.go
というパスは、元のファイルが/tmp
ディレクトリにない限り、正しくありません。特に、元のファイルが/tmp
ディレクトリに存在する場合(例: /tmp/test.go
)、この置換はさらに混乱を招く可能性がありました。
新しいコードでは、この問題を解決するために以下の変更が行われました。
let
ブロックとreplace-string gofmt-stdin-tag
の行が削除されました。- これは、
gofmt-stdin-tag
による置換が不要になったか、あるいは問題の原因となっていたことを示唆しています。gofmt
の出力形式が変更され、gofmt-stdin-tag
のようなプレースホルダーが使われなくなったか、あるいは次のreplace-regexp
で十分になったと考えられます。
- これは、
replace-regexp
の置換文字列が(concat "--- " filename)
に変更されました。- ここで重要なのは、
filename
変数がgofmt-apply-patch
関数の引数として渡される絶対パス(例:/home/user/project/main.go
)を指すようになった点です。これにより、gofmt
が生成する一時ファイルパス(例:--- /tmp/gofmt12345
)は、直接--- /home/user/project/main.go
のような正しい絶対パスに置換されるようになります。これにより、ファイルがどこにあっても、gofmt
のdiffが正しく適用されるようになります。
- ここで重要なのは、
この変更により、gofmt
が一時ファイル名を使用するdiffを生成した場合でも、Emacsのgo-mode
がそのdiffを正しく解釈し、実際のファイルパスにマッピングして適用できるようになり、/tmp
ディレクトリ内のファイルに対するgofmt
の適用失敗が解消されました。
コアとなるコードの変更箇所
misc/emacs/go-mode.el
ファイルのgofmt-apply-patch
関数内。
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -817,13 +817,10 @@ Replace the current buffer on success; display errors on failure."
(defun gofmt-apply-patch (filename srcbuf patchbuf)
(require 'diff-mode)
- ;; apply all the patch hunks and restore the mark and point
+ ;; apply all the patch hunks
(with-current-buffer patchbuf
- (let ((filename (file-name-nondirectory filename))
- (min (point-min)))
- (replace-string gofmt-stdin-tag filename nil min (point-max))
- (replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- /tmp/" filename)
- nil min (point-max)))
+ (replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- " filename)
+ nil (point-min) (point-max))
(condition-case nil
(while t
(diff-hunk-next)
コアとなるコードの解説
変更されたのはgofmt-apply-patch
関数内の以下の部分です。
変更前:
(let ((filename (file-name-nondirectory filename))
(min (point-min)))
(replace-string gofmt-stdin-tag filename nil min (point-max))
(replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- /tmp/" filename)
nil min (point-max)))
このブロックでは、まずfilename
変数をfile-name-nondirectory
でファイル名のみに再定義し、gofmt-stdin-tag
の置換と、/tmp/gofmt
から始まるパスの置換を行っていました。特に、後者の置換で--- /tmp/
にファイル名を結合していた点が問題でした。
変更後:
(replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- " filename)
nil (point-min) (point-max))
変更後では、let
ブロックが削除され、gofmt-stdin-tag
の置換もなくなりました。そして、replace-regexp
の置換文字列が(concat "--- " filename)
に簡略化されました。ここで使われているfilename
は、gofmt-apply-patch
関数の引数として渡された元のファイルへの絶対パスです。これにより、gofmt
が生成する一時ファイルパスのヘッダーが、編集中のファイルの正しい絶対パスに置換されるようになり、パッチ適用時のパス解決の不整合が解消されました。
関連リンク
- Go言語の
gofmt
ツールに関する公式ドキュメント: https://pkg.go.dev/cmd/gofmt - Emacs Lispのドキュメント(
replace-regexp
など): https://www.gnu.org/software/emacs/manual/html_node/elisp/
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Emacsの
go-mode.el
ソースコード(コミット前後の比較) gofmt
の動作に関する一般的な情報(標準入力と一時ファイルの使用)- Emacs Lispの関数に関するドキュメント