Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/gc.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * git gc builtin command
3
 *
4
 * Cleanup unreachable files and optimize the repository.
5
 *
6
 * Copyright (c) 2007 James Bowes
7
 *
8
 * Based on git-gc.sh, which is
9
 *
10
 * Copyright (c) 2006 Shawn O. Pearce
11
 */
12
13
#include "builtin.h"
14
#include "abspath.h"
15
#include "date.h"
16
#include "environment.h"
17
#include "hex.h"
18
#include "repository.h"
19
#include "config.h"
20
#include "tempfile.h"
21
#include "lockfile.h"
22
#include "parse-options.h"
23
#include "run-command.h"
24
#include "sigchain.h"
25
#include "strvec.h"
26
#include "commit.h"
27
#include "commit-graph.h"
28
#include "packfile.h"
29
#include "object-file.h"
30
#include "object-store-ll.h"
31
#include "pack.h"
32
#include "pack-objects.h"
33
#include "path.h"
34
#include "blob.h"
35
#include "tree.h"
36
#include "promisor-remote.h"
37
#include "refs.h"
38
#include "remote.h"
39
#include "exec-cmd.h"
40
#include "gettext.h"
41
#include "hook.h"
42
#include "setup.h"
43
#include "trace2.h"
44
45
0
#define FAILED_RUN "failed to run %s"
46
47
static const char * const builtin_gc_usage[] = {
48
  N_("git gc [<options>]"),
49
  NULL
50
};
51
52
static timestamp_t gc_log_expire_time;
53
54
static struct strvec reflog = STRVEC_INIT;
55
static struct strvec repack = STRVEC_INIT;
56
static struct strvec prune = STRVEC_INIT;
57
static struct strvec prune_worktrees = STRVEC_INIT;
58
static struct strvec rerere = STRVEC_INIT;
59
60
static struct tempfile *pidfile;
61
static struct lock_file log_lock;
62
63
static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
64
65
static void clean_pack_garbage(void)
66
0
{
67
0
  int i;
68
0
  for (i = 0; i < pack_garbage.nr; i++)
69
0
    unlink_or_warn(pack_garbage.items[i].string);
70
0
  string_list_clear(&pack_garbage, 0);
71
0
}
72
73
static void report_pack_garbage(unsigned seen_bits, const char *path)
74
0
{
75
0
  if (seen_bits == PACKDIR_FILE_IDX)
76
0
    string_list_append(&pack_garbage, path);
77
0
}
78
79
static void process_log_file(void)
80
0
{
81
0
  struct stat st;
82
0
  if (fstat(get_lock_file_fd(&log_lock), &st)) {
83
    /*
84
     * Perhaps there was an i/o error or another
85
     * unlikely situation.  Try to make a note of
86
     * this in gc.log along with any existing
87
     * messages.
88
     */
89
0
    int saved_errno = errno;
90
0
    fprintf(stderr, _("Failed to fstat %s: %s"),
91
0
      get_lock_file_path(&log_lock),
92
0
      strerror(saved_errno));
93
0
    fflush(stderr);
94
0
    commit_lock_file(&log_lock);
95
0
    errno = saved_errno;
96
0
  } else if (st.st_size) {
97
    /* There was some error recorded in the lock file */
98
0
    commit_lock_file(&log_lock);
99
0
  } else {
100
    /* No error, clean up any old gc.log */
101
0
    unlink(git_path("gc.log"));
102
0
    rollback_lock_file(&log_lock);
103
0
  }
104
0
}
105
106
static void process_log_file_at_exit(void)
107
0
{
108
0
  fflush(stderr);
109
0
  process_log_file();
110
0
}
111
112
static int gc_config_is_timestamp_never(const char *var)
113
0
{
114
0
  const char *value;
115
0
  timestamp_t expire;
116
117
0
  if (!git_config_get_value(var, &value) && value) {
118
0
    if (parse_expiry_date(value, &expire))
119
0
      die(_("failed to parse '%s' value '%s'"), var, value);
120
0
    return expire == 0;
121
0
  }
122
0
  return 0;
123
0
}
124
125
struct gc_config {
126
  int pack_refs;
127
  int prune_reflogs;
128
  int cruft_packs;
129
  unsigned long max_cruft_size;
130
  int aggressive_depth;
131
  int aggressive_window;
132
  int gc_auto_threshold;
133
  int gc_auto_pack_limit;
134
  int detach_auto;
135
  char *gc_log_expire;
136
  char *prune_expire;
137
  char *prune_worktrees_expire;
138
  char *repack_filter;
139
  char *repack_filter_to;
140
  unsigned long big_pack_threshold;
141
  unsigned long max_delta_cache_size;
142
};
143
144
0
#define GC_CONFIG_INIT { \
145
0
  .pack_refs = 1, \
146
0
  .prune_reflogs = 1, \
147
0
  .cruft_packs = 1, \
148
0
  .aggressive_depth = 50, \
149
0
  .aggressive_window = 250, \
150
0
  .gc_auto_threshold = 6700, \
151
0
  .gc_auto_pack_limit = 50, \
152
0
  .detach_auto = 1, \
153
0
  .gc_log_expire = xstrdup("1.day.ago"), \
154
0
  .prune_expire = xstrdup("2.weeks.ago"), \
155
0
  .prune_worktrees_expire = xstrdup("3.months.ago"), \
156
0
  .max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE, \
157
0
}
158
159
static void gc_config_release(struct gc_config *cfg)
160
0
{
161
0
  free(cfg->gc_log_expire);
162
0
  free(cfg->prune_expire);
163
0
  free(cfg->prune_worktrees_expire);
164
0
  free(cfg->repack_filter);
165
0
  free(cfg->repack_filter_to);
166
0
}
167
168
static void gc_config(struct gc_config *cfg)
169
0
{
170
0
  const char *value;
171
0
  char *owned = NULL;
172
173
0
  if (!git_config_get_value("gc.packrefs", &value)) {
174
0
    if (value && !strcmp(value, "notbare"))
175
0
      cfg->pack_refs = -1;
176
0
    else
177
0
      cfg->pack_refs = git_config_bool("gc.packrefs", value);
178
0
  }
179
180
0
  if (gc_config_is_timestamp_never("gc.reflogexpire") &&
181
0
      gc_config_is_timestamp_never("gc.reflogexpireunreachable"))
182
0
    cfg->prune_reflogs = 0;
183
184
0
  git_config_get_int("gc.aggressivewindow", &cfg->aggressive_window);
185
0
  git_config_get_int("gc.aggressivedepth", &cfg->aggressive_depth);
186
0
  git_config_get_int("gc.auto", &cfg->gc_auto_threshold);
187
0
  git_config_get_int("gc.autopacklimit", &cfg->gc_auto_pack_limit);
188
0
  git_config_get_bool("gc.autodetach", &cfg->detach_auto);
189
0
  git_config_get_bool("gc.cruftpacks", &cfg->cruft_packs);
190
0
  git_config_get_ulong("gc.maxcruftsize", &cfg->max_cruft_size);
191
192
0
  if (!repo_config_get_expiry(the_repository, "gc.pruneexpire", &owned)) {
193
0
    free(cfg->prune_expire);
194
0
    cfg->prune_expire = owned;
195
0
  }
196
197
0
  if (!repo_config_get_expiry(the_repository, "gc.worktreepruneexpire", &owned)) {
198
0
    free(cfg->prune_worktrees_expire);
199
0
    cfg->prune_worktrees_expire = owned;
200
0
  }
201
202
0
  if (!repo_config_get_expiry(the_repository, "gc.logexpiry", &owned)) {
203
0
    free(cfg->gc_log_expire);
204
0
    cfg->gc_log_expire = owned;
205
0
  }
206
207
0
  git_config_get_ulong("gc.bigpackthreshold", &cfg->big_pack_threshold);
208
0
  git_config_get_ulong("pack.deltacachesize", &cfg->max_delta_cache_size);
209
210
0
  if (!git_config_get_string("gc.repackfilter", &owned)) {
211
0
    free(cfg->repack_filter);
212
0
    cfg->repack_filter = owned;
213
0
  }
214
215
0
  if (!git_config_get_string("gc.repackfilterto", &owned)) {
216
0
    free(cfg->repack_filter_to);
217
0
    cfg->repack_filter_to = owned;
218
0
  }
219
220
0
  git_config(git_default_config, NULL);
221
0
}
222
223
enum schedule_priority {
224
  SCHEDULE_NONE = 0,
225
  SCHEDULE_WEEKLY = 1,
226
  SCHEDULE_DAILY = 2,
227
  SCHEDULE_HOURLY = 3,
228
};
229
230
static enum schedule_priority parse_schedule(const char *value)
231
0
{
232
0
  if (!value)
233
0
    return SCHEDULE_NONE;
234
0
  if (!strcasecmp(value, "hourly"))
235
0
    return SCHEDULE_HOURLY;
236
0
  if (!strcasecmp(value, "daily"))
237
0
    return SCHEDULE_DAILY;
238
0
  if (!strcasecmp(value, "weekly"))
239
0
    return SCHEDULE_WEEKLY;
240
0
  return SCHEDULE_NONE;
241
0
}
242
243
struct maintenance_run_opts {
244
  int auto_flag;
245
  int detach;
246
  int quiet;
247
  enum schedule_priority schedule;
248
};
249
0
#define MAINTENANCE_RUN_OPTS_INIT { \
250
0
  .detach = -1, \
251
0
}
252
253
static int pack_refs_condition(UNUSED struct gc_config *cfg)
254
0
{
255
  /*
256
   * The auto-repacking logic for refs is handled by the ref backends and
257
   * exposed via `git pack-refs --auto`. We thus always return truish
258
   * here and let the backend decide for us.
259
   */
260
0
  return 1;
261
0
}
262
263
static int maintenance_task_pack_refs(struct maintenance_run_opts *opts,
264
              UNUSED struct gc_config *cfg)
265
0
{
266
0
  struct child_process cmd = CHILD_PROCESS_INIT;
267
268
0
  cmd.git_cmd = 1;
269
0
  strvec_pushl(&cmd.args, "pack-refs", "--all", "--prune", NULL);
270
0
  if (opts->auto_flag)
271
0
    strvec_push(&cmd.args, "--auto");
272
273
0
  return run_command(&cmd);
274
0
}
275
276
static int too_many_loose_objects(struct gc_config *cfg)
277
0
{
278
  /*
279
   * Quickly check if a "gc" is needed, by estimating how
280
   * many loose objects there are.  Because SHA-1 is evenly
281
   * distributed, we can check only one and get a reasonable
282
   * estimate.
283
   */
284
0
  DIR *dir;
285
0
  struct dirent *ent;
286
0
  int auto_threshold;
287
0
  int num_loose = 0;
288
0
  int needed = 0;
289
0
  const unsigned hexsz_loose = the_hash_algo->hexsz - 2;
290
291
0
  dir = opendir(git_path("objects/17"));
292
0
  if (!dir)
293
0
    return 0;
294
295
0
  auto_threshold = DIV_ROUND_UP(cfg->gc_auto_threshold, 256);
296
0
  while ((ent = readdir(dir)) != NULL) {
297
0
    if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose ||
298
0
        ent->d_name[hexsz_loose] != '\0')
299
0
      continue;
300
0
    if (++num_loose > auto_threshold) {
301
0
      needed = 1;
302
0
      break;
303
0
    }
304
0
  }
305
0
  closedir(dir);
306
0
  return needed;
307
0
}
308
309
static struct packed_git *find_base_packs(struct string_list *packs,
310
            unsigned long limit)
311
0
{
312
0
  struct packed_git *p, *base = NULL;
313
314
0
  for (p = get_all_packs(the_repository); p; p = p->next) {
315
0
    if (!p->pack_local || p->is_cruft)
316
0
      continue;
317
0
    if (limit) {
318
0
      if (p->pack_size >= limit)
319
0
        string_list_append(packs, p->pack_name);
320
0
    } else if (!base || base->pack_size < p->pack_size) {
321
0
      base = p;
322
0
    }
323
0
  }
324
325
0
  if (base)
326
0
    string_list_append(packs, base->pack_name);
327
328
0
  return base;
329
0
}
330
331
static int too_many_packs(struct gc_config *cfg)
332
0
{
333
0
  struct packed_git *p;
334
0
  int cnt;
335
336
0
  if (cfg->gc_auto_pack_limit <= 0)
337
0
    return 0;
338
339
0
  for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) {
340
0
    if (!p->pack_local)
341
0
      continue;
342
0
    if (p->pack_keep)
343
0
      continue;
344
    /*
345
     * Perhaps check the size of the pack and count only
346
     * very small ones here?
347
     */
348
0
    cnt++;
349
0
  }
350
0
  return cfg->gc_auto_pack_limit < cnt;
351
0
}
352
353
static uint64_t total_ram(void)
354
0
{
355
0
#if defined(HAVE_SYSINFO)
356
0
  struct sysinfo si;
357
358
0
  if (!sysinfo(&si))
359
0
    return si.totalram;
360
#elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM))
361
  int64_t physical_memory;
362
  int mib[2];
363
  size_t length;
364
365
  mib[0] = CTL_HW;
366
# if defined(HW_MEMSIZE)
367
  mib[1] = HW_MEMSIZE;
368
# else
369
  mib[1] = HW_PHYSMEM;
370
# endif
371
  length = sizeof(int64_t);
372
  if (!sysctl(mib, 2, &physical_memory, &length, NULL, 0))
373
    return physical_memory;
374
#elif defined(GIT_WINDOWS_NATIVE)
375
  MEMORYSTATUSEX memInfo;
376
377
  memInfo.dwLength = sizeof(MEMORYSTATUSEX);
378
  if (GlobalMemoryStatusEx(&memInfo))
379
    return memInfo.ullTotalPhys;
380
#endif
381
0
  return 0;
382
0
}
383
384
static uint64_t estimate_repack_memory(struct gc_config *cfg,
385
               struct packed_git *pack)
