Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/commit-graph.c
Line
Count
Source (jump to first uncovered line)
1
#include "builtin.h"
2
#include "commit.h"
3
#include "config.h"
4
#include "environment.h"
5
#include "gettext.h"
6
#include "hex.h"
7
#include "parse-options.h"
8
#include "repository.h"
9
#include "commit-graph.h"
10
#include "object-store-ll.h"
11
#include "progress.h"
12
#include "replace-object.h"
13
#include "strbuf.h"
14
#include "tag.h"
15
#include "trace2.h"
16
17
#define BUILTIN_COMMIT_GRAPH_VERIFY_USAGE \
18
  N_("git commit-graph verify [--object-dir <dir>] [--shallow] [--[no-]progress]")
19
20
#define BUILTIN_COMMIT_GRAPH_WRITE_USAGE \
21
  N_("git commit-graph write [--object-dir <dir>] [--append]\n" \
22
     "                       [--split[=<strategy>]] [--reachable | --stdin-packs | --stdin-commits]\n" \
23
     "                       [--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress]\n" \
24
     "                       <split-options>")
25
26
static const char * builtin_commit_graph_verify_usage[] = {
27
  BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
28
  NULL
29
};
30
31
static const char * builtin_commit_graph_write_usage[] = {
32
  BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
33
  NULL
34
};
35
36
static char const * const builtin_commit_graph_usage[] = {
37
  BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
38
  BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
39
  NULL,
40
};
41
42
static struct opts_commit_graph {
43
  const char *obj_dir;
44
  int reachable;
45
  int stdin_packs;
46
  int stdin_commits;
47
  int append;
48
  int split;
49
  int shallow;
50
  int progress;
51
  int enable_changed_paths;
52
} opts;
53
54
static struct option common_opts[] = {
55
  OPT_STRING(0, "object-dir", &opts.obj_dir,
56
       N_("dir"),
57
       N_("the object directory to store the graph")),
58
  OPT_END()
59
};
60
61
static struct option *add_common_options(struct option *to)
62
0
{
63
0
  return parse_options_concat(common_opts, to);
64
0
}
65
66
static int graph_verify(int argc, const char **argv, const char *prefix)
67
0
{
68
0
  struct commit_graph *graph = NULL;
69
0
  struct object_directory *odb = NULL;
70
0
  char *graph_name;
71
0
  char *chain_name;
72
0
  enum { OPENED_NONE, OPENED_GRAPH, OPENED_CHAIN } opened = OPENED_NONE;
73
0
  int fd;
74
0
  struct stat st;
75
0
  int flags = 0;
76
0
  int incomplete_chain = 0;
77
0
  int ret;
78
79
0
  static struct option builtin_commit_graph_verify_options[] = {
80
0
    OPT_BOOL(0, "shallow", &opts.shallow,
81
0
       N_("if the commit-graph is split, only verify the tip file")),
82
0
    OPT_BOOL(0, "progress", &opts.progress,
83
0
       N_("force progress reporting")),
84
0
    OPT_END(),
85
0
  };
86
0
  struct option *options = add_common_options(builtin_commit_graph_verify_options);
87
88
0
  trace2_cmd_mode("verify");
89
90
0
  opts.progress = isatty(2);
91
0
  argc = parse_options(argc, argv, prefix,
92
0
           options,
93
0
           builtin_commit_graph_verify_usage, 0);
94
0
  if (argc)
95
0
    usage_with_options(builtin_commit_graph_verify_usage, options);
96
97
0
  if (!opts.obj_dir)
98
0
    opts.obj_dir = get_object_directory();
99
0
  if (opts.shallow)
100
0
    flags |= COMMIT_GRAPH_VERIFY_SHALLOW;
101
0
  if (opts.progress)
102
0
    flags |= COMMIT_GRAPH_WRITE_PROGRESS;
103
104
0
  odb = find_odb(the_repository, opts.obj_dir);
105
0
  graph_name = get_commit_graph_filename(odb);
106
0
  chain_name = get_commit_graph_chain_filename(odb);
107
0
  if (open_commit_graph(graph_name, &fd, &st))
108
0
    opened = OPENED_GRAPH;
109
0
  else if (errno != ENOENT)
110
0
    die_errno(_("Could not open commit-graph '%s'"), graph_name);
111
0
  else if (open_commit_graph_chain(chain_name, &fd, &st))
112
0
    opened = OPENED_CHAIN;
113
0
  else if (errno != ENOENT)
114
0
    die_errno(_("could not open commit-graph chain '%s'"), chain_name);
115
116
0
  FREE_AND_NULL(graph_name);
117
0
  FREE_AND_NULL(chain_name);
118
0
  FREE_AND_NULL(options);
119
120
0
  if (opened == OPENED_NONE)
121
0
    return 0;
122
0
  else if (opened == OPENED_GRAPH)
123
0
    graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
124
0
  else
125
0
    graph = load_commit_graph_chain_fd_st(the_repository, fd, &st,
126
0
                  &incomplete_chain);
127
128
0
  if (!graph)
129
0
    return 1;
130
131
0
  ret = verify_commit_graph(the_repository, graph, flags);
132
0
  free_commit_graph(graph);
133
134
0
  if (incomplete_chain) {
135
0
    error("one or more commit-graph chain files could not be loaded");
136
0
    ret |= 1;
137
0
  }
138
139
0
  return ret;
140
0
}
141
142
extern int read_replace_refs;
143
static struct commit_graph_opts write_opts;
144
145
static int write_option_parse_split(const struct option *opt, const char *arg,
146
            int unset)
