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