386
0
{
387
0
  unsigned long nr_objects = repo_approximate_object_count(the_repository);
388
0
  size_t os_cache, heap;
389
390
0
  if (!pack || !nr_objects)
391
0
    return 0;
392
393
  /*
394
   * First we have to scan through at least one pack.
395
   * Assume enough room in OS file cache to keep the entire pack
396
   * or we may accidentally evict data of other processes from
397
   * the cache.
398
   */
399
0
  os_cache = pack->pack_size + pack->index_size;
400
  /* then pack-objects needs lots more for book keeping */
401
0
  heap = sizeof(struct object_entry) * nr_objects;
402
  /*
403
   * internal rev-list --all --objects takes up some memory too,
404
   * let's say half of it is for blobs
405
   */
406
0
  heap += sizeof(struct blob) * nr_objects / 2;
407
  /*
408
   * and the other half is for trees (commits and tags are
409
   * usually insignificant)
410
   */
411
0
  heap += sizeof(struct tree) * nr_objects / 2;
412
  /* and then obj_hash[], underestimated in fact */
413
0
  heap += sizeof(struct object *) * nr_objects;
414
  /* revindex is used also */
415
0
  heap += (sizeof(off_t) + sizeof(uint32_t)) * nr_objects;
416
  /*
417
   * read_sha1_file() (either at delta calculation phase, or
418
   * writing phase) also fills up the delta base cache
419
   */
420
0
  heap += delta_base_cache_limit;
421
  /* and of course pack-objects has its own delta cache */
422
0
  heap += cfg->max_delta_cache_size;
423
424
0
  return os_cache + heap;
425
0
}
426
427
static int keep_one_pack(struct string_list_item *item, void *data UNUSED)
428
0
{
429
0
  strvec_pushf(&repack, "--keep-pack=%s", basename(item->string));
430
0
  return 0;
431
0
}
432
433
static void add_repack_all_option(struct gc_config *cfg,
434
          struct string_list *keep_pack)
435
0
{
436
0
  if (cfg->prune_expire && !strcmp(cfg->prune_expire, "now"))
437
0
    strvec_push(&repack, "-a");
438
0
  else if (cfg->cruft_packs) {
439
0
    strvec_push(&repack, "--cruft");
440
0
    if (cfg->prune_expire)
441
0
      strvec_pushf(&repack, "--cruft-expiration=%s", cfg->prune_expire);
442
0
    if (cfg->max_cruft_size)
443
0
      strvec_pushf(&repack, "--max-cruft-size=%lu",
444
0
             cfg->max_cruft_size);
445
0
  } else {
446
0
    strvec_push(&repack, "-A");
447
0
    if (cfg->prune_expire)
448
0
      strvec_pushf(&repack, "--unpack-unreachable=%s", cfg->prune_expire);
449
0
  }
450
451
0
  if (keep_pack)
452
0
    for_each_string_list(keep_pack, keep_one_pack, NULL);
453
454
0
  if (cfg->repack_filter && *cfg->repack_filter)
455
0
    strvec_pushf(&repack, "--filter=%s", cfg->repack_filter);
456
0
  if (cfg->repack_filter_to && *cfg->repack_filter_to)
457
0
    strvec_pushf(&repack, "--filter-to=%s", cfg->repack_filter_to);
458
0
}
459
460
static void add_repack_incremental_option(void)
461
0
{
462
0
  strvec_push(&repack, "--no-write-bitmap-index");
463
0
}
464
465
static int need_to_gc(struct gc_config *cfg)
466
0
{
467
  /*
468
   * Setting gc.auto to 0 or negative can disable the
469
   * automatic gc.
470
   */
471
0
  if (cfg->gc_auto_threshold <= 0)
472
0
    return 0;
473
474
  /*
475
   * If there are too many loose objects, but not too many
476
   * packs, we run "repack -d -l".  If there are too many packs,
477
   * we run "repack -A -d -l".  Otherwise we tell the caller
478
   * there is no need.
479
   */
480
0
  if (too_many_packs(cfg)) {
481
0
    struct string_list keep_pack = STRING_LIST_INIT_NODUP;
482
483
0
    if (cfg->big_pack_threshold) {
484
0
      find_base_packs(&keep_pack, cfg->big_pack_threshold);
485
0
      if (keep_pack.nr >= cfg->gc_auto_pack_limit) {
486
0
        cfg->big_pack_threshold = 0;
487
0
        string_list_clear(&keep_pack, 0);
488
0
        find_base_packs(&keep_pack, 0);
489
0
      }
490
0
    } else {
491
0
      struct packed_git *p = find_base_packs(&keep_pack, 0);
492
0
      uint64_t mem_have, mem_want;
493
494
0
      mem_have = total_ram();
495
0
      mem_want = estimate_repack_memory(cfg, p);
496
497
      /*
498
       * Only allow 1/2 of memory for pack-objects, leave
499
       * the rest for the OS and other processes in the
500
       * system.
501
       */
502
0
      if (!mem_have || mem_want < mem_have / 2)
503
0
        string_list_clear(&keep_pack, 0);
504
0
    }
505
506
0
    add_repack_all_option(cfg, &keep_pack);
507
0
    string_list_clear(&keep_pack, 0);
508
0
  } else if (too_many_loose_objects(cfg))
509
0
    add_repack_incremental_option();
510
0
  else
511
0
    return 0;
512
513
0
  if (run_hooks(the_repository, "pre-auto-gc"))
514
0
    return 0;
515
0
  return 1;
516
0
}
517
518
/* return NULL on success, else hostname running the gc */
519
static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
520
0
{
521
0
  struct lock_file lock = LOCK_INIT;
522
0
  char my_host[HOST_NAME_MAX + 1];
523
0
  struct strbuf sb = STRBUF_INIT;
524
0
  struct stat st;
525
0
  uintmax_t pid;
526
0
  FILE *fp;
527
0
  int fd;
528
0
  char *pidfile_path;
529
530
0
  if (is_tempfile_active(pidfile))
531
    /* already locked */
532
0
    return NULL;
533
534
0
  if (xgethostname(my_host, sizeof(my_host)))
535
0
    xsnprintf(my_host, sizeof(my_host), "unknown");
536
537
0
  pidfile_path = git_pathdup("gc.pid");
538
0
  fd = hold_lock_file_for_update(&lock, pidfile_path,
539
0
               LOCK_DIE_ON_ERROR);
540
0
  if (!force) {
541
0
    static char locking_host[HOST_NAME_MAX + 1];
542
0
    static char *scan_fmt;
543
0
    int should_exit;
544
545
0
    if (!scan_fmt)
546
0
      scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX);
547
0
    fp = fopen(pidfile_path, "r");
548
0
    memset(locking_host, 0, sizeof(locking_host));
549
0
    should_exit =
550
0
      fp != NULL &&
551
0
      !fstat(fileno(fp), &st) &&
552
      /*
553
       * 12 hour limit is very generous as gc should
554
       * never take that long. On the other hand we
555
       * don't really need a strict limit here,
556
       * running gc --auto one day late is not a big
557
       * problem. --force can be used in manual gc
558
       * after the user verifies that no gc is
559
       * running.
560
       */
561
0
      time(NULL) - st.st_mtime <= 12 * 3600 &&
562
0
      fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
563
      /* be gentle to concurrent "gc" on remote hosts */
564
0
      (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
565
0
    if (fp)
566
0
      fclose(fp);
567
0
    if (should_exit) {
568
0
      if (fd >= 0)
569
0
        rollback_lock_file(&lock);
570
0
      *ret_pid = pid;
571
0
      free(pidfile_path);
572
0
      return locking_host;
573
0
    }
574
0
  }
575
576
0
  strbuf_addf(&sb, "%"PRIuMAX" %s",
577
0
        (uintmax_t) getpid(), my_host);
578
0
  write_in_full(fd, sb.buf, sb.len);
579
0
  strbuf_release(&sb);
580
0
  commit_lock_file(&lock);
581
0
  pidfile = register_tempfile(pidfile_path);
582
0
  free(pidfile_path);
583
0
  return NULL;
584
0
}
585
586
/*
587
 * Returns 0 if there was no previous error and gc can proceed, 1 if
588
 * gc should not proceed due to an error in the last run. Prints a
589
 * message and returns with a non-[01] status code if an error occurred
590
 * while reading gc.log
591
 */
592
static int report_last_gc_error(void)
593
0
{
594
0
  struct strbuf sb = STRBUF_INIT;
595
0
  int ret = 0;
596
0
  ssize_t len;
597
0
  struct stat st;
598
0
  char *gc_log_path = git_pathdup("gc.log");
599
600
0
  if (stat(gc_log_path, &st)) {
601
0
    if (errno == ENOENT)
602
0
      goto done;
603
604
0
    ret = die_message_errno(_("cannot stat '%s'"), gc_log_path);
605
0
    goto done;
606
0
  }
607
608
0
  if (st.st_mtime < gc_log_expire_time)
609
0
    goto done;
610
611
0
  len = strbuf_read_file(&sb, gc_log_path, 0);
612
0
  if (len < 0)
613
0
    ret = die_message_errno(_("cannot read '%s'"), gc_log_path);
614
0
  else if (len > 0) {
615
    /*
616
     * A previous gc failed.  Report the error, and don't
617
     * bother with an automatic gc run since it is likely
618
     * to fail in the same way.
619
     */
620
0
    warning(_("The last gc run reported the following. "
621
0
             "Please correct the root cause\n"
622
0
             "and remove %s\n"
623
0
             "Automatic cleanup will not be performed "
624
0
             "until the file is removed.\n\n"
625
0
             "%s"),
626
0
          gc_log_path, sb.buf);
627
0
    ret = 1;
628
0
  }
629
0
  strbuf_release(&sb);
630
0
done:
631
0
  free(gc_log_path);
632
0
  return ret;
633
0
}
634
635
static void gc_before_repack(struct maintenance_run_opts *opts,
636
           struct gc_config *cfg)
637
0
{
638
  /*
639
   * We may be called twice, as both the pre- and
640
   * post-daemonized phases will call us, but running these
641
   * commands more than once is pointless and wasteful.
642
   */
643
0
  static int done = 0;
644
0
  if (done++)
645
0
    return;
646
647
0
  if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg))
648
0
    die(FAILED_RUN, "pack-refs");
649
650
0
  if (cfg->prune_reflogs) {
651
0
    struct child_process cmd = CHILD_PROCESS_INIT;
652
653
0
    cmd.git_cmd = 1;
654
0
    strvec_pushv(&cmd.args, reflog.v);
655
0
    if (run_command(&cmd))
656
0
      die(FAILED_RUN, reflog.v[0]);
657
0
  }
658
0
}
659
660
int cmd_gc(int argc, const char **argv, const char *prefix)
661
0
{
662
0
  int aggressive = 0;
663
0
  int quiet = 0;
664
0
  int force = 0;
665
0
  const char *name;
666
0
  pid_t pid;
667
0
  int daemonized = 0;
668
0
  int keep_largest_pack = -1;
669
0
  timestamp_t dummy;
670
0
  struct child_process rerere_cmd = CHILD_PROCESS_INIT;
671
0
  struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
672
0
  struct gc_config cfg = GC_CONFIG_INIT;
673
0
  const char *prune_expire_sentinel = "sentinel";
674
0
  const char *prune_expire_arg = prune_expire_sentinel;
675
0
  int ret;
676
677
0
  struct option builtin_gc_options[] = {
678
0
    OPT__QUIET(&quiet, N_("suppress progress reporting")),
679
0
    { OPTION_STRING, 0, "prune", &prune_expire_arg, N_("date"),
680
0
      N_("prune unreferenced objects"),
681
0
      PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire_arg },
682
0
    OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")),
683
0
    OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size,
684
0
            N_("with --cruft, limit the size of new cruft packs")),
685
0
    OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
686
0
    OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"),
687
0
         PARSE_OPT_NOCOMPLETE),
688
0
    OPT_BOOL(0, "detach", &opts.detach,
689
0
       N_("perform garbage collection in the background")),
690
0
    OPT_BOOL_F(0, "force", &force,
691
0
         N_("force running gc even if there may be another gc running"),
692
0
         PARSE_OPT_NOCOMPLETE),
693
0
    OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack,
694
0
       N_("repack all other packs except the largest pack")),
695
0
    OPT_END()
696
0
  };
697
698
0
  if (argc == 2 && !strcmp(argv[1], "-h"))
699
0
    usage_with_options(builtin_gc_usage, builtin_gc_options);
700
701
0
  strvec_pushl(&reflog, "reflog", "expire", "--all", NULL);
702
0
  strvec_pushl(&repack, "repack", "-d", "-l", NULL);
703
0
  strvec_pushl(&prune, "prune", "--expire", NULL);
704
0
  strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
705
0
  strvec_pushl(&rerere, "rerere", "gc", NULL);
706
707
0
  gc_config(&cfg);
708
709
0
  if (parse_expiry_date(cfg.gc_log_expire, &gc_log_expire_time))
710
0
    die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire);
711
712
0
  if (cfg.pack_refs < 0)
713
0
    cfg.pack_refs = !is_bare_repository();
714
715
0
  argc = parse_options(argc, argv, prefix, builtin_gc_options,
716
0
           builtin_gc_usage, 0);
717
0
  if (argc > 0)
718
0
    usage_with_options(builtin_gc_usage, builtin_gc_options);
719
720
0
  if (prune_expire_arg != prune_expire_sentinel) {
721
0
    free(cfg.prune_expire);
722
0
    cfg.prune_expire = xstrdup_or_null(prune_expire_arg);
723
0
  }
724
0
  if (cfg.prune_expire && parse_expiry_date(cfg.prune_expire, &dummy))
