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