说明
本文所有代码基于 GIT COMMIT e83c5163316f89bfbde7d9ab23ca2e25604af290 版本的源码,在 Ubuntu 16.04 上编译运行,部分代码有所改动。
update-cache
update-cache 是 GIT 最初版本中用于向缓存目录增加追踪文件的命令,该命令不能用于将目录添加至缓存,其使用方法为 update-cache <filename ...>
。主函数如下所示:
int main(int argc, char **argv)
{
int i, newfd, entries;
entries = read_cache();
if (entries < 0) {
perror("cache corrupted");
return -1;
}
newfd = open(".dircache/index.lock", O_RDWR | O_CREAT | O_EXCL, 0600);
if (newfd < 0) {
perror("unable to create new cachefile");
return -1;
}
for (i = 1; i < argc; i++) {
char *path = argv[i];
if (!verify_path(path)) {
fprintf(stderr, "Ignoring path %s\n", argv[i]);
continue;
}
if (add_file_to_cache(path)) {
fprintf(stderr, "Unable to add %s to database\n", path);
goto out;
}
}
if (!write_cache(newfd, active_cache, active_nr) && !rename(".dircache/index.lock", ".dircache/index"))
return 0;
out:
unlink(".dircache/index.lock");
}
程序运行时,首先会通过 read_cache()
函数加载存储在 .dircache/index 中的文件信息。接着程序会在 .dircache 目录下新建 index.lock 文件用于保存当前文件的缓存信息。然后通过 add_file_to_cache()
函数将命令行中指定的文件添加到缓存对象中。最后通过 write_cache()
函数将缓存信息写入到 .dircache/index.lock 文件中,并重命名为 .dircache/index 完成添加文件到缓存中的功能。
接下来我们看看 read_cache()
函数,该函数负责将存储在 .dircache/index 中的文件信息载入内存并保存至全局变量 active_cache
数组中。因此,我们需要先了解 .dircache/index 的文件结果。 .dircache/index 在文件头部会存放文件实例的信息 cache_header
,其定义如下:
struct cache_header {
unsigned int signature;
unsigned int version;
unsigned int entries;
unsigned char sha1[20];
};
- signature 签名标识,固定为 0x44495243 ,即 DIRC 的十六进制形式。
- version 版本标识。
- entries 文件总个数。
- sha1 校验信息,index 文件中去除 sha1 自身的所有数据的 SHA1 值。
read_cache()
函数源码如下所示:
int read_cache(void)
{
int fd, i;
struct stat st;
unsigned long size, offset;
void *map;
struct cache_header *hdr;
errno = EBUSY; /* 有什么用?*/
if (active_cache)
return error("more than on cachefile");
errno = ENOENT;
sha1_file_directory = getenv(DB_ENVIRONMENT);
if (!sha1_file_directory)
sha1_file_directory = DEFAULT_DB_ENVIRONMENT;
if (access(sha1_file_directory, X_OK) < 0)
return error("no access to SHA1 file directory");
fd = open(".dircache/index", O_RDONLY);
if (fd < 0)
return (errno == ENOENT) ? 0 : error("open failed");
map = (void *)-1;
if (!fstat(fd, &st)) {
map = NULL;
size = st.st_size;
errno = EINVAL;
if (size > sizeof(struct cache_header))
map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
}
close(fd);
if (-1 == (int)(long)map)
return error("mmap failed");
hdr = map;
if (verify_hdr(hdr, size) < 0)
goto unmap;
active_nr = hdr->entries;
active_alloc = alloc_nr(active_nr);
active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
offset = sizeof(*hdr);
for (i = 0; i < hdr->entries; i++) {
struct cache_entry *ce = map + offset;
offset = offset + ce_size(ce);
active_cache[i] = ce;
}
return active_nr;
unmap:
munmap(map, size);
errno = EINVAL;
return error("verify header failed");
}
该函数中需要设置 errno 的值,关于这些值有什么作用我个人并不是很清楚。 read_cache()
函数首先会检测是否已经载入过 .dircache/index 到 active_cache
中,同时需要判断能否访问 .dircache/objects 目录。接着,该函数通过 mmap()
函数将 .dircache/index 映射到内存中并通过 verify_hdr()
校验信息的正确性。最后将 .dircache/index 文件实例依次载入到 active_cache
数组中。
add_file_to_cache()
函数负责将文件添加到缓存 active_cache
数组中,其函数定义如下:
static int add_file_to_cache(char *path)
{
int size, namelen;
struct cache_entry *ce;
struct stat st;
int fd;
fd = open(path, O_RDONLY);
if (fd < 0) {
if (errno == ENOENT)
return remove_file_from_cache(path);
return -1;
}
if (fstat(fd, &st) < 0) {
close(fd);
return -1;
}
namelen = strlen(path);
size = cache_entry_size(namelen);
ce = malloc(size);
memset(ce, 0, size);
memcpy(ce->name, path, namelen);
ce->ctime.sec = st.st_ctime;
ce->ctime.nsec = st.st_ctim.tv_nsec;
ce->mtime.sec = st.st_mtime;
ce->mtime.nsec = st.st_mtim.tv_nsec;
ce->st_dev = st.st_dev;
ce->st_ino = st.st_ino;
ce->st_mode = st.st_mode;
ce->st_uid = st.st_uid;
ce->st_gid = st.st_gid;
ce->st_size = st.st_size;
ce->namelen = namelen;
if (index_fd(path, namelen, ce, fd, &st) < 0)
return -1;
return add_cache_entry(ce);
}
该函数接收一个文件路径参数,若该文件不存在,它会试图将 active_cache
中相同路径的文件信息移除。否则它将创建并填充 cache_entry
结构,同时将文件进行压缩并存储到压缩后的 SHA1 值对应的文件中(备注:在压缩前加入了 blob 字符串,size 为文件大小)。最后,通过将新建的 cache_entry
信息加入到 active_cache
中(根据路径名进行快速排序)。
write_cache()
函数负责将内存中 active_cache
的内容写入到磁盘文件中,其定义如下:
static int write_cache(int newfd, struct cache_entry **cache, int entries)
{
SHA_CTX c;
struct cache_header hdr;
int i;
hdr.signature = CACHE_SIGNATURE;
hdr.version = 1;
hdr.entries = entries;
SHA1_Init(&c);
SHA1_Update(&c, &hdr, offsetof(struct cache_header, sha1));
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
int size = ce_size(ce);
SHA1_Update(&c, ce, size);
}
SHA1_Final(hdr.sha1, &c);
if (write(newfd, &hdr, sizeof(hdr)) != sizeof(hdr))
return -1;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
int size = ce_size(ce);
if (write(newfd, ce, size) != size)
return -1;
}
return 0;
}
该函数对 cache_header
中除 sha1
字段以及 active_cache
所有的 cache_entry
计算 SHA1 值并保存至 cache_header
的 sha1
字段中。接着将头信息写入文件,最后遍历 active_cache
并写入所有的 cache_entry
信息。