Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/patch-id.c
Line
Count
Source (jump to first uncovered line)
1
#include "builtin.h"
2
#include "config.h"
3
#include "diff.h"
4
#include "gettext.h"
5
#include "hash.h"
6
#include "hex.h"
7
#include "parse-options.h"
8
#include "setup.h"
9
10
static void flush_current_id(struct object_id *id, struct object_id *result)
11
0
{
12
0
  printf("%s %s\n", oid_to_hex(result), oid_to_hex(id));
13
0
}
14
15
static int remove_space(char *line)
16
0
{
17
0
  char *src = line;
18
0
  char *dst = line;
19
0
  unsigned char c;
20
21
0
  while ((c = *src++) != '\0') {
22
0
    if (!isspace(c))
23
0
      *dst++ = c;
24
0
  }
25
0
  return dst - line;
26
0
}
27
28
static int scan_hunk_header(const char *p, int *p_before, int *p_after)
29
0
{
30
0
  static const char digits[] = "0123456789";
31
0
  const char *q, *r;
32
0
  int n;
33
34
0
  q = p + 4;
35
0
  n = strspn(q, digits);
36
0
  if (q[n] == ',') {
37
0
    q += n + 1;
38
0
    *p_before = atoi(q);
39
0
    n = strspn(q, digits);
40
0
  } else {
41
0
    *p_before = 1;
42
0
  }
43
44
0
  if (n == 0 || q[n] != ' ' || q[n+1] != '+')
45
0
    return 0;
46
47
0
  r = q + n + 2;
48
0
  n = strspn(r, digits);
49
0
  if (r[n] == ',') {
50
0
    r += n + 1;
51
0
    *p_after = atoi(r);
52
0
    n = strspn(r, digits);
53
0
  } else {
54
0
    *p_after = 1;
55
0
  }
56
0
  if (n == 0)
57
0
    return 0;
58
59
0
  return 1;
60
0
}
61
62
/*
63
 * flag bits to control get_one_patchid()'s behaviour.
64
 *
65
 * STABLE/VERBATIM are given from the command line option as
66
 * --stable/--verbatim.  FIND_HEADER conveys the internal state
67
 * maintained by the caller to allow the function to avoid mistaking
68
 * lines of log message before seeing the "diff" part as the beginning
69
 * of the next patch.
70
 */
71
enum {
72
  GOPID_STABLE = (1<<0),    /* --stable */
73
  GOPID_VERBATIM = (1<<1),  /* --verbatim */
74
  GOPID_FIND_HEADER = (1<<2), /* stop at the beginning of patch message */
75
};
76
77
static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
78
         struct strbuf *line_buf, unsigned flags)
