git的show-diff
命令执行入口函数main
(show-diff.c):
50 int main(int argc, char **argv)
51 {
52 int entries = read_cache();
53 int i;
54
55 if (entries < 0) {
56 perror("read_cache");
57 exit(1);
58 }
59 for (i = 0; i < entries; i++) {
60 struct stat st;
61 struct cache_entry *ce = active_cache[i];
62 int n, changed;
63 unsigned int mode;
64 unsigned long size;
65 char type[20];
66 void *new;
67
68 if (stat(ce->name, &st) < 0) {
69 printf("%s: %s\n", ce->name, strerror(errno));
70 continue;
71 }
72 changed = match_stat(ce, &st);
73 if (!changed) {
74 printf("%s: ok\n", ce->name);
75 continue;
76 }
77 printf("%.*s: ", ce->namelen, ce->name);
78 for (n = 0; n < 20; n++)
79 printf("%02x", ce->sha1[n]);
80 printf("\n");
81 new = read_sha1_file(ce->sha1, type, &size);
82 show_differences(ce, &st, new, size);
83 free(new);
84 }
85 return 0;
86 }
git的show-diff命令比较的是同一文件在工作目录区(Working Directory
)相对暂存区(cache
)的变化.Line52:58获得cache信息,如果cache为空,视为错误,直接返回.Line59:84遍历cache区中的每个cache_entry
对象,进行比较的逻辑.Line68通过系统调用stat
获得工作目录区该文件的状态.Line72调用函数match_stat
比较该文件在工作目录区和cache区中的文件元数据信息是否发生改变.该函数的实现为(show-diff.c):
8 #define MTIME_CHANGED 0x0001
9 #define CTIME_CHANGED 0x0002
10 #define OWNER_CHANGED 0x0004
11 #define MODE_CHANGED 0x0008
12 #define INODE_CHANGED 0x0010
13 #define DATA_CHANGED 0x0020
14
15 static int match_stat(struct cache_entry *ce, struct stat *st)
16 {
17 unsigned int changed = 0;
18
19 if (ce->mtime.sec != (unsigned int)st->st_mtim.tv_sec ||
20 ce->mtime.nsec != (unsigned int)st->st_mtim.tv_nsec)
21 changed |= MTIME_CHANGED;
22 if (ce->ctime.sec != (unsigned int)st->st_ctim.tv_sec ||
23 ce->ctime.nsec != (unsigned int)st->st_ctim.tv_nsec)
24 changed |= CTIME_CHANGED;
25 if (ce->st_uid != (unsigned int)st->st_uid ||
26 ce->st_gid != (unsigned int)st->st_gid)
27 changed |= OWNER_CHANGED;
28 if (ce->st_mode != (unsigned int)st->st_mode)
29 changed |= MODE_CHANGED;
30 if (ce->st_dev != (unsigned int)st->st_dev ||
31 ce->st_ino != (unsigned int)st->st_ino)
32 changed |= INODE_CHANGED;
33 if (ce->st_size != (unsigned int)st->st_size)
34 changed |= DATA_CHANGED;
35 return changed;
36 }
如果文件发生改变,返回非0,如果未改变,返回0.
回到函数main
, Line73:76如果未发生改变,则遍历下一个cache_entry
对象,进行比较.如果发生改变,Line81调用函数read_sha1_file
获得该文件在cache中的文件内容和长度.该函数的实现为(read-cache.c):
87 void * read_sha1_file(unsigned char *sha1, char *type, unsigned long *size)
88 {
89 z_stream stream;
90 char buffer[8192];
91 struct stat st;
92 int i, fd, ret, bytes;
93 void *map, *buf;
94 char *filename = sha1_file_name(sha1);
95
96 fd = open(filename, O_RDONLY);
97 if (fd < 0) {
98 perror(filename);
99 return NULL;
100 }
101 if (fstat(fd, &st) < 0) {
102 close(fd);
103 return NULL;
104 }
105 map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
106 close(fd);
107 if (-1 == (int)(long)map)
108 return NULL;
109
110 /* Get the data stream */
111 memset(&stream, 0, sizeof(stream));
112 stream.next_in = map;
113 stream.avail_in = st.st_size;
114 stream.next_out = buffer;
115 stream.avail_out = sizeof(buffer);
116
117 inflateInit(&stream);
118 ret = inflate(&stream, 0);
119 if (sscanf(buffer, "%10s %lu", type, size) != 2)
120 return NULL;
121 bytes = strlen(buffer) + 1;
122 buf = malloc(*size);
123 if (!buf)
124 return NULL;
125
126 memcpy(buf, buffer + bytes, stream.total_out - bytes);
127 bytes = stream.total_out - bytes;
128 if (bytes < *size && ret == Z_OK) {
129 stream.next_out = buf + bytes;
130 stream.avail_out = *size - bytes;
131 while (inflate(&stream, Z_FINISH) == Z_OK)
132 /* nothing */;
133 }
134 inflateEnd(&stream);
135 return buf;
136 }
Line94根据sha1获得对应的文件名,Line96:108打开文件,通过系统调用fstat
获得文件长度,并通过系统调用mmap
将文件内容映射到内存,然后关闭文件.cache中的文件对象都是经过压缩的,所以Line110:118解压缩8192个字节,这样先把文件头部的元数据读取出来.Line119:124通过库函数sscanf
从解压缩后的文件头中解析中类型字符串(这里为blob
)和文件长度信息.分配文件长度大小的动态内存buf
.Line126把已经解压缩的数据拷贝到buf.Line127:133如果还没有解压缩完毕,继续解压缩剩余的文件内容到buf,最后返回解压缩后的文件内容.
回到函数main
,Line82调用函数show_differences
比较工作区和暂存区该文件的差异,比较完毕后释放为解压缩cache文件动态分配的内存空间.函数show_differences
的实现为(show-diff.c):
38 static void show_differences(struct cache_entry *ce, struct stat *cur,
39 void *old_contents, unsigned long long old_size)
40 {
41 static char cmd[1000];
42 FILE *f;
43
44 snprintf(cmd, sizeof(cmd), "diff -u - %s", ce->name);
45 f = popen(cmd, "w");
46 fwrite(old_contents, old_size, 1, f);
47 pclose(f);
48 }
库函数popen
将启用一个shell
子进程,来执行diff
命令比较文件的差异.