一、git内部objects文件的格式
在执行git cat-file命令的时候,可以看到提示文件的类型可以有四种:blob、tree、commit和tag。其中的blob和tree是比较直观的概念,也是比较常用的概念,tag相对更加简单,而这个commit是通常被忽视的一种类型。但是这种类型恰好又是整个版本控制结构中最为关键和基础的一个概念。在git的说明中,对于这种类型文件的格式说明并不太多,所以还是简单看下commit类型文件的格式是怎样的的,它是如何处理扩展性、兼容性问题的(添加一个新的字段之后如何处理)?
tsecer@harry: git cat-file
usage: git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>
or: git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]
<type> can be one of: blob, tree, commit, tag
二、git内部objects(.git/objects/文件夹下文件)格式
从代码上看,它包含了cat-file中枚举的常见类型:
git-master\cache.h
/*
* Values in this enum (except those outside the 3 bit range) are part
* of pack file format. See Documentation/technical/pack-format.txt
* for more information.
*/
enum object_type {
OBJ_BAD = -1,
OBJ_NONE = 0,
OBJ_COMMIT = 1,
OBJ_TREE = 2,
OBJ_BLOB = 3,
OBJ_TAG = 4,
/* 5 for future expansion */
OBJ_OFS_DELTA = 6,
OBJ_REF_DELTA = 7,
OBJ_ANY,
OBJ_MAX
};
三、文件如何识别自己格式
也就是在文件的最开始以字符串的形式保存了文件类型。这里的loose是相对于pack格式,pack格式使用的不是字符串而是二进制格式。但是,也是在文件的最开始保存了文件的格式信息。
git-master\sha1-file.c
int unpack_loose_header(git_zstream *stream,
unsigned char *map, unsigned long mapsize,
void *buffer, unsigned long bufsiz)
{
int status = unpack_loose_short_header(stream, map, mapsize,
buffer, bufsiz);
if (status < Z_OK)
return status;
/* Make sure we have the terminating NUL */
if (!memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
return -1;
return 0;
}
从网上找到的展示文件内容的命令可以看到
tsecer@harry: python -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" < .git/objects/d4/bf73b80b80f1fa62b397066854ce812fe9b338
commit 184tree 2cbe7cd574af6dec02c3ac609cb8845746307f73
author tsecer <tsecer@harry> 1640590880 +0800
committer tsecer <tsecer@harry> 1640590880 +0800
tsecer add commit
tsecer@harry:
四、从字符串到枚举类型的转换
将字符串格式内容转换为内部枚举类型格式的代码为
git-master\object.c
static const char *object_type_strings[] = {
NULL, /* OBJ_NONE = 0 */
"commit", /* OBJ_COMMIT = 1 */
"tree", /* OBJ_TREE = 2 */
"blob", /* OBJ_BLOB = 3 */
"tag", /* OBJ_TAG = 4 */
};
const char *type_name(unsigned int type)
{
if (type >= ARRAY_SIZE(object_type_strings))
return NULL;
return object_type_strings[type];
}
int type_from_string_gently(const char *str, ssize_t len, int gentle)
{
int i;
if (len < 0)
len = strlen(str);
for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
if (!strncmp(str, object_type_strings[i], len) &&
object_type_strings[i][len] == '\0')
return i;
if (gentle)
return -1;
die(_("invalid object type \"%s\""), str);
}
五、commit文件的格式
从这种解析来看,它更像是一种json文本的描述格式。开始的tree属性是必须的,然后有可选的parent字段,之后可选的提交者信息,也主要是文本格式保存。
git-master\commit.c
int parse_commit_buffer(struct repository *r, struct commit *item, const void *buffer, unsigned long size, int check_graph)
{
……
if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) ||
bufptr[tree_entry_len] != '\n')
return error("bogus commit object %s", oid_to_hex(&item->object.oid));
……
//父节点可能有多个
while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) {
……
}
……
item->date = parse_commit_date(bufptr, tail);
……
}
static timestamp_t parse_commit_date(const char *buf, const char *tail)
{
const char *dateptr;
if (buf + 6 >= tail)
return 0;
if (memcmp(buf, "author", 6))
return 0;
while (buf < tail && *buf++ != '\n')
/* nada */;
if (buf + 9 >= tail)
return 0;
if (memcmp(buf, "committer", 9))
return 0;
while (buf < tail && *buf++ != '>')
/* nada */;
if (buf >= tail)
return 0;
dateptr = buf;
while (buf < tail && *buf++ != '\n')
/* nada */;
if (buf >= tail)
return 0;
/* dateptr < buf && buf[-1] == '\n', so parsing will stop at buf-1 */
return parse_timestamp(dateptr, NULL, 10);
}
六、顺便看下tree文件的格式
git-master\tree-walk.c文件描述了文件格式。可以看到,这里并没有保存文件的修改时间之类的参数,但是有文件是否可执行之类的mode字段。这也意味着,当使用git checkout文件的时候,文件的修改时间不会改变,使用make之类依赖文件时间戳的构件系统也不会导致依赖丢失。
static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size, struct strbuf *err)
{
const char *path;
unsigned int mode, len;
const unsigned hashsz = the_hash_algo->rawsz;
if (size < hashsz + 3 || buf[size - (hashsz + 1)]) {
strbuf_addstr(err, _("too-short tree object"));
return -1;
}
path = get_mode(buf, &mode);
if (!path) {
strbuf_addstr(err, _("malformed mode in tree entry"));
return -1;
}
if (!*path) {
strbuf_addstr(err, _("empty filename in tree entry"));
return -1;
}
len = strlen(path) + 1;
/* Initialize the descriptor entry */
desc->entry.path = path;
desc->entry.mode = canon_mode(mode);
desc->entry.pathlen = len - 1;
hashcpy(desc->entry.oid.hash, (const unsigned char *)path + len);
return 0;
}
七、git文件格式描述
网上关于该文件的描述,的确是linus的风格——更偏向使用文本格式描述。