79
0
{
80
0
  int stable = flags & GOPID_STABLE;
81
0
  int verbatim = flags & GOPID_VERBATIM;
82
0
  int find_header = flags & GOPID_FIND_HEADER;
83
0
  int patchlen = 0, found_next = 0;
84
0
  int before = -1, after = -1;
85
0
  int diff_is_binary = 0;
86
0
  char pre_oid_str[GIT_MAX_HEXSZ + 1], post_oid_str[GIT_MAX_HEXSZ + 1];
87
0
  git_hash_ctx ctx;
88
89
0
  the_hash_algo->init_fn(&ctx);
90
0
  oidclr(result, the_repository->hash_algo);
91
92
0
  while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
93
0
    char *line = line_buf->buf;
94
0
    const char *p = line;
95
0
    int len;
96
97
    /*
98
     * The caller hasn't seen us find a patch header and
99
     * return to it, or we have started processing patch
100
     * and may encounter the beginning of the next patch.
101
     */
102
0
    if (find_header) {
103
      /*
104
       * If we see a line that begins with "<object name>",
105
       * "commit <object name>" or "From <object name>", it is
106
       * the beginning of a patch.  Return to the caller, as
107
       * we are done with the one we have been processing.
108
       */
109
0
      if (skip_prefix(line, "commit ", &p))
110
0
        ;
111
0
      else if (skip_prefix(line, "From ", &p))
112
0
        ;
113
0
      if (!get_oid_hex(p, next_oid)) {
114
0
        if (verbatim)
115
0
          the_hash_algo->update_fn(&ctx, line, strlen(line));
116
0
        found_next = 1;
117
0
        break;
118
0
      }
119
0
    }
120
121
    /* Ignore commit comments */
122
0
    if (!patchlen && !starts_with(line, "diff "))
123
0
      continue;
124
125
    /*
126
     * We are past the commit log message.  Prepare to
127
     * stop at the beginning of the next patch header.
128
     */
129
0
    find_header = 1;
130
131
    /* Parsing diff header?  */
132
0
    if (before == -1) {
133
0
      if (starts_with(line, "GIT binary patch") ||
134
0
          starts_with(line, "Binary files")) {
135
0
        diff_is_binary = 1;
136
0
        before = 0;
137
0
        the_hash_algo->update_fn(&ctx, pre_oid_str,
138
0
               strlen(pre_oid_str));
139
0
        the_hash_algo->update_fn(&ctx, post_oid_str,
140
0
               strlen(post_oid_str));
141
0
        if (stable)
142
0
          flush_one_hunk(result, &ctx);
143
0
        continue;
144
0
      } else if (skip_prefix(line, "index ", &p)) {
145
0
        char *oid1_end = strstr(line, "..");
146
0
        char *oid2_end = NULL;
147
0
        if (oid1_end)
148
0
          oid2_end = strstr(oid1_end, " ");
149
0
        if (!oid2_end)
150
0
          oid2_end = line + strlen(line) - 1;
151
0
        if (oid1_end != NULL && oid2_end != NULL) {
152
0
          *oid1_end = *oid2_end = '\0';
153
0
          strlcpy(pre_oid_str, p, GIT_MAX_HEXSZ + 1);
154
0
          strlcpy(post_oid_str, oid1_end + 2, GIT_MAX_HEXSZ + 1);
155
0
        }
156
0
        continue;
157
0
      } else if (starts_with(line, "--- "))
158
0
        before = after = 1;
159
0
      else if (!isalpha(line[0]))
160
0
        break;
161
0
    }
162
163
    /*
164
     * A hunk about an incomplete line may have this
165
     * marker at the end, which should just be ignored.
166
     */
167
0
    if (starts_with(line, "\\ ") && 12 < strlen(line)) {
168
0
      if (verbatim)
169
0
        the_hash_algo->update_fn(&ctx, line, strlen(line));
170
0
      continue;
171
0
    }
172
173
0
    if (diff_is_binary) {
174
0
      if (starts_with(line, "diff ")) {
175
0
        diff_is_binary = 0;
176
0
        before = -1;
177
0
      }
178
0
      continue;
179
0
    }
180
181
    /* Looking for a valid hunk header?  */
182
0
    if (before == 0 && after == 0) {
183
0
      if (starts_with(line, "@@ -")) {
184
        /* Parse next hunk, but ignore line numbers.  */
185
0
        scan_hunk_header(line, &before, &after);
186
0
        continue;
187
0
      }
188
189
      /* Split at the end of the patch.  */
190
0
      if (!starts_with(line, "diff "))
191
0
        break;
192
193
      /* Else we're parsing another header.  */
194
0
      if (stable)
195
0
        flush_one_hunk(result, &ctx);
196
0
      before = after = -1;
197
0
    }
198
199
    /* If we get here, we're inside a hunk.  */
200
0
    if (line[0] == '-' || line[0] == ' ')
201
0
      before--;
202
0
    if (line[0] == '+' || line[0] == ' ')
203
0
      after--;
204
205
    /* Add line to hash algo (possibly removing whitespace) */
206
0
    len = verbatim ? strlen(line) : remove_space(line);
207
0
    patchlen += len;
208
0
    the_hash_algo->update_fn(&ctx, line, len);
209
0
  }
