Merge branch 'ds/multi-pack-verify'
[git] / builtin / patch-id.c
1 #include "builtin.h"
2 #include "config.h"
3
4 static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
5 {
6         char name[50];
7
8         if (!patchlen)
9                 return;
10
11         memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
12         printf("%s %s\n", oid_to_hex(result), name);
13 }
14
15 static int remove_space(char *line)
16 {
17         char *src = line;
18         char *dst = line;
19         unsigned char c;
20
21         while ((c = *src++) != '\0') {
22                 if (!isspace(c))
23                         *dst++ = c;
24         }
25         return dst - line;
26 }
27
28 static int scan_hunk_header(const char *p, int *p_before, int *p_after)
29 {
30         static const char digits[] = "0123456789";
31         const char *q, *r;
32         int n;
33
34         q = p + 4;
35         n = strspn(q, digits);
36         if (q[n] == ',') {
37                 q += n + 1;
38                 n = strspn(q, digits);
39         }
40         if (n == 0 || q[n] != ' ' || q[n+1] != '+')
41                 return 0;
42
43         r = q + n + 2;
44         n = strspn(r, digits);
45         if (r[n] == ',') {
46                 r += n + 1;
47                 n = strspn(r, digits);
48         }
49         if (n == 0)
50                 return 0;
51
52         *p_before = atoi(q);
53         *p_after = atoi(r);
54         return 1;
55 }
56
57 static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
58 {
59         unsigned char hash[GIT_MAX_RAWSZ];
60         unsigned short carry = 0;
61         int i;
62
63         git_SHA1_Final(hash, ctx);
64         git_SHA1_Init(ctx);
65         /* 20-byte sum, with carry */
66         for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
67                 carry += result->hash[i] + hash[i];
68                 result->hash[i] = carry;
69                 carry >>= 8;
70         }
71 }
72
73 static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
74                            struct strbuf *line_buf, int stable)
75 {
76         int patchlen = 0, found_next = 0;
77         int before = -1, after = -1;
78         git_SHA_CTX ctx;
79
80         git_SHA1_Init(&ctx);
81         oidclr(result);
82
83         while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
84                 char *line = line_buf->buf;
85                 const char *p = line;
86                 int len;
87
88                 if (!skip_prefix(line, "diff-tree ", &p) &&
89                     !skip_prefix(line, "commit ", &p) &&
90                     !skip_prefix(line, "From ", &p) &&
91                     starts_with(line, "\\ ") && 12 < strlen(line))
92                         continue;
93
94                 if (!get_oid_hex(p, next_oid)) {
95                         found_next = 1;
96                         break;
97                 }
98
99                 /* Ignore commit comments */
100                 if (!patchlen && !starts_with(line, "diff "))
101                         continue;
102
103                 /* Parsing diff header?  */
104                 if (before == -1) {
105                         if (starts_with(line, "index "))
106                                 continue;
107                         else if (starts_with(line, "--- "))
108                                 before = after = 1;
109                         else if (!isalpha(line[0]))
110                                 break;
111                 }
112
113                 /* Looking for a valid hunk header?  */
114                 if (before == 0 && after == 0) {
115                         if (starts_with(line, "@@ -")) {
116                                 /* Parse next hunk, but ignore line numbers.  */
117                                 scan_hunk_header(line, &before, &after);
118                                 continue;
119                         }
120
121                         /* Split at the end of the patch.  */
122                         if (!starts_with(line, "diff "))
123                                 break;
124
125                         /* Else we're parsing another header.  */
126                         if (stable)
127                                 flush_one_hunk(result, &ctx);
128                         before = after = -1;
129                 }
130
131                 /* If we get here, we're inside a hunk.  */
132                 if (line[0] == '-' || line[0] == ' ')
133                         before--;
134                 if (line[0] == '+' || line[0] == ' ')
135                         after--;
136
137                 /* Compute the sha without whitespace */
138                 len = remove_space(line);
139                 patchlen += len;
140                 git_SHA1_Update(&ctx, line, len);
141         }
142
143         if (!found_next)
144                 oidclr(next_oid);
145
146         flush_one_hunk(result, &ctx);
147
148         return patchlen;
149 }
150
151 static void generate_id_list(int stable)
152 {
153         struct object_id oid, n, result;
154         int patchlen;
155         struct strbuf line_buf = STRBUF_INIT;
156
157         oidclr(&oid);
158         while (!feof(stdin)) {
159                 patchlen = get_one_patchid(&n, &result, &line_buf, stable);
160                 flush_current_id(patchlen, &oid, &result);
161                 oidcpy(&oid, &n);
162         }
163         strbuf_release(&line_buf);
164 }
165
166 static const char patch_id_usage[] = "git patch-id [--stable | --unstable]";
167
168 static int git_patch_id_config(const char *var, const char *value, void *cb)
169 {
170         int *stable = cb;
171
172         if (!strcmp(var, "patchid.stable")) {
173                 *stable = git_config_bool(var, value);
174                 return 0;
175         }
176
177         return git_default_config(var, value, cb);
178 }
179
180 int cmd_patch_id(int argc, const char **argv, const char *prefix)
181 {
182         int stable = -1;
183
184         git_config(git_patch_id_config, &stable);
185
186         /* If nothing is set, default to unstable. */
187         if (stable < 0)
188                 stable = 0;
189
190         if (argc == 2 && !strcmp(argv[1], "--stable"))
191                 stable = 1;
192         else if (argc == 2 && !strcmp(argv[1], "--unstable"))
193                 stable = 0;
194         else if (argc != 1)
195                 usage(patch_id_usage);
196
197         generate_id_list(stable);
198         return 0;
199 }