725
0
    die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
726
727
0
  if (aggressive) {
728
0
    strvec_push(&repack, "-f");
729
0
    if (cfg.aggressive_depth > 0)
730
0
      strvec_pushf(&repack, "--depth=%d", cfg.aggressive_depth);
731
0
    if (cfg.aggressive_window > 0)
732
0
      strvec_pushf(&repack, "--window=%d", cfg.aggressive_window);
733
0
  }
734
0
  if (quiet)
735
0
    strvec_push(&repack, "-q");
736
737
0
  if (opts.auto_flag) {
738
0
    if (cfg.detach_auto && opts.detach < 0)
739
0
      opts.detach = 1;
740
741
    /*
742
     * Auto-gc should be least intrusive as possible.
743
     */
744
0
    if (!need_to_gc(&cfg)) {
745
0
      ret = 0;
746
0
      goto out;
747
0
    }
748
749
0
    if (!quiet) {
750
0
      if (opts.detach > 0)
751
0
        fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
752
0
      else
753
0
        fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
754
0
      fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
755
0
    }
756
0
  } else {
757
0
    struct string_list keep_pack = STRING_LIST_INIT_NODUP;
758
759
0
    if (keep_largest_pack != -1) {
760
0
      if (keep_largest_pack)
761
0
        find_base_packs(&keep_pack, 0);
762
0
    } else if (cfg.big_pack_threshold) {
763
0
      find_base_packs(&keep_pack, cfg.big_pack_threshold);
764
0
    }
765
766
0
    add_repack_all_option(&cfg, &keep_pack);
767
0
    string_list_clear(&keep_pack, 0);
768
0
  }
769
770
0
  if (opts.detach > 0) {
771
0
    ret = report_last_gc_error();
772
0
    if (ret == 1) {
773
      /* Last gc --auto failed. Skip this one. */
774
0
      ret = 0;
775
0
      goto out;
776
777
0
    } else if (ret) {
778
      /* an I/O error occurred, already reported */
779
0
      goto out;
780
0
    }
781
782
0
    if (lock_repo_for_gc(force, &pid)) {
783
0
      ret = 0;
784
0
      goto out;
785
0
    }
786
787
0
    gc_before_repack(&opts, &cfg); /* dies on failure */
788
0
    delete_tempfile(&pidfile);
789
790
    /*
791
     * failure to daemonize is ok, we'll continue
792
     * in foreground
793
     */
794
0
    daemonized = !daemonize();
795
0
  }
796
797
0
  name = lock_repo_for_gc(force, &pid);
798
0
  if (name) {
799
0
    if (opts.auto_flag) {
800
0
      ret = 0;
801
0
      goto out; /* be quiet on --auto */
802
0
    }
803
804
0
    die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
805
0
        name, (uintmax_t)pid);
806
0
  }
807
808
0
  if (daemonized) {
809
0
    hold_lock_file_for_update(&log_lock,
810
0
            git_path("gc.log"),
811
0
            LOCK_DIE_ON_ERROR);
812
0
    dup2(get_lock_file_fd(&log_lock), 2);
813
0
    atexit(process_log_file_at_exit);
814
0
  }
815
816
0
  gc_before_repack(&opts, &cfg);
817
818
0
  if (!repository_format_precious_objects) {
819
0
    struct child_process repack_cmd = CHILD_PROCESS_INIT;
820
821
0
    repack_cmd.git_cmd = 1;
822
0
    repack_cmd.close_object_store = 1;
823
0
    strvec_pushv(&repack_cmd.args, repack.v);
824
0
    if (run_command(&repack_cmd))
825
0
      die(FAILED_RUN, repack.v[0]);
826
827
0
    if (cfg.prune_expire) {
828
0
      struct child_process prune_cmd = CHILD_PROCESS_INIT;
829
830
      /* run `git prune` even if using cruft packs */
831
0
      strvec_push(&prune, cfg.prune_expire);
832
0
      if (quiet)
833
0
        strvec_push(&prune, "--no-progress");
834
0
      if (repo_has_promisor_remote(the_repository))
835
0
        strvec_push(&prune,
836
0
              "--exclude-promisor-objects");
837
0
      prune_cmd.git_cmd = 1;
838
0
      strvec_pushv(&prune_cmd.args, prune.v);
839
0
      if (run_command(&prune_cmd))
840
0
        die(FAILED_RUN, prune.v[0]);
841
0
    }
842
0
  }
843
844
0
  if (cfg.prune_worktrees_expire) {
845
0
    struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
846
847
0
    strvec_push(&prune_worktrees, cfg.prune_worktrees_expire);
848
0
    prune_worktrees_cmd.git_cmd = 1;
849
0
    strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v);
850
0
    if (run_command(&prune_worktrees_cmd))
851
0
      die(FAILED_RUN, prune_worktrees.v[0]);
852
0
  }
853
854
0
  rerere_cmd.git_cmd = 1;
855
0
  strvec_pushv(&rerere_cmd.args, rerere.v);
856
0
  if (run_command(&rerere_cmd))
857
0
    die(FAILED_RUN, rerere.v[0]);
858
859
0
  report_garbage = report_pack_garbage;
860
0
  reprepare_packed_git(the_repository);
861
0
  if (pack_garbage.nr > 0) {
862
0
    close_object_store(the_repository->objects);
863
0
    clean_pack_garbage();
864
0
  }
865
866
0
  if (the_repository->settings.gc_write_commit_graph == 1)
867
0
    write_commit_graph_reachable(the_repository->objects->odb,
868
0
               !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
869
0
               NULL);
870
871
0
  if (opts.auto_flag && too_many_loose_objects(&cfg))
872
0
    warning(_("There are too many unreachable loose objects; "
873
0
      "run 'git prune' to remove them."));
874
875
0
  if (!daemonized)
876
0
    unlink(git_path("gc.log"));
877
878
0
out:
879
0
  gc_config_release(&cfg);
880
0
  return 0;
881
0
}
882
883
static const char *const builtin_maintenance_run_usage[] = {
884
  N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>] [--schedule]"),
885
  NULL
886
};
887
888
static int maintenance_opt_schedule(const struct option *opt, const char *arg,
889
            int unset)
890
0
{
891
0
  enum schedule_priority *priority = opt->value;
892
893
0
  if (unset)
894
0
    die(_("--no-schedule is not allowed"));
895
896
0
  *priority = parse_schedule(arg);
897
898
0
  if (!*priority)
899
0
    die(_("unrecognized --schedule argument '%s'"), arg);
900
901
0
  return 0;
902
0
}
903
904
/* Remember to update object flag allocation in object.h */
905
0
#define SEEN    (1u<<0)
906
907
struct cg_auto_data {
908
  int num_not_in_graph;
909
  int limit;
910
};
911
912
static int dfs_on_ref(const char *refname UNUSED,
913
          const char *referent UNUSED,
914
          const struct object_id *oid,
915
          int flags UNUSED,
916
          void *cb_data)
917
0
{
918
0
  struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
919
0
  int result = 0;
920
0
  struct object_id peeled;
921
0
  struct commit_list *stack = NULL;
922
0
  struct commit *commit;
923
924
0
  if (!peel_iterated_oid(the_repository, oid, &peeled))
925
0
    oid = &peeled;
926
0
  if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
927
0
    return 0;
928
929
0
  commit = lookup_commit(the_repository, oid);
930
0
  if (!commit)
931
0
    return 0;
932
0
  if (repo_parse_commit(the_repository, commit) ||
933
0
      commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
934
0
    return 0;
935
936
0
  data->num_not_in_graph++;
937
938
0
  if (data->num_not_in_graph >= data->limit)
939
0
    return 1;
940
941
0
  commit_list_append(commit, &stack);
942
943
0
  while (!result && stack) {
944
0
    struct commit_list *parent;
945
946
0
    commit = pop_commit(&stack);
947
948
0
    for (parent = commit->parents; parent; parent = parent->next) {
949
0
      if (repo_parse_commit(the_repository, parent->item) ||
950
0
          commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||
951
0
          parent->item->object.flags & SEEN)
952
0
        continue;
953
954
0
      parent->item->object.flags |= SEEN;
955
0
      data->num_not_in_graph++;
956
957
0
      if (data->num_not_in_graph >= data->limit) {
958
0
        result = 1;
959
0
        break;
960
0
      }
961
962
0
      commit_list_append(parent->item, &stack);
963
0
    }
964
0
  }
965
966
0
  free_commit_list(stack);
967
0
  return result;
968
0
}
969
970
static int should_write_commit_graph(struct gc_config *cfg UNUSED)
971
0
{
972
0
  int result;
973
0
  struct cg_auto_data data;
974
975
0
  data.num_not_in_graph = 0;
976
0
  data.limit = 100;
977
0
  git_config_get_int("maintenance.commit-graph.auto",
978
0
         &data.limit);
979
980
0
  if (!data.limit)
981
0
    return 0;
982
0
  if (data.limit < 0)
983
0
    return 1;
984
985
0
  result = refs_for_each_ref(get_main_ref_store(the_repository),
986
0
           dfs_on_ref, &data);
987
988
0
  repo_clear_commit_marks(the_repository, SEEN);
989
990
0
  return result;
991
0
}
992
993
static int run_write_commit_graph(struct maintenance_run_opts *opts)
994
0
{
995
0
  struct child_process child = CHILD_PROCESS_INIT;
996
997
0
  child.git_cmd = child.close_object_store = 1;
998
0
  strvec_pushl(&child.args, "commit-graph", "write",
999
0
         "--split", "--reachable", NULL);
1000
1001
0
  if (opts->quiet)
1002
0
    strvec_push(&child.args, "--no-progress");
1003
1004
0
  return !!run_command(&child);
1005
0
}
1006
1007
static int maintenance_task_commit_graph(struct maintenance_run_opts *opts,
1008
           struct gc_config *cfg UNUSED)
1009
0
{
1010
0
  prepare_repo_settings(the_repository);
1011
0
  if (!the_repository->settings.core_commit_graph)
1012
0
    return 0;
1013
1014
0
  if (run_write_commit_graph(opts)) {
1015
0
    error(_("failed to write commit-graph"));
1016
0
    return 1;
1017
0
  }
1018
1019
0
  return 0;
1020
0
}
1021
1022
static int fetch_remote(struct remote *remote, void *cbdata)
1023
0
{
1024
0
  struct maintenance_run_opts *opts = cbdata;
1025
0
  struct child_process child = CHILD_PROCESS_INIT;
1026
1027
0
  if (remote->skip_default_update)
1028
0
    return 0;
1029
1030
0
  child.git_cmd = 1;
1031
0
  strvec_pushl(&child.args, "fetch", remote->name,
1032
0
         "--prefetch", "--prune", "--no-tags",
1033
0
         "--no-write-fetch-head", "--recurse-submodules=no",
1034
0
         NULL);
1035
1036
0
  if (opts->quiet)
1037
0
    strvec_push(&child.args, "--quiet");
1038
1039
0
  return !!run_command(&child);
1040
0
}
1041
1042
static int maintenance_task_prefetch(struct maintenance_run_opts *opts,
1043
             struct gc_config *cfg UNUSED)
1044
0
{
1045
0
  if (for_each_remote(fetch_remote, opts)) {
1046
0
    error(_("failed to prefetch remotes"));
1047
0
    return 1;
1048
0
  }
1049
1050
0
  return 0;
1051
0
}
1052
1053
static int maintenance_task_gc(struct maintenance_run_opts *opts,
1054
             struct gc_config *cfg UNUSED)
1055
0
{
1056
0
  struct child_process child = CHILD_PROCESS_INIT;
1057
1058
0
  child.git_cmd = child.close_object_store = 1;
1059
0
  strvec_push(&child.args, "gc");
1060
1061
0
  if (opts->auto_flag)
1062
0
    strvec_push(&child.args, "--auto");
1063
0
  if (opts->quiet)
1064
0
    strvec_push(&child.args, "--quiet");
1065
0
  else
1066
0
    strvec_push(&child.args, "--no-quiet");
1067
0
  strvec_push(&child.args, "--no-detach");
1068
1069
0
  return run_command(&child);
1070
0
}
1071
1072
static int prune_packed(struct maintenance_run_opts *opts)
1073
0
{
1074
0
  struct child_process child = CHILD_PROCESS_INIT;
1075
1076
0
  child.git_cmd = 1;
1077
0
  strvec_push(&child.args, "prune-packed");
1078
1079
0
  if (opts->quiet)
1080
0
    strvec_push(&child.args, "--quiet");
1081
1082
0
  return !!run_command(&child);
1083
0
}
1084
1085
struct write_loose_object_data {
1086
  FILE *in;
1087
  int count;
1088
  int batch_size;
1089
};
1090
1091
static int loose_object_auto_limit = 100;
1092
1093
static int loose_object_count(const struct object_id *oid UNUSED,
1094
            const char *path UNUSED,
1095
            void *data)
1096
0
{
1097
0
  int *count = (int*)data;
1098
0
  if (++(*count) >= loose_object_auto_limit)
1099
0
    return 1;
1100
0
  return 0;
1101
0
}
1102
1103
static int loose_object_auto_condition(struct gc_config *cfg UNUSED)
1104
0
{
1105
0
  int count = 0;
1106
1107
0
  git_config_get_int("maintenance.loose-objects.auto",
1108
0
         &loose_object_auto_limit);
1109
1110
0
  if (!loose_object_auto_limit)
1111
0
    return 0;
1112
0
  if (loose_object_auto_limit < 0)
1113
0
    return 1;
1114
1115
0
  return for_each_loose_file_in_objdir(the_repository->objects->odb->path,
1116
0
               loose_object_count,
1117
0
               NULL, NULL, &count);
1118
0
}
1119
1120
static int bail_on_loose(const struct object_id *oid UNUSED,
1121
       const char *path UNUSED,
1122
       void *data UNUSED)
1123
0
{
1124
0
  return 1;
1125
0
}
1126
1127
static int write_loose_object_to_stdin(const struct object_id *oid,
1128
               const char *path UNUSED,
1129
               void *data)
1130
0
{
1131
0
  struct write_loose_object_data *d = (struct write_loose_object_data *)data;
1132
1133
0
  fprintf(d->in, "%s\n", oid_to_hex(oid));
1134
1135
0
  return ++(d->count) > d->batch_size;
1136
0
}
1137
1138
static int pack_loose(struct maintenance_run_opts *opts)
1139
0
{
1140
0
  struct repository *r = the_repository;
1141
0
  int result = 0;
1142
0
  struct write_loose_object_data data;
1143
0
  struct child_process pack_proc = CHILD_PROCESS_INIT;
1144
1145
  /*
1146
   * Do not start pack-objects process
1147
   * if there are no loose objects.
1148
   */
1149
0
  if (!for_each_loose_file_in_objdir(r->objects->odb->path,
1150
0
             bail_on_loose,
1151
0
             NULL, NULL, NULL))
1152
0
    return 0;
1153
1154
0
  pack_proc.git_cmd = 1;
1155
1156
0
  strvec_push(&pack_proc.args, "pack-objects");
1157
0
  if (opts->quiet)
1158
0
    strvec_push(&pack_proc.args, "--quiet");
1159
0
  strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->odb->path);
1160
1161
0
  pack_proc.in = -1;
1162
1163
  /*
1164
   * git-pack-objects(1) ends up writing the pack hash to stdout, which
1165
   * we do not care for.
1166
   */
1167
0
  pack_proc.out = -1;
1168
1169
0
  if (start_command(&pack_proc)) {
1170
0
    error(_("failed to start 'git pack-objects' process"));
1171
0
    return 1;
1172
0
  }
1173
1174
0
  data.in = xfdopen(pack_proc.in, "w");
1175
0
  data.count = 0;
1176
0
  data.batch_size = 50000;
1177
1178
0
  for_each_loose_file_in_objdir(r->objects->odb->path,
1179
0
              write_loose_object_to_stdin,
1180
0
              NULL,
1181
0
              NULL,
1182
0
              &data);
1183
1184
0
  fclose(data.in);
1185
1186
0
  if (finish_command(&pack_proc)) {
1187
0
    error(_("failed to finish 'git pack-objects' process"));
1188
0
    result = 1;
1189
0
  }
1190
1191
0
  return result;
1192
0
}
1193
1194
static int maintenance_task_loose_objects(struct maintenance_run_opts *opts,
1195
            struct gc_config *cfg UNUSED)
