| 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 | |
| 13 | static 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 | |
| 19 | static 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 | |
| 32 | static 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 | |
| 66 | static 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 | |
| 180 | static 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 | |
| 195 | static const char *const patch_id_usage[] = { |
| 196 | N_("git patch-id [--stable | --unstable | --verbatim]"), NULL |
| 197 | }; |
| 198 | |
| 199 | struct patch_id_opts { |
| 200 | int stable; |
| 201 | int verbatim; |
| 202 | }; |
| 203 | |
| 204 | static 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 | |
| 221 | int 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 | } |