]> git.ipfire.org Git - thirdparty/git.git/blob - builtin/patch-id.c
cache.h: remove this no-longer-used header
[thirdparty/git.git] / builtin / patch-id.c
1 #include "builtin.h"
2 #include "config.h"
3 #include "diff.h"
4 #include "gettext.h"
5 #include "hex.h"
6 #include "parse-options.h"
7
8 static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
9 {
10 if (patchlen)
11 printf("%s %s\n", oid_to_hex(result), oid_to_hex(id));
12 }
13
14 static int remove_space(char *line)
15 {
16 char *src = line;
17 char *dst = line;
18 unsigned char c;
19
20 while ((c = *src++) != '\0') {
21 if (!isspace(c))
22 *dst++ = c;
23 }
24 return dst - line;
25 }
26
27 static int scan_hunk_header(const char *p, int *p_before, int *p_after)
28 {
29 static const char digits[] = "0123456789";
30 const char *q, *r;
31 int n;
32
33 q = p + 4;
34 n = strspn(q, digits);
35 if (q[n] == ',') {
36 q += n + 1;
37 *p_before = atoi(q);
38 n = strspn(q, digits);
39 } else {
40 *p_before = 1;
41 }
42
43 if (n == 0 || q[n] != ' ' || q[n+1] != '+')
44 return 0;
45
46 r = q + n + 2;
47 n = strspn(r, digits);
48 if (r[n] == ',') {
49 r += n + 1;
50 *p_after = atoi(r);
51 n = strspn(r, digits);
52 } else {
53 *p_after = 1;
54 }
55 if (n == 0)
56 return 0;
57
58 return 1;
59 }
60
61 static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
62 struct strbuf *line_buf, int stable, int verbatim)
63 {
64 int patchlen = 0, found_next = 0;
65 int before = -1, after = -1;
66 int diff_is_binary = 0;
67 char pre_oid_str[GIT_MAX_HEXSZ + 1], post_oid_str[GIT_MAX_HEXSZ + 1];
68 git_hash_ctx ctx;
69
70 the_hash_algo->init_fn(&ctx);
71 oidclr(result);
72
73 while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
74 char *line = line_buf->buf;
75 const char *p = line;
76 int len;
77
78 /* Possibly skip over the prefix added by "log" or "format-patch" */
79 if (!skip_prefix(line, "commit ", &p) &&
80 !skip_prefix(line, "From ", &p) &&
81 starts_with(line, "\\ ") && 12 < strlen(line)) {
82 if (verbatim)
83 the_hash_algo->update_fn(&ctx, line, strlen(line));
84 continue;
85 }
86
87 if (!get_oid_hex(p, next_oid)) {
88 found_next = 1;
89 break;
90 }
91
92 /* Ignore commit comments */
93 if (!patchlen && !starts_with(line, "diff "))
94 continue;
95
96 /* Parsing diff header? */
97 if (before == -1) {
98 if (starts_with(line, "GIT binary patch") ||
99 starts_with(line, "Binary files")) {
100 diff_is_binary = 1;
101 before = 0;
102 the_hash_algo->update_fn(&ctx, pre_oid_str,
103 strlen(pre_oid_str));
104 the_hash_algo->update_fn(&ctx, post_oid_str,
105 strlen(post_oid_str));
106 if (stable)
107 flush_one_hunk(result, &ctx);
108 continue;
109 } else if (skip_prefix(line, "index ", &p)) {
110 char *oid1_end = strstr(line, "..");
111 char *oid2_end = NULL;
112 if (oid1_end)
113 oid2_end = strstr(oid1_end, " ");
114 if (!oid2_end)
115 oid2_end = line + strlen(line) - 1;
116 if (oid1_end != NULL && oid2_end != NULL) {
117 *oid1_end = *oid2_end = '\0';
118 strlcpy(pre_oid_str, p, GIT_MAX_HEXSZ + 1);
119 strlcpy(post_oid_str, oid1_end + 2, GIT_MAX_HEXSZ + 1);
120 }
121 continue;
122 } else if (starts_with(line, "--- "))
123 before = after = 1;
124 else if (!isalpha(line[0]))
125 break;
126 }
127
128 if (diff_is_binary) {
129 if (starts_with(line, "diff ")) {
130 diff_is_binary = 0;
131 before = -1;
132 }
133 continue;
134 }
135
136 /* Looking for a valid hunk header? */
137 if (before == 0 && after == 0) {
138 if (starts_with(line, "@@ -")) {
139 /* Parse next hunk, but ignore line numbers. */
140 scan_hunk_header(line, &before, &after);
141 continue;
142 }
143
144 /* Split at the end of the patch. */
145 if (!starts_with(line, "diff "))
146 break;
147
148 /* Else we're parsing another header. */
149 if (stable)
150 flush_one_hunk(result, &ctx);
151 before = after = -1;
152 }
153
154 /* If we get here, we're inside a hunk. */
155 if (line[0] == '-' || line[0] == ' ')
156 before--;
157 if (line[0] == '+' || line[0] == ' ')
158 after--;
159
160 /* Add line to hash algo (possibly removing whitespace) */
161 len = verbatim ? strlen(line) : remove_space(line);
162 patchlen += len;
163 the_hash_algo->update_fn(&ctx, line, len);
164 }
165
166 if (!found_next)
167 oidclr(next_oid);
168
169 flush_one_hunk(result, &ctx);
170
171 return patchlen;
172 }
173
174 static void generate_id_list(int stable, int verbatim)
175 {
176 struct object_id oid, n, result;
177 int patchlen;
178 struct strbuf line_buf = STRBUF_INIT;
179
180 oidclr(&oid);
181 while (!feof(stdin)) {
182 patchlen = get_one_patchid(&n, &result, &line_buf, stable, verbatim);
183 flush_current_id(patchlen, &oid, &result);
184 oidcpy(&oid, &n);
185 }
186 strbuf_release(&line_buf);
187 }
188
189 static const char *const patch_id_usage[] = {
190 N_("git patch-id [--stable | --unstable | --verbatim]"), NULL
191 };
192
193 struct patch_id_opts {
194 int stable;
195 int verbatim;
196 };
197
198 static int git_patch_id_config(const char *var, const char *value, void *cb)
199 {
200 struct patch_id_opts *opts = cb;
201
202 if (!strcmp(var, "patchid.stable")) {
203 opts->stable = git_config_bool(var, value);
204 return 0;
205 }
206 if (!strcmp(var, "patchid.verbatim")) {
207 opts->verbatim = git_config_bool(var, value);
208 return 0;
209 }
210
211 return git_default_config(var, value, cb);
212 }
213
214 int cmd_patch_id(int argc, const char **argv, const char *prefix)
215 {
216 /* if nothing is set, default to unstable */
217 struct patch_id_opts config = {0, 0};
218 int opts = 0;
219 struct option builtin_patch_id_options[] = {
220 OPT_CMDMODE(0, "unstable", &opts,
221 N_("use the unstable patch-id algorithm"), 1),
222 OPT_CMDMODE(0, "stable", &opts,
223 N_("use the stable patch-id algorithm"), 2),
224 OPT_CMDMODE(0, "verbatim", &opts,
225 N_("don't strip whitespace from the patch"), 3),
226 OPT_END()
227 };
228
229 git_config(git_patch_id_config, &config);
230
231 /* verbatim implies stable */
232 if (config.verbatim)
233 config.stable = 1;
234
235 argc = parse_options(argc, argv, prefix, builtin_patch_id_options,
236 patch_id_usage, 0);
237
238 generate_id_list(opts ? opts > 1 : config.stable,
239 opts ? opts == 3 : config.verbatim);
240 return 0;
241 }