🗂

git commit -aは一時的なインデックスファイルを作る

2023/06/25に公開

要約

git commit -a とか、 git commit ファイル名 でコミットするときには、一時的なインデックスファイルが作られているよ。

疑問

git commit -a するとバージョン管理してるけど git add してないファイルもコミット対象に入る。けど、コミットメッセージを書かずにキャンセルした時、元の状態に戻るのはどうやって実現されているんだろう?

例えば、aとbという二つのファイルが編集されていたとして、aのみをgit addしておく

$ git add a
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   a

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   b

この状態で git commit -a するとaもbもコミット対象となる:


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Committer: Foo Bar <foo@example.com>
#
# On branch master
# Changes to be committed:
#       modified:   a
#       modified:   b
#

viを:qで抜けてコミットをキャンセルするとaのみがaddされた状態に戻る:

Aborting commit due to empty commit message.
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   a

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   b

調査

コミット前の状態

$ ls .git
COMMIT_EDITMSG  HEAD  branches  config  description  hooks  index  info  logs  objects  refs

git commit -a で vi が起動している状態

$ ls .git
COMMIT_EDITMSG  HEAD  branches  config  description  hooks  index  index.lock  info  logs  objects  refs

前後を見比べると、index.lockというファイルが増えてる。

$ file .git/index.lock
.git/index.lock: Git index, version 2, 2 entries

Linux環境だったので、proc経由で環境変数を確認すると、

$ ps x|grep vi|grep -v grep
3861159 pts/2    S+     0:00 /usr/bin/vi /home/foo/tmp/t/.git/COMMIT_EDITMSG
$ sed "s/\x00/\n/g" /proc/3861159/environ|grep "^GIT"
GIT_AUTHOR_DATE=@1687649309 +0900
GIT_AUTHOR_EMAIL=foo@example.com
GIT_AUTHOR_NAME=Foo Bar
GIT_EXEC_PATH=/usr/lib/git-core
GIT_INDEX_FILE=/home/foo/tmp/t/.git/index.lock
GIT_PREFIX=

GIT_INDEX_FILE で制御できそうな雰囲気なので、それを指定してやると確かにコミット対象の状態が保持されている:

% GIT_INDEX_FILE=.git/index.lock git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   a
	modified:   b

一方、デフォルトのインデックスファイルを指定すると、コミット前の状態になっている:

% GIT_INDEX_FILE=.git/index git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   a

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   b

というわけで、

  • git commit -a すると一時的なインデックスファイルが作成され、そこにコミット対象の状態が保存される
  • その時、デフォルトのインデックスファイルは変更されず元のまま維持される
  • コミットキャンセルした場合には一時的なインデックスファイルがなかったことになり、元の状態に戻る

ということがわかった

調査の背景

Emacs に magit をインストールした状態で EDITOR="emacsclient -t" などとしていると、コミットメッセージ編集バッファと合わせて表示される差分が想定と違うものになってしまう。

調べて直そうと思ったけど、

  • 直すためには emacsclient の環境変数を接続先の emacs server 側で検知する必要がある
  • server-buffer-clients に格納されたプロセスのプロパティに環境変数が格納されているが、新規フレームを作成しない場合には格納されない
  • emacsclient の実装で、新規フレームを作成しない場合には環境変数をserverに伝達しないようになっていたので emacs-devel で聞いた
  • Eli曰く、環境変数をserverに伝達しているのは新規フレームにそれを伝達するため。server-buffer-clients のプロパティに環境変数を保持してることを利用するのは非推奨

ということで修正は断念しました🥺

Discussion