1196
0
{
1197
0
  return prune_packed(opts) || pack_loose(opts);
1198
0
}
1199
1200
static int incremental_repack_auto_condition(struct gc_config *cfg UNUSED)
1201
0
{
1202
0
  struct packed_git *p;
1203
0
  int incremental_repack_auto_limit = 10;
1204
0
  int count = 0;
1205
1206
0
  prepare_repo_settings(the_repository);
1207
0
  if (!the_repository->settings.core_multi_pack_index)
1208
0
    return 0;
1209
1210
0
  git_config_get_int("maintenance.incremental-repack.auto",
1211
0
         &incremental_repack_auto_limit);
1212
1213
0
  if (!incremental_repack_auto_limit)
1214
0
    return 0;
1215
0
  if (incremental_repack_auto_limit < 0)
1216
0
    return 1;
1217
1218
0
  for (p = get_packed_git(the_repository);
1219
0
       count < incremental_repack_auto_limit && p;
1220
0
       p = p->next) {
1221
0
    if (!p->multi_pack_index)
1222
0
      count++;
1223
0
  }
1224
1225
0
  return count >= incremental_repack_auto_limit;
1226
0
}
1227
1228
static int multi_pack_index_write(struct maintenance_run_opts *opts)
1229
0
{
1230
0
  struct child_process child = CHILD_PROCESS_INIT;
1231
1232
0
  child.git_cmd = 1;
1233
0
  strvec_pushl(&child.args, "multi-pack-index", "write", NULL);
1234
1235
0
  if (opts->quiet)
1236
0
    strvec_push(&child.args, "--no-progress");
1237
1238
0
  if (run_command(&child))
1239
0
    return error(_("failed to write multi-pack-index"));
1240
1241
0
  return 0;
1242
0
}
1243
1244
static int multi_pack_index_expire(struct maintenance_run_opts *opts)
1245
0
{
1246
0
  struct child_process child = CHILD_PROCESS_INIT;
1247
1248
0
  child.git_cmd = child.close_object_store = 1;
1249
0
  strvec_pushl(&child.args, "multi-pack-index", "expire", NULL);
1250
1251
0
  if (opts->quiet)
1252
0
    strvec_push(&child.args, "--no-progress");
1253
1254
0
  if (run_command(&child))
1255
0
    return error(_("'git multi-pack-index expire' failed"));
1256
1257
0
  return 0;
1258
0
}
1259
1260
0
#define TWO_GIGABYTES (INT32_MAX)
1261
1262
static off_t get_auto_pack_size(void)
1263
0
{
1264
  /*
1265
   * The "auto" value is special: we optimize for
1266
   * one large pack-file (i.e. from a clone) and
1267
   * expect the rest to be small and they can be
1268
   * repacked quickly.
1269
   *
1270
   * The strategy we select here is to select a
1271
   * size that is one more than the second largest
1272
   * pack-file. This ensures that we will repack
1273
   * at least two packs if there are three or more
1274
   * packs.
1275
   */
1276
0
  off_t max_size = 0;
1277
0
  off_t second_largest_size = 0;
1278
0
  off_t result_size;
1279
0
  struct packed_git *p;
1280
0
  struct repository *r = the_repository;
1281
1282
0
  reprepare_packed_git(r);
1283
0
  for (p = get_all_packs(r); p; p = p->next) {
1284
0
    if (p->pack_size > max_size) {
1285
0
      second_largest_size = max_size;
1286
0
      max_size = p->pack_size;
1287
0
    } else if (p->pack_size > second_largest_size)
1288
0
      second_largest_size = p->pack_size;
1289
0
  }
1290
1291
0
  result_size = second_largest_size + 1;
1292
1293
  /* But limit ourselves to a batch size of 2g */
1294
0
  if (result_size > TWO_GIGABYTES)
1295
0
    result_size = TWO_GIGABYTES;
1296
1297
0
  return result_size;
1298
0
}
1299
1300
static int multi_pack_index_repack(struct maintenance_run_opts *opts)
1301
0
{
1302
0
  struct child_process child = CHILD_PROCESS_INIT;
1303
1304
0
  child.git_cmd = child.close_object_store = 1;
1305
0
  strvec_pushl(&child.args, "multi-pack-index", "repack", NULL);
1306
1307
0
  if (opts->quiet)
1308
0
    strvec_push(&child.args, "--no-progress");
1309
1310
0
  strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
1311
0
          (uintmax_t)get_auto_pack_size());
1312
1313
0
  if (run_command(&child))
1314
0
    return error(_("'git multi-pack-index repack' failed"));
1315
1316
0
  return 0;
1317
0
}
1318
1319
static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts,
1320
                 struct gc_config *cfg UNUSED)
1321
0
{
1322
0
  prepare_repo_settings(the_repository);
1323
0
  if (!the_repository->settings.core_multi_pack_index) {
1324
0
    warning(_("skipping incremental-repack task because core.multiPackIndex is disabled"));
1325
0
    return 0;
1326
0
  }
1327
1328
0
  if (multi_pack_index_write(opts))
1329
0
    return 1;
1330
0
  if (multi_pack_index_expire(opts))
1331
0
    return 1;
1332
0
  if (multi_pack_index_repack(opts))
1333
0
    return 1;
1334
0
  return 0;
1335
0
}
1336
1337
typedef int maintenance_task_fn(struct maintenance_run_opts *opts,
1338
        struct gc_config *cfg);
1339
1340
/*
1341
 * An auto condition function returns 1 if the task should run
1342
 * and 0 if the task should NOT run. See needs_to_gc() for an
1343
 * example.
1344
 */
1345
typedef int maintenance_auto_fn(struct gc_config *cfg);
1346
1347
struct maintenance_task {
1348
  const char *name;
1349
  maintenance_task_fn *fn;
1350
  maintenance_auto_fn *auto_condition;
1351
  unsigned enabled:1;
1352
1353
  enum schedule_priority schedule;
1354
1355
  /* -1 if not selected. */
1356
  int selected_order;
1357
};
1358
1359
enum maintenance_task_label {
1360
  TASK_PREFETCH,
1361
  TASK_LOOSE_OBJECTS,
1362
  TASK_INCREMENTAL_REPACK,
1363
  TASK_GC,
1364
  TASK_COMMIT_GRAPH,
1365
  TASK_PACK_REFS,
1366
1367
  /* Leave as final value */
1368
  TASK__COUNT
1369
};
1370
1371
static struct maintenance_task tasks[] = {
1372
  [TASK_PREFETCH] = {
1373
    "prefetch",
1374
    maintenance_task_prefetch,
1375
  },
1376
  [TASK_LOOSE_OBJECTS] = {
1377
    "loose-objects",
1378
    maintenance_task_loose_objects,
1379
    loose_object_auto_condition,
1380
  },
1381
  [TASK_INCREMENTAL_REPACK] = {
1382
    "incremental-repack",
1383
    maintenance_task_incremental_repack,
1384
    incremental_repack_auto_condition,
1385
  },
1386
  [TASK_GC] = {
1387
    "gc",
1388
    maintenance_task_gc,
1389
    need_to_gc,
1390
    1,
1391
  },
1392
  [TASK_COMMIT_GRAPH] = {
1393
    "commit-graph",
1394
    maintenance_task_commit_graph,
1395
    should_write_commit_graph,
1396
  },
1397
  [TASK_PACK_REFS] = {
1398
    "pack-refs",
1399
    maintenance_task_pack_refs,
1400
    pack_refs_condition,
1401
  },
1402
};
1403
1404
static int compare_tasks_by_selection(const void *a_, const void *b_)
1405
0
{
1406
0
  const struct maintenance_task *a = a_;
1407
0
  const struct maintenance_task *b = b_;
1408
1409
0
  return b->selected_order - a->selected_order;
1410
0
}
1411
1412
static int maintenance_run_tasks(struct maintenance_run_opts *opts,
1413
         struct gc_config *cfg)
1414
0
{
1415
0
  int i, found_selected = 0;
1416
0
  int result = 0;
1417
0
  struct lock_file lk;
1418
0
  struct repository *r = the_repository;
1419
0
  char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
1420
1421
0
  if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
1422
    /*
1423
     * Another maintenance command is running.
1424
     *
1425
     * If --auto was provided, then it is likely due to a
1426
     * recursive process stack. Do not report an error in
1427
     * that case.
1428
     */
1429
0
    if (!opts->auto_flag && !opts->quiet)
1430
0
      warning(_("lock file '%s' exists, skipping maintenance"),
1431
0
        lock_path);
1432
0
    free(lock_path);
1433
0
    return 0;
1434
0
  }
1435
0
  free(lock_path);
1436
1437
  /* Failure to daemonize is ok, we'll continue in foreground. */
1438
0
  if (opts->detach > 0) {
1439
0
    trace2_region_enter("maintenance", "detach", the_repository);
1440
0
    daemonize();
1441
0
    trace2_region_leave("maintenance", "detach", the_repository);
1442
0
  }
1443
1444
0
  for (i = 0; !found_selected && i < TASK__COUNT; i++)
1445
0
    found_selected = tasks[i].selected_order >= 0;
1446
1447
0
  if (found_selected)
1448
0
    QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);
1449
1450
0
  for (i = 0; i < TASK__COUNT; i++) {
1451
0
    if (found_selected && tasks[i].selected_order < 0)
1452
0
      continue;
1453
1454
0
    if (!found_selected && !tasks[i].enabled)
1455
0
      continue;
1456
1457
0
    if (opts->auto_flag &&
1458
0
        (!tasks[i].auto_condition ||
1459
0
         !tasks[i].auto_condition(cfg)))
1460
0
      continue;
1461
1462
0
    if (opts->schedule && tasks[i].schedule < opts->schedule)
1463
0
      continue;
1464
1465
0
    trace2_region_enter("maintenance", tasks[i].name, r);
1466
0
    if (tasks[i].fn(opts, cfg)) {
1467
0
      error(_("task '%s' failed"), tasks[i].name);
1468
0
      result = 1;
1469
0
    }
1470
0
    trace2_region_leave("maintenance", tasks[i].name, r);
1471
0
  }
1472
1473
0
  rollback_lock_file(&lk);
1474
0
  return result;