147
0
{
148
0
  enum commit_graph_split_flags *flags = opt->value;
149
150
0
  BUG_ON_OPT_NEG(unset);
151
152
0
  opts.split = 1;
153
0
  if (!arg)
154
0
    return 0;
155
156
0
  if (!strcmp(arg, "no-merge"))
157
0
    *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED;
158
0
  else if (!strcmp(arg, "replace"))
159
0
    *flags = COMMIT_GRAPH_SPLIT_REPLACE;
160
0
  else
161
0
    die(_("unrecognized --split argument, %s"), arg);
162
163
0
  return 0;
164
0
}
165
166
static int read_one_commit(struct oidset *commits, struct progress *progress,
167
         const char *hash)
168
0
{
169
0
  struct object *result;
170
0
  struct object_id oid;
171
0
  const char *end;
172
173
0
  if (parse_oid_hex(hash, &oid, &end))
174
0
    return error(_("unexpected non-hex object ID: %s"), hash);
175
176
0
  result = deref_tag(the_repository, parse_object(the_repository, &oid),
177
0
         NULL, 0);
178
0
  if (!result)
179
0
    return error(_("invalid object: %s"), hash);
180
0
  else if (object_as_type(result, OBJ_COMMIT, 1))
181
0
    oidset_insert(commits, &result->oid);
182
183
0
  display_progress(progress, oidset_size(commits));
184
185
0
  return 0;
186
0
}
187
188
static int write_option_max_new_filters(const struct option *opt,
189
          const char *arg,
190
          int unset)
191
0
{
192
0
  int *to = opt->value;
193
0
  if (unset)
194
0
    *to = -1;
195
0
  else {
196
0
    const char *s;
197
0
    *to = strtol(arg, (char **)&s, 10);
198
0
    if (*s)
199
0
      return error(_("option `%s' expects a numerical value"),
200
0
             "max-new-filters");
201
0
  }
202
0
  return 0;
203
0
}
204
205
static int git_commit_graph_write_config(const char *var, const char *value,
206
           const struct config_context *ctx,
207
           void *cb UNUSED)
208
0
{
209
0
  if (!strcmp(var, "commitgraph.maxnewfilters"))
210
0
    write_opts.max_new_filters = git_config_int(var, value, ctx->kvi);
211
  /*
212
   * No need to fall-back to 'git_default_config', since this was already
213
   * called in 'cmd_commit_graph()'.
214
   */
215
0
  return 0;
216
0
}
217
218
static int graph_write(int argc, const char **argv, const char *prefix)
219
0
{
220
0
  struct string_list pack_indexes = STRING_LIST_INIT_DUP;
221
0
  struct strbuf buf = STRBUF_INIT;
222
0
  struct oidset commits = OIDSET_INIT;
223
0
  struct object_directory *odb = NULL;
224
0
  int result = 0;
225
0
  enum commit_graph_write_flags flags = 0;
226
0
  struct progress *progress = NULL;
227
228
0
  static struct option builtin_commit_graph_write_options[] = {
229
0
    OPT_BOOL(0, "reachable", &opts.reachable,
230
0
      N_("start walk at all refs")),
231
0
    OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
232
0
      N_("scan pack-indexes listed by stdin for commits")),
233
0
    OPT_BOOL(0, "stdin-commits", &opts.stdin_commits,
234
0
      N_("start walk at commits listed by stdin")),
235
0
    OPT_BOOL(0, "append", &opts.append,
236
0
      N_("include all commits already in the commit-graph file")),
237
0
    OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths,
238
0
      N_("enable computation for changed paths")),
239
0
    OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL,
240
0
      N_("allow writing an incremental commit-graph file"),
241
0
      PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
242
0
      write_option_parse_split),
243
0
    OPT_INTEGER(0, "max-commits", &write_opts.max_commits,
244
0
      N_("maximum number of commits in a non-base split commit-graph")),
