builtin / patch-id.con commit Merge branch 'cb/t4201-robustify' (4fff9c7)
   1#include "builtin.h"
   2#include "config.h"
   3
   4static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
   5{
   6        char name[50];
   7
   8        if (!patchlen)
   9                return;
  10
  11        memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
  12        printf("%s %s\n", oid_to_hex(result), name);
  13}
  14
  15static int remove_space(char *line)
  16{
  17        char *src = line;
  18        char *dst = line;
  19        unsigned char c;
  20
  21        while ((c = *src++) != '\0') {
  22                if (!isspace(c))
  23                        *dst++ = c;
  24        }
  25        return dst - line;
  26}
  27
  28static int scan_hunk_header(const char *p, int *p_before, int *p_after)
  29{
  30        static const char digits[] = "0123456789";
  31        const char *q, *r;
  32        int n;
  33
  34        q = p + 4;
  35        n = strspn(q, digits);
  36        if (q[n] == ',') {
  37                q += n + 1;
  38                n = strspn(q, digits);
  39        }
  40        if (n == 0 || q[n] != ' ' || q[n+1] != '+')
  41                return 0;
  42
  43        r = q + n + 2;
  44        n = strspn(r, digits);
  45        if (r[n] == ',') {
  46                r += n + 1;
  47                n = strspn(r, digits);
  48        }
  49        if (n == 0)
  50                return 0;
  51
  52        *p_before = atoi(q);
  53        *p_after = atoi(r);
  54        return 1;
  55}
  56
  57static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
  58{
  59        unsigned char hash[GIT_MAX_RAWSZ];
  60        unsigned short carry = 0;
  61        int i;
  62
  63        git_SHA1_Final(hash, ctx);
  64        git_SHA1_Init(ctx);
  65        /* 20-byte sum, with carry */
  66        for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
  67                carry += result->hash[i] + hash[i];
  68                result->hash[i] = carry;
  69                carry >>= 8;
  70        }
  71}
  72
  73static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
  74                           struct strbuf *line_buf, int stable)
  75{
  76        int patchlen = 0, found_next = 0;
  77        int before = -1, after = -1;
  78        git_SHA_CTX ctx;
  79
  80        git_SHA1_Init(&ctx);
  81        oidclr(result);
  82
  83        while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
  84                char *line = line_buf->buf;
  85                const char *p = line;
  86                int len;
  87
  88                if (!skip_prefix(line, "diff-tree ", &p) &&
  89                    !skip_prefix(line, "commit ", &p) &&
  90                    !skip_prefix(line, "From ", &p) &&
  91                    starts_with(line, "\\ ") && 12 < strlen(line))
  92                        continue;
  93
  94                if (!get_oid_hex(p, next_oid)) {
  95                        found_next = 1;
  96                        break;
  97                }
  98
  99                /* Ignore commit comments */
 100                if (!patchlen && !starts_with(line, "diff "))
 101                        continue;
 102
 103                /* Parsing diff header?  */
 104                if (before == -1) {
 105                        if (starts_with(line, "index "))
 106                                continue;
 107                        else if (starts_with(line, "--- "))
 108                                before = after = 1;
 109                        else if (!isalpha(line[0]))
 110                                break;
 111                }
 112
 113                /* Looking for a valid hunk header?  */
 114                if (before == 0 && after == 0) {
 115                        if (starts_with(line, "@@ -")) {
 116                                /* Parse next hunk, but ignore line numbers.  */
 117                                scan_hunk_header(line, &before, &after);
 118                                continue;
 119                        }
 120
 121                        /* Split at the end of the patch.  */
 122                        if (!starts_with(line, "diff "))
 123                                break;
 124
 125                        /* Else we're parsing another header.  */
 126                        if (stable)
 127                                flush_one_hunk(result, &ctx);
 128                        before = after = -1;
 129                }
 130
 131                /* If we get here, we're inside a hunk.  */
 132                if (line[0] == '-' || line[0] == ' ')
 133                        before--;
 134                if (line[0] == '+' || line[0] == ' ')
 135                        after--;
 136
 137                /* Compute the sha without whitespace */
 138                len = remove_space(line);
 139                patchlen += len;
 140                git_SHA1_Update(&ctx, line, len);
 141        }
 142
 143        if (!found_next)
 144                oidclr(next_oid);
 145
 146        flush_one_hunk(result, &ctx);
 147
 148        return patchlen;
 149}
 150
 151static void generate_id_list(int stable)
 152{
 153        struct object_id oid, n, result;
 154        int patchlen;
 155        struct strbuf line_buf = STRBUF_INIT;
 156
 157        oidclr(&oid);
 158        while (!feof(stdin)) {
 159                patchlen = get_one_patchid(&n, &result, &line_buf, stable);
 160                flush_current_id(patchlen, &oid, &result);
 161                oidcpy(&oid, &n);
 162        }
 163        strbuf_release(&line_buf);
 164}
 165
 166static const char patch_id_usage[] = "git patch-id [--stable | --unstable]";
 167
 168static int git_patch_id_config(const char *var, const char *value, void *cb)
 169{
 170        int *stable = cb;
 171
 172        if (!strcmp(var, "patchid.stable")) {
 173                *stable = git_config_bool(var, value);
 174                return 0;
 175        }
 176
 177        return git_default_config(var, value, cb);
 178}
 179
 180int cmd_patch_id(int argc, const char **argv, const char *prefix)
 181{
 182        int stable = -1;
 183
 184        git_config(git_patch_id_config, &stable);
 185
 186        /* If nothing is set, default to unstable. */
 187        if (stable < 0)
 188                stable = 0;
 189
 190        if (argc == 2 && !strcmp(argv[1], "--stable"))
 191                stable = 1;
 192        else if (argc == 2 && !strcmp(argv[1], "--unstable"))
 193                stable = 0;
 194        else if (argc != 1)
 195                usage(patch_id_usage);
 196
 197        generate_id_list(stable);
 198        return 0;
 199}