git add fileの挙動を追ってみた。

git (v1.8.4)でのgit add fileの挙動を追ってみました。

方法

git init
echo hello > hello.txt
としておいてから、Emacsのgdbで処理を追いかけました。
(gdb) run add hello.txt
最初 add.cファイルのcmd_add関数にブレークポイントをしかけて読んでいたのですが、cmd_add関数のコードが多すぎて何がなにやらさっぱり解読できず途方にくれました。

https://github.com/git/git/blob/v1.8.4/builtin/add.c#L445

そこで頭を切り替えて別のアプローチをとりました。
「addしたら結局はzlibで圧縮されるわけだから、deflateまわりのコードにブレークポイントをしかければ引っかかるんじゃないか」と考え、 git grep deflateで検索したらgit_deflate_initというそれっぽい関数が見つかったのでそこにブレークポイントを仕掛けてみました。
break zlib.c:160
そしたらビンゴ!
このgit_deflate_initは addするときのzlib圧縮処理の一部であることがわかりました。

git add fileのスタックトレース

スタックトレースはこんな感じです。
#0  git_deflate_init (strm=0x7fffffffc8a0, level=1) at zlib.c:163
#1  write_loose_object (sha1=0x7fffffffca30 "\316\001\066%\003\v\250\333\251\006\367V\226\177\236\234\243\224FJ", hdr=0x7fffffffca10 "blob 6", hdrlen=7, buf\
=0x7d3af0, len=6, mtime=0) at sha1_file.c:2892
#2  write_sha1_file (buf=0x7d3af0, len=6, type=<value optimized out>, returnsha1=0x7d3550 "\316\001\066%\003\v\250\333\251\006\367V\226\177\236\234\243\224F\
J") at sha1_file.c:2954
#3  index_mem (sha1=0x7d3550 "\316\001\066%\003\v\250\333\251\006\367V\226\177\236\234\243\224FJ", buf=0x7d3af0, size=6, type=<value optimized out>, path=<v\
alue optimized out>, flags=<value optimized out>) at sha1_file.c:3060
#4  index_core (sha1=0x7d3550 "\316\001\066%\003\v\250\333\251\006\367V\226\177\236\234\243\224FJ", fd=7, st=<value optimized out>, type=OBJ_BLOB, path=0x7d\
44b4 "hello.txt", flags=1) at sha1_file.c:3095
#5  index_fd (sha1=0x7d3550 "\316\001\066%\003\v\250\333\251\006\367V\226\177\236\234\243\224FJ", fd=7, st=<value optimized out>, type=OBJ_BLOB, path=0x7d44\
b4 "hello.txt", flags=1) at sha1_file.c:3139
#6  index_path (sha1=0x7d3550 "\316\001\066%\003\v\250\333\251\006\367V\226\177\236\234\243\224FJ", path=0x7d44b4 "hello.txt", st=<value optimized out>, fla\
gs=1) at sha1_file.c:3157
#7  add_to_index (istate=0x7d19e0, path=0x7d44b4 "hello.txt", st=0x7fffffffcd40, flags=8) at read-cache.c:665
#8  add_file_to_index (istate=0x7d19e0, path=0x7d44b4 "hello.txt", flags=8) at read-cache.c:694
#9  add_files (argc=<value optimized out>, argv=<value optimized out>, prefix=0x0) at builtin/add.c:397
#10 cmd_add (argc=<value optimized out>, argv=<value optimized out>, prefix=0x0) at builtin/add.c:581
#11 run_builtin (argc=2, argv=0x7fffffffe920) at git.c:303
#12 handle_internal_command (argc=2, argv=0x7fffffffe920) at git.c:466
#13 run_argv (argc=2, av=<value optimized out>) at git.c:512
#14 main (argc=2, av=<value optimized out>) at git.c:595

簡単に解説

簡単に解説すると、zlib圧縮して保存する処理はsha_fil1.cのwrite_loose_object関数をみればだいたいわかります。

  • 一時ファイルを作る
  • オブジェクトのヘッダをzlib圧縮
  • オブジェクトのデータ部をzlib圧縮
  • 一時ファイルに書き込む
  • 完了したら一時ファイルを.git/objects/xx/xxxxxx にリネーム

という感じですね。
static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
			      const void *buf, unsigned long len, time_t mtime)
{
	int fd, ret;
	unsigned char compressed[4096];
	git_zstream stream;
	git_SHA_CTX c;
	unsigned char parano_sha1[20];
	char *filename;
	static char tmp_file[PATH_MAX];

	filename = sha1_file_name(sha1);
	fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
	if (fd < 0) {
		if (errno == EACCES)
			return error("insufficient permission for adding an object to repository database %s", get_object_directory());
		else
			return error("unable to create temporary file: %s", strerror(errno));
	}

	/* Set it up */
	memset(&stream, 0, sizeof(stream));
	git_deflate_init(&stream, zlib_compression_level);
	stream.next_out = compressed;
	stream.avail_out = sizeof(compressed);
	git_SHA1_Init(&c);

	/* First header.. */
	stream.next_in = (unsigned char *)hdr;
	stream.avail_in = hdrlen;
	while (git_deflate(&stream, 0) == Z_OK)
		; /* nothing */
	git_SHA1_Update(&c, hdr, hdrlen);

	/* Then the data itself.. */
	stream.next_in = (void *)buf;
	stream.avail_in = len;
	do {
		unsigned char *in0 = stream.next_in;
		ret = git_deflate(&stream, Z_FINISH);
		git_SHA1_Update(&c, in0, stream.next_in - in0);
		if (write_buffer(fd, compressed, stream.next_out - compressed) < 0)
			die("unable to write sha1 file");
		stream.next_out = compressed;
		stream.avail_out = sizeof(compressed);
	} while (ret == Z_OK);

	if (ret != Z_STREAM_END)
		die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
	ret = git_deflate_end_gently(&stream);
	if (ret != Z_OK)
		die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
	git_SHA1_Final(parano_sha1, &c);
	if (hashcmp(sha1, parano_sha1) != 0)
		die("confused by unstable object source data for %s", sha1_to_hex(sha1));

	close_sha1_file(fd);

	if (mtime) {
		struct utimbuf utb;
		utb.actime = mtime;
		utb.modtime = mtime;
		if (utime(tmp_file, &utb) < 0)
			warning("failed utime() on %s: %s",
				tmp_file, strerror(errno));
	}

	return move_temp_to_file(tmp_file, filename);
}
カテゴリ:

人気記事