245
0
    OPT_INTEGER(0, "size-multiple", &write_opts.size_multiple,
246
0
      N_("maximum ratio between two levels of a split commit-graph")),
247
0
    OPT_EXPIRY_DATE(0, "expire-time", &write_opts.expire_time,
248
0
      N_("only expire files older than a given date-time")),
249
0
    OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters,
250
0
      NULL, N_("maximum number of changed-path Bloom filters to compute"),
251
0
      0, write_option_max_new_filters),
252
0
    OPT_BOOL(0, "progress", &opts.progress,
253
0
       N_("force progress reporting")),
254
0
    OPT_END(),
255
0
  };
256
0
  struct option *options = add_common_options(builtin_commit_graph_write_options);
257
258
0
  opts.progress = isatty(2);
259
0
  opts.enable_changed_paths = -1;
260
0
  write_opts.size_multiple = 2;
261
0
  write_opts.max_commits = 0;
262
0
  write_opts.expire_time = 0;
263
0
  write_opts.max_new_filters = -1;
264
265
0
  trace2_cmd_mode("write");
266
267
0
  git_config(git_commit_graph_write_config, &opts);
268
269
0
  argc = parse_options(argc, argv, prefix,
270
0
           options,
271
0
           builtin_commit_graph_write_usage, 0);
272
0
  if (argc)
273
0
    usage_with_options(builtin_commit_graph_write_usage, options);
274
275
0
  if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1)
276
0
    die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs"));
277
0
  if (!opts.obj_dir)
278
0
    opts.obj_dir = get_object_directory();
279
0
  if (opts.append)
280
0
    flags |= COMMIT_GRAPH_WRITE_APPEND;
281
0
  if (opts.split)
282
0
    flags |= COMMIT_GRAPH_WRITE_SPLIT;
283
0
  if (opts.progress)
284
0
    flags |= COMMIT_GRAPH_WRITE_PROGRESS;
285
0
  if (!opts.enable_changed_paths)
286
0
    flags |= COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS;
287
0
  if (opts.enable_changed_paths == 1 ||
288
0
      git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
289
0
    flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
290
291
0
  odb = find_odb(the_repository, opts.obj_dir);
292
293
0
  if (opts.reachable) {
294
0
    if (write_commit_graph_reachable(odb, flags, &write_opts))
295
0
      result = 1;
296
0
    goto cleanup;
297
0
  }
298
299
0
  if (opts.stdin_packs) {
300
0
    while (strbuf_getline(&buf, stdin) != EOF)
301
0
      string_list_append_nodup(&pack_indexes,
302
0
             strbuf_detach(&buf, NULL));
303
0
  } else if (opts.stdin_commits) {
304
0
    oidset_init(&commits, 0);
305
0
    if (opts.progress)
306
0
      progress = start_delayed_progress(
307
0
        _("Collecting commits from input"), 0);
308
309
0
    while (strbuf_getline(&buf, stdin) != EOF) {
310
0
      if (read_one_commit(&commits, progress, buf.buf)) {
311
0
        result = 1;
312
0
        goto cleanup;
313
0
      }
314
0
    }
315
316
0
    stop_progress(&progress);
317
0
  }
318
319
0
  if (write_commit_graph(odb,
320
0
             opts.stdin_packs ? &pack_indexes : NULL,
321
0
             opts.stdin_commits ? &commits : NULL,
322
0
             flags,
323
0
             &write_opts))
324
0
    result = 1;
325
326
0
cleanup:
327
0
  FREE_AND_NULL(options);
328
0
  string_list_clear(&pack_indexes, 0);
329
0
  strbuf_release(&buf);
330
0
  oidset_clear(&commits);
331
0
  return result;
332
0
}
333
334
int cmd_commit_graph(int argc, const char **argv, const char *prefix)
335
0
{
336
0
  parse_opt_subcommand_fn *fn = NULL;
337
0
  struct option builtin_commit_graph_options[] = {
338
0
    OPT_SUBCOMMAND("verify", &fn, graph_verify),
339
0
    OPT_SUBCOMMAND("write", &fn, graph_write),
340
0
    OPT_END(),
341
0
  };
342
0
  struct option *options = parse_options_concat(builtin_commit_graph_options, common_opts);
343
344
0
  git_config(git_default_config, NULL);
345
346
0
  disable_replace_refs();
347
0
  save_commit_buffer = 0;
348
349
0
  argc = parse_options(argc, argv, prefix, options,
350
0
           builtin_commit_graph_usage, 0);
351
0
  FREE_AND_NULL(options);
352
353
0
  return fn(argc, argv, prefix);
354
0
}