1475
0
}
1476
1477
static void initialize_maintenance_strategy(void)
1478
0
{
1479
0
  char *config_str;
1480
1481
0
  if (git_config_get_string("maintenance.strategy", &config_str))
1482
0
    return;
1483
1484
0
  if (!strcasecmp(config_str, "incremental")) {
1485
0
    tasks[TASK_GC].schedule = SCHEDULE_NONE;
1486
0
    tasks[TASK_COMMIT_GRAPH].enabled = 1;
1487
0
    tasks[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY;
1488
0
    tasks[TASK_PREFETCH].enabled = 1;
1489
0
    tasks[TASK_PREFETCH].schedule = SCHEDULE_HOURLY;
1490
0
    tasks[TASK_INCREMENTAL_REPACK].enabled = 1;
1491
0
    tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY;
1492
0
    tasks[TASK_LOOSE_OBJECTS].enabled = 1;
1493
0
    tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY;
1494
0
    tasks[TASK_PACK_REFS].enabled = 1;
1495
0
    tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY;
1496
0
  }
1497
0
}
1498
1499
static void initialize_task_config(int schedule)
1500
0
{
1501
0
  int i;
1502
0
  struct strbuf config_name = STRBUF_INIT;
1503
1504
0
  if (schedule)
1505
0
    initialize_maintenance_strategy();
1506
1507
0
  for (i = 0; i < TASK__COUNT; i++) {
1508
0
    int config_value;
1509
0
    char *config_str;
1510
1511
0
    strbuf_reset(&config_name);
1512
0
    strbuf_addf(&config_name, "maintenance.%s.enabled",
1513
0
          tasks[i].name);
1514
1515
0
    if (!git_config_get_bool(config_name.buf, &config_value))
1516
0
      tasks[i].enabled = config_value;
1517
1518
0
    strbuf_reset(&config_name);
1519
0
    strbuf_addf(&config_name, "maintenance.%s.schedule",
1520
0
          tasks[i].name);
1521
1522
0
    if (!git_config_get_string(config_name.buf, &config_str)) {
1523
0
      tasks[i].schedule = parse_schedule(config_str);
1524
0
      free(config_str);
1525
0
    }
1526
0
  }
1527
1528
0
  strbuf_release(&config_name);
1529
0
}
1530
1531
static int task_option_parse(const struct option *opt UNUSED,
1532
           const char *arg, int unset)
1533
0
{
1534
0
  int i, num_selected = 0;
1535
0
  struct maintenance_task *task = NULL;
1536
1537
0
  BUG_ON_OPT_NEG(unset);
1538
1539
0
  for (i = 0; i < TASK__COUNT; i++) {
1540
0
    if (tasks[i].selected_order >= 0)
1541
0
      num_selected++;
1542
0
    if (!strcasecmp(tasks[i].name, arg)) {
1543
0
      task = &tasks[i];
1544
0
    }
1545
0
  }
1546
1547
0
  if (!task) {
1548
0
    error(_("'%s' is not a valid task"), arg);
1549
0
    return 1;
1550
0
  }
1551
1552
0
  if (task->selected_order >= 0) {
1553
0
    error(_("task '%s' cannot be selected multiple times"), arg);
1554
0
    return 1;
1555
0
  }
1556
1557
0
  task->selected_order = num_selected + 1;
1558
1559
0
  return 0;
1560
0
}
1561
1562
static int maintenance_run(int argc, const char **argv, const char *prefix)
1563
0
{
1564
0
  int i;
1565
0
  struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
1566
0
  struct gc_config cfg = GC_CONFIG_INIT;
1567
0
  struct option builtin_maintenance_run_options[] = {
1568
0
    OPT_BOOL(0, "auto", &opts.auto_flag,
1569
0
       N_("run tasks based on the state of the repository")),
1570
0
    OPT_BOOL(0, "detach", &opts.detach,
1571
0
       N_("perform maintenance in the background")),
1572
0
    OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"),
1573
0
           N_("run tasks based on frequency"),
1574
0
           maintenance_opt_schedule),
1575
0
    OPT_BOOL(0, "quiet", &opts.quiet,
1576
0
       N_("do not report progress or other information over stderr")),
1577
0
    OPT_CALLBACK_F(0, "task", NULL, N_("task"),
1578
0
      N_("run a specific task"),
1579
0
      PARSE_OPT_NONEG, task_option_parse),
1580
0
    OPT_END()
1581
0
  };
1582
0
  int ret;
1583
1584
0
  opts.quiet = !isatty(2);
1585
1586
0
  for (i = 0; i < TASK__COUNT; i++)
1587
0
    tasks[i].selected_order = -1;
1588
1589
0
  argc = parse_options(argc, argv, prefix,
1590
0
           builtin_maintenance_run_options,
1591
0
           builtin_maintenance_run_usage,
1592
0
           PARSE_OPT_STOP_AT_NON_OPTION);
1593
1594
0
  if (opts.auto_flag && opts.schedule)
1595
0
    die(_("use at most one of --auto and --schedule=<frequency>"));
1596
1597
0
  gc_config(&cfg);
1598
0
  initialize_task_config(opts.schedule);
1599
1600
0
  if (argc != 0)
1601
0
    usage_with_options(builtin_maintenance_run_usage,
1602
0
           builtin_maintenance_run_options);
1603
1604
0
  ret = maintenance_run_tasks(&opts, &cfg);
1605
0
  gc_config_release(&cfg);
1606
0
  return ret;
1607
0
}
1608
1609
static char *get_maintpath(void)
1610
0
{
1611
0
  struct strbuf sb = STRBUF_INIT;
1612
0
  const char *p = the_repository->worktree ?
1613
0
    the_repository->worktree : the_repository->gitdir;
1614
1615
0
  strbuf_realpath(&sb, p, 1);
1616
0
  return strbuf_detach(&sb, NULL);
1617
0
}
1618
1619
static char const * const builtin_maintenance_register_usage[] = {
1620
  "git maintenance register [--config-file <path>]",
1621
  NULL
1622
};
1623
1624
static int maintenance_register(int argc, const char **argv, const char *prefix)
1625
0
{
1626
0
  char *config_file = NULL;
1627
0
  struct option options[] = {
1628
0
    OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
1629
0
    OPT_END(),
1630
0
  };
1631
0
  int found = 0;
1632
0
  const char *key = "maintenance.repo";
1633
0
  char *maintpath = get_maintpath();
1634
0
  struct string_list_item *item;
1635
0
  const struct string_list *list;
1636
1637
0
  argc = parse_options(argc, argv, prefix, options,
1638
0
           builtin_maintenance_register_usage, 0);
1639
0
  if (argc)
1640
0
    usage_with_options(builtin_maintenance_register_usage,
1641
0
           options);
1642
1643
  /* Disable foreground maintenance */
1644
0
  git_config_set("maintenance.auto", "false");
1645
1646
  /* Set maintenance strategy, if unset */
1647
0
  if (git_config_get("maintenance.strategy"))
1648
0
    git_config_set("maintenance.strategy", "incremental");
1649
1650
0
  if (!git_config_get_string_multi(key, &list)) {
1651
0
    for_each_string_list_item(item, list) {
1652
0
      if (!strcmp(maintpath, item->string)) {
1653
0
        found = 1;
1654
0
        break;
1655
0
      }
1656
0
    }
1657
0
  }
1658
1659
0
  if (!found) {
1660
0
    int rc;
1661
0
    char *global_config_file = NULL;
1662
1663
0
    if (!config_file) {
1664
0
      global_config_file = git_global_config();
1665
0
      config_file = global_config_file;
1666
0
    }
1667
0
    if (!config_file)
1668
0
      die(_("$HOME not set"));
1669
0
    rc = git_config_set_multivar_in_file_gently(
1670
0
      config_file, "maintenance.repo", maintpath,
1671
0
      CONFIG_REGEX_NONE, NULL, 0);
1672
0
    free(global_config_file);
1673
1674
0
    if (rc)
1675
0
      die(_("unable to add '%s' value of '%s'"),
1676
0
          key, maintpath);
1677
0
  }
1678
1679
0
  free(maintpath);
1680
0
  return 0;
1681
0
}
1682
1683
static char const * const builtin_maintenance_unregister_usage[] = {
1684
  "git maintenance unregister [--config-file <path>] [--force]",
1685
  NULL
1686
};
1687
1688
static int maintenance_unregister(int argc, const char **argv, const char *prefix)
1689
0
{
1690
0
  int force = 0;
1691
0
  char *config_file = NULL;
1692
0
  struct option options[] = {
1693
0
    OPT_STRING(0, "config-file", &config_file, N_("file"), N_("use given config file")),
1694
0
    OPT__FORCE(&force,
1695
0
         N_("return success even if repository was not registered"),
1696
0
         PARSE_OPT_NOCOMPLETE),
1697
0
    OPT_END(),
1698
0
  };
1699
0
  const char *key = "maintenance.repo";
1700
0
  char *maintpath = get_maintpath();
1701
0
  int found = 0;
1702
0
  struct string_list_item *item;
1703
0
  const struct string_list *list;
1704
0
  struct config_set cs = { { 0 } };
1705
1706
0
  argc = parse_options(argc, argv, prefix, options,
1707
0
           builtin_maintenance_unregister_usage, 0);
1708
0
  if (argc)
1709
0
    usage_with_options(builtin_maintenance_unregister_usage,
1710
0
           options);
1711
1712
0
  if (config_file) {
1713
0
    git_configset_init(&cs);
1714
0
    git_configset_add_file(&cs, config_file);
1715
0
  }
1716
0
  if (!(config_file
1717
0
        ? git_configset_get_string_multi(&cs, key, &list)
1718
0
        : git_config_get_string_multi(key, &list))) {
1719
0
    for_each_string_list_item(item, list) {
1720
0
      if (!strcmp(maintpath, item->string)) {
1721
0
        found = 1;
1722
0
        break;
1723
0
      }
1724
0
    }
1725
0
  }
1726
1727
0
  if (found) {
1728
0
    int rc;
1729
0
    char *global_config_file = NULL;
1730
1731
0
    if (!config_file) {
1732
0
      global_config_file = git_global_config();
1733
0
      config_file = global_config_file;
1734
0
    }
1735
0
    if (!config_file)
1736
0
      die(_("$HOME not set"));
1737
0
    rc = git_config_set_multivar_in_file_gently(
1738
0
      config_file, key, NULL, maintpath, NULL,
1739
0
      CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
1740
0
    free(global_config_file);
1741
1742
0
    if (rc &&
1743
0
        (!force || rc == CONFIG_NOTHING_SET))
1744
0
      die(_("unable to unset '%s' value of '%s'"),
1745
0
          key, maintpath);
1746
0
  } else if (!force) {
1747
0
    die(_("repository '%s' is not registered"), maintpath);
1748
0
  }
1749
1750
0
  git_configset_clear(&cs);
1751
0
  free(maintpath);
1752
0
  return 0;
1753
0
}
1754
1755
static const char *get_frequency(enum schedule_priority schedule)
1756
0
{
1757
0
  switch (schedule) {
1758
0
  case SCHEDULE_HOURLY:
1759
0
    return "hourly";
1760
0
  case SCHEDULE_DAILY:
1761
0
    return "daily";
1762
0
  case SCHEDULE_WEEKLY:
1763
0
    return "weekly";
1764
0
  default:
1765
0
    BUG("invalid schedule %d", schedule);
1766
0
  }
1767
0
}
1768
1769
/*
1770
 * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
1771
 * to mock the schedulers that `git maintenance start` rely on.
1772
 *
1773
 * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
1774
 * list of colon-separated key/value pairs where each pair contains a scheduler
1775
 * and its corresponding mock.
1776
 *
1777
 * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
1778
 *   arguments unmodified.
1779
 *
1780
 * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
1781
 *   In this case, the *cmd value is read as input.
1782
 *
1783
 *   * if the input value *cmd is the key of one of the comma-separated list
1784
 *     item, then *is_available is set to true and *cmd is modified and becomes
1785
 *     the mock command.
1786
 *
1787
 *   * if the input value *cmd isn’t the key of any of the comma-separated list
1788
 *     item, then *is_available is set to false.
1789
 *
1790
 * Ex.:
1791
 *   GIT_TEST_MAINT_SCHEDULER not set
1792
 *     +-------+-------------------------------------------------+
1793
 *     | Input |                     Output                      |
1794
 *     | *cmd  | return code |       *cmd        | *is_available |
1795
 *     +-------+-------------+-------------------+---------------+
1796
 *     | "foo" |    false    | "foo" (unchanged) |  (unchanged)  |
1797
 *     +-------+-------------+-------------------+---------------+
1798
 *
1799
 *   GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
1800
 *     +-------+-------------------------------------------------+
1801
 *     | Input |                     Output                      |
1802
 *     | *cmd  | return code |       *cmd        | *is_available |
1803
 *     +-------+-------------+-------------------+---------------+
1804
 *     | "foo" |    true     |  "./mock.foo.sh"  |     true      |
1805
 *     | "qux" |    true     | "qux" (unchanged) |     false     |
1806
 *     +-------+-------------+-------------------+---------------+
1807
 */
1808
static int get_schedule_cmd(const char **cmd, int *is_available)
1809
0
{
1810
0
  char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
1811
0
  struct string_list_item *item;
1812
0
  struct string_list list = STRING_LIST_INIT_NODUP;
1813
1814
0
  if (!testing)
1815
0
    return 0;
1816
1817
0
  if (is_available)
1818
0
    *is_available = 0;
1819
1820
0
  string_list_split_in_place(&list, testing, ",", -1);
1821
0
  for_each_string_list_item(item, &list) {
1822
0
    struct string_list pair = STRING_LIST_INIT_NODUP;
1823
1824
0
    if (string_list_split_in_place(&pair, item->string, ":", 2) != 2)
1825
0
      continue;
1826
1827
0
    if (!strcmp(*cmd, pair.items[0].string)) {
1828
0
      *cmd = pair.items[1].string;
1829
0
      if (is_available)
1830
0
        *is_available = 1;
1831
0
      string_list_clear(&list, 0);
1832
0
      UNLEAK(testing);
1833
0
      return 1;
1834
0
    }
1835
0
  }
1836
1837
0
  string_list_clear(&list, 0);
1838
0
  free(testing);
1839
0
  return 1;
1840
0
}
1841
1842
static int get_random_minute(void)
1843
0
{
1844
  /* Use a static value when under tests. */
1845
0
  if (getenv("GIT_TEST_MAINT_SCHEDULER"))
1846
0
    return 13;
1847
1848
0
  return git_rand() % 60;
1849
0
}
1850
1851
static int is_launchctl_available(void)
1852
0
{
1853
0
  const char *cmd = "launchctl";
1854
0
  int is_available;
1855
0
  if (get_schedule_cmd(&cmd, &is_available))
1856
0
    return is_available;
1857
1858
#ifdef __APPLE__
1859
  return 1;
1860
#else
1861
0
  return 0;
1862
0
#endif
1863
0
}
1864
1865
static char *launchctl_service_name(const char *frequency)
1866
0
{
1867
0
  struct strbuf label = STRBUF_INIT;
1868
0
  strbuf_addf(&label, "org.git-scm.git.%s", frequency);
1869
0
  return strbuf_detach(&label, NULL);
1870
0
}
1871
1872
static char *launchctl_service_filename(const char *name)
1873
0
{
1874
0
  char *expanded;
1875
0
  struct strbuf filename = STRBUF_INIT;
1876
0
  strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
1877
1878
0
  expanded = interpolate_path(filename.buf, 1);
1879
0
  if (!expanded)
1880
0
    die(_("failed to expand path '%s'"), filename.buf);
1881
1882
0
  strbuf_release(&filename);
1883
0
  return expanded;
1884
0
}
1885
1886
static char *launchctl_get_uid(void)
1887
0
{
1888
0
  return xstrfmt("gui/%d", getuid());
1889
0
}
1890
1891
static int launchctl_boot_plist(int enable, const char *filename)
1892
0
{
1893
0
  const char *cmd = "launchctl";
1894
0
  int result;
1895
0
  struct child_process child = CHILD_PROCESS_INIT;
1896
0
  char *uid = launchctl_get_uid();
1897
1898
0
  get_schedule_cmd(&cmd, NULL);
1899
0
  strvec_split(&child.args, cmd);
1900
0
  strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
1901
0
         filename, NULL);
1902
1903
0
  child.no_stderr = 1;
1904
0
  child.no_stdout = 1;
1905
1906
0
  if (start_command(&child))
1907
0
    die(_("failed to start launchctl"));
1908
1909
0
  result = finish_command(&child);
1910
1911
0
  free(uid);
1912
0
  return result;
1913
0
}
1914
1915
static int launchctl_remove_plist(enum schedule_priority schedule)
1916
0
{
1917
0
  const char *frequency = get_frequency(schedule);
1918
0
  char *name = launchctl_service_name(frequency);
1919
0
  char *filename = launchctl_service_filename(name);
1920
0
  int result = launchctl_boot_plist(0, filename);
1921
0
  unlink(filename);
1922
0
  free(filename);
1923
0
  free(name);
1924
0
  return result;
1925
0
}
1926
1927
static int launchctl_remove_plists(void)
1928
0
{
1929
0
  return launchctl_remove_plist(SCHEDULE_HOURLY) ||
1930
0
         launchctl_remove_plist(SCHEDULE_DAILY) ||
1931
0
         launchctl_remove_plist(SCHEDULE_WEEKLY);
1932
0
}
1933
1934
static int launchctl_list_contains_plist(const char *name, const char *cmd)
1935
0
{
1936
0
  struct child_process child = CHILD_PROCESS_INIT;
1937
1938
0
  strvec_split(&child.args, cmd);
1939
0
  strvec_pushl(&child.args, "list", name, NULL);
1940
1941
0
  child.no_stderr = 1;
1942
0
  child.no_stdout = 1;
1943
1944
0
  if (start_command(&child))
1945
0
    die(_("failed to start launchctl"));
1946
1947
  /* Returns failure if 'name' doesn't exist. */
1948
0
  return !finish_command(&child);
1949
0
}
1950
1951
static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
1952
0
{
1953
0
  int i, fd;
1954
0
  const char *preamble, *repeat;
1955
0
  const char *frequency = get_frequency(schedule);
1956
0
  char *name = launchctl_service_name(frequency);
1957
0
  char *filename = launchctl_service_filename(name);
1958
0
  struct lock_file lk = LOCK_INIT;
1959
0
  static unsigned long lock_file_timeout_ms = ULONG_MAX;
1960
0
  struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
1961
0
  struct stat st;
1962
0
  const char *cmd = "launchctl";
1963
0
  int minute = get_random_minute();
1964
1965
0
  get_schedule_cmd(&cmd, NULL);
1966
0
  preamble = "<?xml version=\"1.0\"?>\n"
1967
0
       "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
1968
0
       "<plist version=\"1.0\">"
1969
0
       "<dict>\n"
1970
0
       "<key>Label</key><string>%s</string>\n"
1971
0
       "<key>ProgramArguments</key>\n"
1972
0
       "<array>\n"
1973
0
       "<string>%s/git</string>\n"
1974
0
       "<string>--exec-path=%s</string>\n"
1975
0
       "<string>for-each-repo</string>\n"
1976
0
       "<string>--keep-going</string>\n"
1977
0
       "<string>--config=maintenance.repo</string>\n"
1978
0
       "<string>maintenance</string>\n"
1979
0
       "<string>run</string>\n"
1980
0
       "<string>--schedule=%s</string>\n"
1981
0
       "</array>\n"
1982
0
       "<key>StartCalendarInterval</key>\n"
1983
0
       "<array>\n";
1984
0
  strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
1985
1986
0
  switch (schedule) {
1987
0
  case SCHEDULE_HOURLY:
1988
0
    repeat = "<dict>\n"
1989
0
       "<key>Hour</key><integer>%d</integer>\n"
1990
0
       "<key>Minute</key><integer>%d</integer>\n"
1991
0
       "</dict>\n";
1992
0
    for (i = 1; i <= 23; i++)
1993
0
      strbuf_addf(&plist, repeat, i, minute);
1994
0
    break;
1995
1996
0
  case SCHEDULE_DAILY:
1997
0
    repeat = "<dict>\n"
1998
0
       "<key>Day</key><integer>%d</integer>\n"
1999
0
       "<key>Hour</key><integer>0</integer>\n"
2000
0
       "<key>Minute</key><integer>%d</integer>\n"
2001
0
       "</dict>\n";
2002
0
    for (i = 1; i <= 6; i++)
2003
0
      strbuf_addf(&plist, repeat, i, minute);
2004
0
    break;
2005
2006
0
  case SCHEDULE_WEEKLY:
2007
0
    strbuf_addf(&plist,
2008
0
          "<dict>\n"
2009
0
          "<key>Day</key><integer>0</integer>\n"
2010
0
          "<key>Hour</key><integer>0</integer>\n"
2011
0
          "<key>Minute</key><integer>%d</integer>\n"
2012
0
          "</dict>\n",
2013
0
          minute);
2014
0
    break;
2015
2016
0
  default:
2017
    /* unreachable */
2018
0
    break;
2019
0
  }
2020
0
  strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
2021
2022
0
  if (safe_create_leading_directories(filename))
2023
0
    die(_("failed to create directories for '%s'"), filename);
2024
2025
0
  if ((long)lock_file_timeout_ms < 0 &&
2026
0
      git_config_get_ulong("gc.launchctlplistlocktimeoutms",
2027
0
         &lock_file_timeout_ms))
2028
0
    lock_file_timeout_ms = 150;
2029
2030
0
  fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
2031
0
                 lock_file_timeout_ms);