210
211
0
  if (!found_next)
212
0
    oidclr(next_oid, the_repository->hash_algo);
213
214
0
  flush_one_hunk(result, &ctx);
215
216
0
  return patchlen;
217
0
}
218
219
static void generate_id_list(unsigned flags)
220
0
{
221
0
  struct object_id oid, n, result;
222
0
  int patchlen;
223
0
  struct strbuf line_buf = STRBUF_INIT;
224
225
0
  oidclr(&oid, the_repository->hash_algo);
226
0
  flags |= GOPID_FIND_HEADER;
227
0
  while (!feof(stdin)) {
228
0
    patchlen = get_one_patchid(&n, &result, &line_buf, flags);
229
0
    if (patchlen)
230
0
      flush_current_id(&oid, &result);
231
0
    oidcpy(&oid, &n);
232
0
    flags &= ~GOPID_FIND_HEADER;
233
0
  }
234
0
  strbuf_release(&line_buf);
235
0
}
236
237
static const char *const patch_id_usage[] = {
238
  N_("git patch-id [--stable | --unstable | --verbatim]"), NULL
239
};
240
241
struct patch_id_opts {
242
  int stable;
243
  int verbatim;
244
};
245
246
static int git_patch_id_config(const char *var, const char *value,
247
             const struct config_context *ctx, void *cb)
248
0
{
249
0
  struct patch_id_opts *opts = cb;
250
251
0
  if (!strcmp(var, "patchid.stable")) {
252
0
    opts->stable = git_config_bool(var, value);
253
0
    return 0;
254
0
  }
255
0
  if (!strcmp(var, "patchid.verbatim")) {
256
0
    opts->verbatim = git_config_bool(var, value);
257
0
    return 0;
258
0
  }
259
260
0
  return git_default_config(var, value, ctx, cb);
261
0
}
262
263
int cmd_patch_id(int argc, const char **argv, const char *prefix)
264
0
{
265
  /* if nothing is set, default to unstable */
266
0
  struct patch_id_opts config = {0, 0};
267
0
  int opts = 0;
268
0
  unsigned flags = 0;
269
0
  struct option builtin_patch_id_options[] = {
270
0
    OPT_CMDMODE(0, "unstable", &opts,
271
0
        N_("use the unstable patch-id algorithm"), 1),
272
0
    OPT_CMDMODE(0, "stable", &opts,
273
0
        N_("use the stable patch-id algorithm"), 2),
274
0
    OPT_CMDMODE(0, "verbatim", &opts,
275
0
      N_("don't strip whitespace from the patch"), 3),
276
0
    OPT_END()
277
0
  };
278
279
0
  git_config(git_patch_id_config, &config);
280
281
  /* verbatim implies stable */
282
0
  if (config.verbatim)
283
0
    config.stable = 1;
284
285
0
  argc = parse_options(argc, argv, prefix, builtin_patch_id_options,
286
0
           patch_id_usage, 0);
287
288
  /*
289
   * We rely on `the_hash_algo` to compute patch IDs. This is dubious as
290
   * it means that the hash algorithm now depends on the object hash of
291
   * the repository, even though git-patch-id(1) clearly defines that
292
   * patch IDs always use SHA1.
293
   *
294
   * NEEDSWORK: This hack should be removed in favor of converting
295
   * the code that computes patch IDs to always use SHA1.
296
   */
297
0
  if (!the_hash_algo)
298
0
    repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
299
300
0
  if (opts ? opts > 1 : config.stable)
301
0
    flags |= GOPID_STABLE;
302
0
  if (opts ? opts == 3 : config.verbatim)
303
0
    flags |= GOPID_VERBATIM;
304
0
  generate_id_list(flags);
305
306
0
  return 0;
307
0
}