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