2032
2033
  /*
2034
   * Does this file already exist? With the intended contents? Is it
2035
   * registered already? Then it does not need to be re-registered.
2036
   */
2037
0
  if (!stat(filename, &st) && st.st_size == plist.len &&
2038
0
      strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
2039
0
      !strbuf_cmp(&plist, &plist2) &&
2040
0
      launchctl_list_contains_plist(name, cmd))
2041
0
    rollback_lock_file(&lk);
2042
0
  else {
2043
0
    if (write_in_full(fd, plist.buf, plist.len) < 0 ||
2044
0
        commit_lock_file(&lk))
2045
0
      die_errno(_("could not write '%s'"), filename);
2046
2047
    /* bootout might fail if not already running, so ignore */
2048
0
    launchctl_boot_plist(0, filename);
2049
0
    if (launchctl_boot_plist(1, filename))
2050
0
      die(_("failed to bootstrap service %s"), filename);
2051
0
  }
2052
2053
0
  free(filename);
2054
0
  free(name);
2055
0
  strbuf_release(&plist);
2056
0
  strbuf_release(&plist2);
2057
0
  return 0;
2058
0
}
2059
2060
static int launchctl_add_plists(void)
2061
0
{
2062
0
  const char *exec_path = git_exec_path();
2063
2064
0
  return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
2065
0
         launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
2066
0
         launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
2067
0
}
2068
2069
static int launchctl_update_schedule(int run_maintenance, int fd UNUSED)
2070
0
{
2071
0
  if (run_maintenance)
2072
0
    return launchctl_add_plists();
2073
0
  else
2074
0
    return launchctl_remove_plists();
2075
0
}
2076
2077
static int is_schtasks_available(void)
2078
0
{
2079
0
  const char *cmd = "schtasks";
2080
0
  int is_available;
2081
0
  if (get_schedule_cmd(&cmd, &is_available))
2082
0
    return is_available;
2083
2084
#ifdef GIT_WINDOWS_NATIVE
2085
  return 1;
2086
#else
2087
0
  return 0;
2088
0
#endif
2089
0
}
2090
2091
static char *schtasks_task_name(const char *frequency)
2092
0
{
2093
0
  struct strbuf label = STRBUF_INIT;
2094
0
  strbuf_addf(&label, "Git Maintenance (%s)", frequency);
2095
0
  return strbuf_detach(&label, NULL);
2096
0
}
2097
2098
static int schtasks_remove_task(enum schedule_priority schedule)
2099
0
{
2100
0
  const char *cmd = "schtasks";
2101
0
  struct child_process child = CHILD_PROCESS_INIT;
2102
0
  const char *frequency = get_frequency(schedule);
2103
0
  char *name = schtasks_task_name(frequency);
2104
2105
0
  get_schedule_cmd(&cmd, NULL);
2106
0
  strvec_split(&child.args, cmd);
2107
0
  strvec_pushl(&child.args, "/delete", "/tn", name, "/f", NULL);
2108
0
  free(name);
2109
2110
0
  return run_command(&child);
2111
0
}
2112
2113
static int schtasks_remove_tasks(void)
2114
0
{
2115
0
  return schtasks_remove_task(SCHEDULE_HOURLY) ||
2116
0
         schtasks_remove_task(SCHEDULE_DAILY) ||
2117
0
         schtasks_remove_task(SCHEDULE_WEEKLY);
2118
0
}
2119
2120
static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
2121
0
{
2122
0
  const char *cmd = "schtasks";
2123
0
  int result;
2124
0
  struct child_process child = CHILD_PROCESS_INIT;
2125
0
  const char *xml;
2126
0
  struct tempfile *tfile;
2127
0
  const char *frequency = get_frequency(schedule);
2128
0
  char *name = schtasks_task_name(frequency);
2129
0
  struct strbuf tfilename = STRBUF_INIT;
2130
0
  int minute = get_random_minute();
2131
2132
0
  get_schedule_cmd(&cmd, NULL);
2133
2134
0
  strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
2135
0
        get_git_common_dir(), frequency);
2136
0
  tfile = xmks_tempfile(tfilename.buf);
2137
0
  strbuf_release(&tfilename);
2138
2139
0
  if (!fdopen_tempfile(tfile, "w"))
2140
0
    die(_("failed to create temp xml file"));
2141
2142
0
  xml = "<?xml version=\"1.0\" ?>\n"
2143
0
        "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
2144
0
        "<Triggers>\n"
2145
0
        "<CalendarTrigger>\n";
2146
0
  fputs(xml, tfile->fp);
2147
2148
0
  switch (schedule) {
2149
0
  case SCHEDULE_HOURLY:
2150
0
    fprintf(tfile->fp,
2151
0
      "<StartBoundary>2020-01-01T01:%02d:00</StartBoundary>\n"
2152
0
      "<Enabled>true</Enabled>\n"
2153
0
      "<ScheduleByDay>\n"
2154
0
      "<DaysInterval>1</DaysInterval>\n"
2155
0
      "</ScheduleByDay>\n"
2156
0
      "<Repetition>\n"
2157
0
      "<Interval>PT1H</Interval>\n"
2158
0
      "<Duration>PT23H</Duration>\n"
2159
0
      "<StopAtDurationEnd>false</StopAtDurationEnd>\n"
2160
0
      "</Repetition>\n",
2161
0
      minute);
2162
0
    break;
2163
2164
0
  case SCHEDULE_DAILY:
2165
0
    fprintf(tfile->fp,
2166
0
      "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
2167
0
      "<Enabled>true</Enabled>\n"
2168
0
      "<ScheduleByWeek>\n"
2169
0
      "<DaysOfWeek>\n"
2170
0
      "<Monday />\n"
2171
0
      "<Tuesday />\n"
2172
0
      "<Wednesday />\n"
2173
0
      "<Thursday />\n"
2174
0
      "<Friday />\n"
2175
0
      "<Saturday />\n"
2176
0
      "</DaysOfWeek>\n"
2177
0
      "<WeeksInterval>1</WeeksInterval>\n"
2178
0
      "</ScheduleByWeek>\n",
2179
0
      minute);
2180
0
    break;
2181
2182
0
  case SCHEDULE_WEEKLY:
2183
0
    fprintf(tfile->fp,
2184
0
      "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n"
2185
0
      "<Enabled>true</Enabled>\n"
2186
0
      "<ScheduleByWeek>\n"
2187
0
      "<DaysOfWeek>\n"
2188
0
      "<Sunday />\n"
2189
0
      "</DaysOfWeek>\n"
2190
0
      "<WeeksInterval>1</WeeksInterval>\n"
2191
0
      "</ScheduleByWeek>\n",
2192
0
      minute);
2193
0
    break;
2194
2195
0
  default:
2196
0
    break;
2197
0
  }
2198
2199
0
  xml = "</CalendarTrigger>\n"
2200
0
        "</Triggers>\n"
2201
0
        "<Principals>\n"
2202
0
        "<Principal id=\"Author\">\n"
2203
0
        "<LogonType>InteractiveToken</LogonType>\n"
2204
0
        "<RunLevel>LeastPrivilege</RunLevel>\n"
2205
0
        "</Principal>\n"
2206
0
        "</Principals>\n"
2207
0
        "<Settings>\n"
2208
0
        "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
2209
0
        "<Enabled>true</Enabled>\n"
2210
0
        "<Hidden>true</Hidden>\n"
2211
0
        "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n"
2212
0
        "<WakeToRun>false</WakeToRun>\n"
2213
0
        "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n"
2214
0
        "<Priority>7</Priority>\n"
2215
0
        "</Settings>\n"
2216
0
        "<Actions Context=\"Author\">\n"
2217
0
        "<Exec>\n"
2218
0
        "<Command>\"%s\\headless-git.exe\"</Command>\n"
2219
0
        "<Arguments>--exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
2220
0
        "</Exec>\n"
2221
0
        "</Actions>\n"
2222
0
        "</Task>\n";
2223
0
  fprintf(tfile->fp, xml, exec_path, exec_path, frequency);
2224
0
  strvec_split(&child.args, cmd);
2225
0
  strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
2226
0
          get_tempfile_path(tfile), NULL);
2227
0
  close_tempfile_gently(tfile);
2228
2229
0
  child.no_stdout = 1;
2230
0
  child.no_stderr = 1;
2231
2232
0
  if (start_command(&child))
2233
0
    die(_("failed to start schtasks"));
2234
0
  result = finish_command(&child);
2235
2236
0
  delete_tempfile(&tfile);
2237
0
  free(name);
2238
0
  return result;
2239
0
}
2240
2241
static int schtasks_schedule_tasks(void)
2242
0
{
2243
0
  const char *exec_path = git_exec_path();
2244
2245
0
  return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
2246
0
         schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
2247
0
         schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
2248
0
}
2249
2250
static int schtasks_update_schedule(int run_maintenance, int fd UNUSED)
2251
0
{
2252
0
  if (run_maintenance)
2253
0
    return schtasks_schedule_tasks();
2254
0
  else
2255
0
    return schtasks_remove_tasks();
2256
0
}
2257
2258
MAYBE_UNUSED
2259
static int check_crontab_process(const char *cmd)
2260
0
{
2261
0
  struct child_process child = CHILD_PROCESS_INIT;
2262
2263
0
  strvec_split(&child.args, cmd);
2264
0
  strvec_push(&child.args, "-l");
2265
0
  child.no_stdin = 1;
2266
0
  child.no_stdout = 1;
2267
0
  child.no_stderr = 1;
2268
0
  child.silent_exec_failure = 1;
2269
2270
0
  if (start_command(&child))
2271
0
    return 0;
2272
  /* Ignore exit code, as an empty crontab will return error. */
2273
0
  finish_command(&child);
2274
0
  return 1;
2275
0
}
2276
2277
static int is_crontab_available(void)
2278
0
{
2279
0
  const char *cmd = "crontab";
2280
0
  int is_available;
2281
2282
0
  if (get_schedule_cmd(&cmd, &is_available))
2283
0
    return is_available;
2284
2285
#ifdef __APPLE__
2286
  /*
2287
   * macOS has cron, but it requires special permissions and will
2288
   * create a UI alert when attempting to run this command.
2289
   */
2290
  return 0;
2291
#else
2292
0
  return check_crontab_process(cmd);
2293
0
#endif
2294
0
}
2295
2296
0
#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
2297
0
#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
2298
2299
static int crontab_update_schedule(int run_maintenance, int fd)
2300
0
{
2301
0
  const char *cmd = "crontab";
2302
0
  int result = 0;
2303
0
  int in_old_region = 0;
2304
0
  struct child_process crontab_list = CHILD_PROCESS_INIT;
2305
0
  struct child_process crontab_edit = CHILD_PROCESS_INIT;
2306
0
  FILE *cron_list, *cron_in;
2307
0
  struct strbuf line = STRBUF_INIT;
2308
0
  struct tempfile *tmpedit = NULL;
2309
0
  int minute = get_random_minute();
2310
2311
0
  get_schedule_cmd(&cmd, NULL);
2312
0
  strvec_split(&crontab_list.args, cmd);
2313
0
  strvec_push(&crontab_list.args, "-l");
2314
0
  crontab_list.in = -1;
2315
0
  crontab_list.out = dup(fd);
2316
0
  crontab_list.git_cmd = 0;
2317
2318
0
  if (start_command(&crontab_list))
2319
0
    return error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
2320
2321
  /* Ignore exit code, as an empty crontab will return error. */
2322
0
  finish_command(&crontab_list);
2323
2324
0
  tmpedit = mks_tempfile_t(".git_cron_edit_tmpXXXXXX");
2325
0
  if (!tmpedit) {
2326
0
    result = error(_("failed to create crontab temporary file"));
2327
0
    goto out;
2328
0
  }
2329
0
  cron_in = fdopen_tempfile(tmpedit, "w");
2330
0
  if (!cron_in) {
2331
0
    result = error(_("failed to open temporary file"));
2332
0
    goto out;
2333
0
  }
2334
2335
  /*
2336
   * Read from the .lock file, filtering out the old
2337
   * schedule while appending the new schedule.
2338
   */
2339
0
  cron_list = fdopen(fd, "r");
2340
0
  rewind(cron_list);
2341
2342
0
  while (!strbuf_getline_lf(&line, cron_list)) {
2343
0
    if (!in_old_region && !strcmp(line.buf, BEGIN_LINE))
2344
0
      in_old_region = 1;
2345
0
    else if (in_old_region && !strcmp(line.buf, END_LINE))
2346
0
      in_old_region = 0;
2347
0
    else if (!in_old_region)
2348
0
      fprintf(cron_in, "%s\n", line.buf);
2349
0
  }
2350
0
  strbuf_release(&line);
2351
2352
0
  if (run_maintenance) {
2353
0
    struct strbuf line_format = STRBUF_INIT;
2354
0
    const char *exec_path = git_exec_path();
2355
2356
0
    fprintf(cron_in, "%s\n", BEGIN_LINE);
2357
0
    fprintf(cron_in,
2358
0
      "# The following schedule was created by Git\n");
2359
0
    fprintf(cron_in, "# Any edits made in this region might be\n");
2360
0
    fprintf(cron_in,
2361
0
      "# replaced in the future by a Git command.\n\n");
2362
2363
0
    strbuf_addf(&line_format,
2364
0
          "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n",
2365
0
          exec_path, exec_path);
2366
0
    fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly");
2367
0
    fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily");
2368
0
    fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly");
2369
0
    strbuf_release(&line_format);
2370
2371
0
    fprintf(cron_in, "\n%s\n", END_LINE);
2372
0
  }
2373
2374
0
  fflush(cron_in);
2375
2376
0
  strvec_split(&crontab_edit.args, cmd);
2377
0
  strvec_push(&crontab_edit.args, get_tempfile_path(tmpedit));
2378
0
  crontab_edit.git_cmd = 0;
2379
2380
0
  if (start_command(&crontab_edit)) {
2381
0
    result = error(_("failed to run 'crontab'; your system might not support 'cron'"));
2382
0
    goto out;
2383
0
  }
2384
2385
0
  if (finish_command(&crontab_edit))
2386
0
    result = error(_("'crontab' died"));
2387
0
  else
2388
0
    fclose(cron_list);
2389
0
out:
2390
0
  delete_tempfile(&tmpedit);
2391
0
  return result;
2392
0
}
2393
2394
static int real_is_systemd_timer_available(void)
2395
0
{
2396
0
  struct child_process child = CHILD_PROCESS_INIT;
2397
2398
0
  strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
2399
0
  child.no_stdin = 1;
2400
0
  child.no_stdout = 1;
2401
0
  child.no_stderr = 1;
2402
0
  child.silent_exec_failure = 1;
2403
2404
0
  if (start_command(&child))
2405
0
    return 0;
2406
0
  if (finish_command(&child))
2407
0
    return 0;
2408
0
  return 1;
2409
0
}
2410
2411
static int is_systemd_timer_available(void)
2412
0
{
2413
0
  const char *cmd = "systemctl";
2414
0
  int is_available;
2415
2416
0
  if (get_schedule_cmd(&cmd, &is_available))
2417
0
    return is_available;
2418
2419
0
  return real_is_systemd_timer_available();
2420
0
}
2421
2422
static char *xdg_config_home_systemd(const char *filename)
2423
0
{
2424
0
  return xdg_config_home_for("systemd/user", filename);
2425
0
}
2426
2427
0
#define SYSTEMD_UNIT_FORMAT "git-maintenance@%s.%s"
2428
2429
static int systemd_timer_delete_timer_file(enum schedule_priority priority)
2430
0
{
2431
0
  int ret = 0;
2432
0
  const char *frequency = get_frequency(priority);
2433
0
  char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
2434
0
  char *filename = xdg_config_home_systemd(local_timer_name);
2435
2436
0
  if (unlink(filename) && !is_missing_file_error(errno))
2437
0
    ret = error_errno(_("failed to delete '%s'"), filename);
2438
2439
0
  free(filename);
2440
0
  free(local_timer_name);
2441
0
  return ret;
2442
0
}
2443
2444
static int systemd_timer_delete_service_template(void)
2445
0
{
2446
0
  int ret = 0;
2447
0
  char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
2448
0
  char *filename = xdg_config_home_systemd(local_service_name);
2449
0
  if (unlink(filename) && !is_missing_file_error(errno))
2450
0
    ret = error_errno(_("failed to delete '%s'"), filename);
2451
2452
0
  free(filename);
2453
0
  free(local_service_name);
2454
0
  return ret;
2455
0
}
2456
2457
/*
2458
 * Write the schedule information into a git-maintenance@<schedule>.timer
2459
 * file using a custom minute. This timer file cannot use the templating
2460
 * system, so we generate a specific file for each.
2461
 */
2462
static int systemd_timer_write_timer_file(enum schedule_priority schedule,
2463
            int minute)
2464
0
{
2465
0
  int res = -1;
2466
0
  char *filename;
2467
0
  FILE *file;
2468
0
  const char *unit;
2469
0
  char *schedule_pattern = NULL;
2470
0
  const char *frequency = get_frequency(schedule);
2471
0
  char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer");
2472
2473
0
  filename = xdg_config_home_systemd(local_timer_name);
2474
2475
0
  if (safe_create_leading_directories(filename)) {
2476
0
    error(_("failed to create directories for '%s'"), filename);
2477
0
    goto error;
2478
0
  }
2479
0
  file = fopen_or_warn(filename, "w");
2480
0
  if (!file)
2481
0
    goto error;
2482
2483
0
  switch (schedule) {
2484
0
  case SCHEDULE_HOURLY:
2485
0
    schedule_pattern = xstrfmt("*-*-* 1..23:%02d:00", minute);
2486
0
    break;
2487
2488
0
  case SCHEDULE_DAILY:
2489
0
    schedule_pattern = xstrfmt("Tue..Sun *-*-* 0:%02d:00", minute);
2490
0
    break;
2491
2492
0
  case SCHEDULE_WEEKLY:
2493
0
    schedule_pattern = xstrfmt("Mon 0:%02d:00", minute);
2494
0
    break;
2495
2496
0
  default:
2497
0
    BUG("Unhandled schedule_priority");
2498
0
  }
2499
2500
0
  unit = "# This file was created and is maintained by Git.\n"
2501
0
         "# Any edits made in this file might be replaced in the future\n"
2502
0
         "# by a Git command.\n"
2503
0
         "\n"
2504
0
         "[Unit]\n"
2505
0
         "Description=Optimize Git repositories data\n"
2506
0
         "\n"
2507
0
         "[Timer]\n"
2508
0
         "OnCalendar=%s\n"
2509
0
         "Persistent=true\n"
2510
0
         "\n"
2511
0
         "[Install]\n"
2512
0
         "WantedBy=timers.target\n";
2513
0
  if (fprintf(file, unit, schedule_pattern) < 0) {
2514
0
    error(_("failed to write to '%s'"), filename);
2515
0
    fclose(file);
2516
0
    goto error;
2517
0
  }
2518
0
  if (fclose(file) == EOF) {
2519
0
    error_errno(_("failed to flush '%s'"), filename);
2520
0
    goto error;
2521
0
  }
2522
2523
0
  res = 0;
2524
2525
0
error:
2526
0
  free(schedule_pattern);
2527
0
  free(local_timer_name);
2528
0
  free(filename);
2529
0
  return res;
2530
0
}
2531
2532
/*
2533
 * No matter the schedule, we use the same service and can make use of the
2534
 * templating system. When installing git-maintenance@<schedule>.timer,
2535
 * systemd will notice that git-maintenance@.service exists as a template
2536
 * and will use this file and insert the <schedule> into the template at
2537
 * the position of "%i".
2538
 */
2539
static int systemd_timer_write_service_template(const char *exec_path)
2540
0
{
2541
0
  int res = -1;
2542
0
  char *filename;
2543
0
  FILE *file;
2544
0
  const char *unit;
2545
0
  char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service");
2546
2547
0
  filename = xdg_config_home_systemd(local_service_name);
2548
0
  if (safe_create_leading_directories(filename)) {
2549
0
    error(_("failed to create directories for '%s'"), filename);
2550
0
    goto error;
2551
0
  }
2552
0
  file = fopen_or_warn(filename, "w");
2553
0
  if (!file)
2554
0
    goto error;
2555
2556
0
  unit = "# This file was created and is maintained by Git.\n"
2557
0
         "# Any edits made in this file might be replaced in the future\n"
2558
0
         "# by a Git command.\n"
2559
0
         "\n"
2560
0
         "[Unit]\n"
2561
0
         "Description=Optimize Git repositories data\n"
2562
0
         "\n"
2563
0
         "[Service]\n"
2564
0
         "Type=oneshot\n"
2565
0
         "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n"
2566
0
         "LockPersonality=yes\n"
2567
0
         "MemoryDenyWriteExecute=yes\n"
2568
0
         "NoNewPrivileges=yes\n"
2569
0
         "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_VSOCK\n"
2570
0
         "RestrictNamespaces=yes\n"
2571
0
         "RestrictRealtime=yes\n"
2572
0
         "RestrictSUIDSGID=yes\n"
2573
0
         "SystemCallArchitectures=native\n"
2574
0
         "SystemCallFilter=@system-service\n";
2575
0
  if (fprintf(file, unit, exec_path, exec_path) < 0) {
2576
0
    error(_("failed to write to '%s'"), filename);
2577
0
    fclose(file);
2578
0
    goto error;
2579
0
  }
2580
0
  if (fclose(file) == EOF) {
2581
0
    error_errno(_("failed to flush '%s'"), filename);
2582
0
    goto error;
2583
0
  }
2584
2585
0
  res = 0;
2586
2587
0
error:
2588
0
  free(local_service_name);
2589
0
  free(filename);
2590
0
  return res;
2591
0
}
2592
2593
static int systemd_timer_enable_unit(int enable,
2594
             enum schedule_priority schedule,
2595
             int minute)
2596
0
{
2597
0
  const char *cmd = "systemctl";
2598
0
  struct child_process child = CHILD_PROCESS_INIT;
2599
0
  const char *frequency = get_frequency(schedule);
2600
2601
  /*
2602
   * Disabling the systemd unit while it is already disabled makes
2603
   * systemctl print an error.
2604
   * Let's ignore it since it means we already are in the expected state:
2605
   * the unit is disabled.
2606
   *
2607
   * On the other hand, enabling a systemd unit which is already enabled
2608
   * produces no error.
2609
   */
2610
0
  if (!enable)
2611
0
    child.no_stderr = 1;
2612
0
  else if (systemd_timer_write_timer_file(schedule, minute))
2613
0
    return -1;
2614
2615
0
  get_schedule_cmd(&cmd, NULL);
2616
0
  strvec_split(&child.args, cmd);
2617
0
  strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
2618
0
         "--now", NULL);
2619
0
  strvec_pushf(&child.args, SYSTEMD_UNIT_FORMAT, frequency, "timer");
2620
2621
0
  if (start_command(&child))
2622
0
    return error(_("failed to start systemctl"));
2623
0
  if (finish_command(&child))
2624
    /*
2625
     * Disabling an already disabled systemd unit makes
2626
     * systemctl fail.
2627
     * Let's ignore this failure.
2628
     *
2629
     * Enabling an enabled systemd unit doesn't fail.
2630
     */
2631
0
    if (enable)
2632
0
      return error(_("failed to run systemctl"));
2633
0
  return 0;
2634
0
}
2635
2636
/*
2637
 * A previous version of Git wrote the timer units as template files.
2638
 * Clean these up, if they exist.
2639
 */
2640
static void systemd_timer_delete_stale_timer_templates(void)
2641
0
{
2642
0
  char *timer_template_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "timer");
2643
0
  char *filename = xdg_config_home_systemd(timer_template_name);
2644
2645
0
  if (unlink(filename) && !is_missing_file_error(errno))
2646
0
    warning(_("failed to delete '%s'"), filename);
2647
2648
0
  free(filename);
2649
0
  free(timer_template_name);
2650
0
}
2651
2652
static int systemd_timer_delete_unit_files(void)
2653
0
{
2654
0
  systemd_timer_delete_stale_timer_templates();
2655
2656
  /* Purposefully not short-circuited to make sure all are called. */
2657
0
  return systemd_timer_delete_timer_file(SCHEDULE_HOURLY) |
2658
0
         systemd_timer_delete_timer_file(SCHEDULE_DAILY) |
2659
0
         systemd_timer_delete_timer_file(SCHEDULE_WEEKLY) |
2660
0
         systemd_timer_delete_service_template();
2661
0
}
2662
2663
static int systemd_timer_delete_units(void)
2664
0
{
2665
0
  int minute = get_random_minute();
2666
  /* Purposefully not short-circuited to make sure all are called. */
2667
0
  return systemd_timer_enable_unit(0, SCHEDULE_HOURLY, minute) |
2668
0
         systemd_timer_enable_unit(0, SCHEDULE_DAILY, minute) |
2669
0
         systemd_timer_enable_unit(0, SCHEDULE_WEEKLY, minute) |
2670
0
         systemd_timer_delete_unit_files();
2671
0
}
2672
2673
static int systemd_timer_setup_units(void)
2674
0
{
2675
0
  int minute = get_random_minute();
2676
0
  const char *exec_path = git_exec_path();
2677
2678
0
  int ret = systemd_timer_write_service_template(exec_path) ||
2679
0
      systemd_timer_enable_unit(1, SCHEDULE_HOURLY, minute) ||
2680
0
      systemd_timer_enable_unit(1, SCHEDULE_DAILY, minute) ||
2681
0
      systemd_timer_enable_unit(1, SCHEDULE_WEEKLY, minute);
2682
2683
0
  if (ret)
2684
0
    systemd_timer_delete_units();
2685
0
  else
2686
0
    systemd_timer_delete_stale_timer_templates();
2687
2688
0
  return ret;
2689
0
}
2690
2691
static int systemd_timer_update_schedule(int run_maintenance, int fd UNUSED)
2692
0
{
2693
0
  if (run_maintenance)
2694
0
    return systemd_timer_setup_units();
2695
0
  else
2696
0
    return systemd_timer_delete_units();
2697
0
}
2698
2699
enum scheduler {
2700
  SCHEDULER_INVALID = -1,
2701
  SCHEDULER_AUTO,
2702
  SCHEDULER_CRON,
2703
  SCHEDULER_SYSTEMD,
2704
  SCHEDULER_LAUNCHCTL,
2705
  SCHEDULER_SCHTASKS,
2706
};
2707
2708
static const struct {
2709
  const char *name;
2710
  int (*is_available)(void);
2711
  int (*update_schedule)(int run_maintenance, int fd);
2712
} scheduler_fn[] = {
2713
  [SCHEDULER_CRON] = {
2714
    .name = "crontab",
2715
    .is_available = is_crontab_available,
2716
    .update_schedule = crontab_update_schedule,
2717
  },
2718
  [SCHEDULER_SYSTEMD] = {
2719
    .name = "systemctl",
2720
    .is_available = is_systemd_timer_available,
2721
    .update_schedule = systemd_timer_update_schedule,
2722
  },
2723
  [SCHEDULER_LAUNCHCTL] = {
2724
    .name = "launchctl",
2725
    .is_available = is_launchctl_available,
2726
    .update_schedule = launchctl_update_schedule,
2727
  },
2728
  [SCHEDULER_SCHTASKS] = {
2729
    .name = "schtasks",
2730
    .is_available = is_schtasks_available,
2731
    .update_schedule = schtasks_update_schedule,
2732
  },
2733
};
2734
2735
static enum scheduler parse_scheduler(const char *value)
2736
0
{
2737
0
  if (!value)
2738
0
    return SCHEDULER_INVALID;
2739
0
  else if (!strcasecmp(value, "auto"))
2740
0
    return SCHEDULER_AUTO;
2741
0
  else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
2742
0
    return SCHEDULER_CRON;
2743
0
  else if (!strcasecmp(value, "systemd") ||
2744
0
     !strcasecmp(value, "systemd-timer"))
2745
0
    return SCHEDULER_SYSTEMD;
2746
0
  else if (!strcasecmp(value, "launchctl"))
2747
0
    return SCHEDULER_LAUNCHCTL;
2748
0
  else if (!strcasecmp(value, "schtasks"))
2749
0
    return SCHEDULER_SCHTASKS;
2750
0
  else
2751
0
    return SCHEDULER_INVALID;
2752
0
}
2753
2754
static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
2755
             int unset)
2756
0
{
2757
0
  enum scheduler *scheduler = opt->value;
2758
2759
0
  BUG_ON_OPT_NEG(unset);
2760
2761
0
  *scheduler = parse_scheduler(arg);
2762
0
  if (*scheduler == SCHEDULER_INVALID)
2763
0
    return error(_("unrecognized --scheduler argument '%s'"), arg);
2764
0
  return 0;
2765
0
}
2766
2767
struct maintenance_start_opts {
2768
  enum scheduler scheduler;
2769
};
2770
2771
static enum scheduler resolve_scheduler(enum scheduler scheduler)
2772
0
{
2773
0
  if (scheduler != SCHEDULER_AUTO)
2774
0
    return scheduler;
2775
2776
#if defined(__APPLE__)
2777
  return SCHEDULER_LAUNCHCTL;
2778
2779
#elif defined(GIT_WINDOWS_NATIVE)
2780
  return SCHEDULER_SCHTASKS;
2781
2782
#elif defined(__linux__)
2783
0
  if (is_systemd_timer_available())
2784
0
    return SCHEDULER_SYSTEMD;
2785
0
  else if (is_crontab_available())
2786
0
    return SCHEDULER_CRON;
2787
0
  else
2788
0
    die(_("neither systemd timers nor crontab are available"));
2789
2790
#else
2791
  return SCHEDULER_CRON;
2792
#endif
2793
0
}
2794
2795
static void validate_scheduler(enum scheduler scheduler)
2796
0
{
2797
0
  if (scheduler == SCHEDULER_INVALID)
2798
0
    BUG("invalid scheduler");
2799
0
  if (scheduler == SCHEDULER_AUTO)
2800
0
    BUG("resolve_scheduler should have been called before");
2801
2802
0
  if (!scheduler_fn[scheduler].is_available())
2803
0
    die(_("%s scheduler is not available"),
2804
0
        scheduler_fn[scheduler].name);
2805
0
}
2806
2807
static int update_background_schedule(const struct maintenance_start_opts *opts,
2808
              int enable)
2809
0
{
2810
0
  unsigned int i;
2811
0
  int result = 0;
2812
0
  struct lock_file lk;
2813
0
  char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
2814
2815
0
  if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
2816
0
    free(lock_path);
2817
0
    return error(_("another process is scheduling background maintenance"));
2818
0
  }
2819
2820
0
  for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
2821
0
    if (enable && opts->scheduler == i)
2822
0
      continue;
2823
0
    if (!scheduler_fn[i].is_available())
2824
0
      continue;
2825
0
    scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
2826
0
  }
2827
2828
0
  if (enable)
2829
0
    result = scheduler_fn[opts->scheduler].update_schedule(
2830
0
      1, get_lock_file_fd(&lk));
2831
2832
0
  rollback_lock_file(&lk);
2833
2834
0
  free(lock_path);
2835
0
  return result;
2836
0
}
2837
2838
static const char *const builtin_maintenance_start_usage[] = {
2839
  N_("git maintenance start [--scheduler=<scheduler>]"),
2840
  NULL
2841
};
2842
2843
static int maintenance_start(int argc, const char **argv, const char *prefix)
2844
0
{
2845
0
  struct maintenance_start_opts opts = { 0 };
2846
0
  struct option options[] = {
2847
0
    OPT_CALLBACK_F(
2848
0
      0, "scheduler", &opts.scheduler, N_("scheduler"),
2849
0
      N_("scheduler to trigger git maintenance run"),
2850
0
      PARSE_OPT_NONEG, maintenance_opt_scheduler),
2851
0
    OPT_END()
2852
0
  };
2853
0
  const char *register_args[] = { "register", NULL };
2854
2855
0
  argc = parse_options(argc, argv, prefix, options,
2856
0
           builtin_maintenance_start_usage, 0);
2857
0
  if (argc)
2858
0
    usage_with_options(builtin_maintenance_start_usage, options);
2859
2860
0
  opts.scheduler = resolve_scheduler(opts.scheduler);
2861
0
  validate_scheduler(opts.scheduler);
2862
2863
0
  if (update_background_schedule(&opts, 1))
2864
0
    die(_("failed to set up maintenance schedule"));
2865
2866
0
  if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL))
2867
0
    warning(_("failed to add repo to global config"));
2868
0
  return 0;
2869
0
}
2870
2871
static const char *const builtin_maintenance_stop_usage[] = {
2872
  "git maintenance stop",
2873
  NULL
2874
};
2875
2876
static int maintenance_stop(int argc, const char **argv, const char *prefix)
2877
0
{
2878
0
  struct option options[] = {
2879
0
    OPT_END()
2880
0
  };
2881
0
  argc = parse_options(argc, argv, prefix, options,
2882
0
           builtin_maintenance_stop_usage, 0);
2883
0
  if (argc)
2884
0
    usage_with_options(builtin_maintenance_stop_usage, options);
2885
0
  return update_background_schedule(NULL, 0);
2886
0
}
2887
2888
static const char * const builtin_maintenance_usage[] = {
2889
  N_("git maintenance <subcommand> [<options>]"),
2890
  NULL,
2891
};
2892
2893
int cmd_maintenance(int argc, const char **argv, const char *prefix)
2894
0
{
2895
0
  parse_opt_subcommand_fn *fn = NULL;
2896
0
  struct option builtin_maintenance_options[] = {
2897
0
    OPT_SUBCOMMAND("run", &fn, maintenance_run),
2898
0
    OPT_SUBCOMMAND("start", &fn, maintenance_start),
2899
0
    OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
2900
0
    OPT_SUBCOMMAND("register", &fn, maintenance_register),
2901
0
    OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
2902
0
    OPT_END(),
2903
0
  };
2904
2905
0
  argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
2906
0
           builtin_maintenance_usage, 0);
2907
0
  return fn(argc, argv, prefix);
2908
0
}