1529
}
1530
}
1531
1532
+/*
1533
+ * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
1534
+ * to mock the schedulers that `git maintenance start` rely on.
1535
+ *
1536
+ * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
1537
+ * list of colon-separated key/value pairs where each pair contains a scheduler
1538
+ * and its corresponding mock.
1539
+ *
1540
+ * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
1541
+ * arguments unmodified.
1542
+ *
1543
+ * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
1544
+ * In this case, the *cmd value is read as input.
1545
+ *
1546
+ * * if the input value *cmd is the key of one of the comma-separated list
1547
+ * item, then *is_available is set to true and *cmd is modified and becomes
1548
+ * the mock command.
1549
+ *
1550
+ * * if the input value *cmd isn’t the key of any of the comma-separated list
1551
+ * item, then *is_available is set to false.
1552
+ *
1553
+ * Ex.:
1554
+ * GIT_TEST_MAINT_SCHEDULER not set
1555
+ * +-------+-------------------------------------------------+
1556
+ * | Input | Output |
1557
+ * | *cmd | return code | *cmd | *is_available |
1558
+ * +-------+-------------+-------------------+---------------+
1559
+ * | "foo" | false | "foo" (unchanged) | (unchanged) |
1560
+ * +-------+-------------+-------------------+---------------+
1561
+ *
1562
+ * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
1563
+ * +-------+-------------------------------------------------+
1564
+ * | Input | Output |
1565
+ * | *cmd | return code | *cmd | *is_available |
1566
+ * +-------+-------------+-------------------+---------------+
1567
+ * | "foo" | true | "./mock.foo.sh" | true |
1568
+ * | "qux" | true | "qux" (unchanged) | false |
1569
+ * +-------+-------------+-------------------+---------------+
1570
+ */
1571
+static int get_schedule_cmd(const char **cmd, int *is_available)
1572
+{
1573
+ char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
1574
+ struct string_list_item *item;
1575
+ struct string_list list = STRING_LIST_INIT_NODUP;
1576
+
1577
+ if (!testing)
1578
+ return 0;
1579
+
1580
+ if (is_available)
1581
+ *is_available = 0;
1582
+
1583
+ string_list_split_in_place(&list, testing, ',', -1);
1584
+ for_each_string_list_item(item, &list) {
1585
+ struct string_list pair = STRING_LIST_INIT_NODUP;
1586
+
1587
+ if (string_list_split_in_place(&pair, item->string, ':', 2) != 2)
1588
+ continue;
1589
+
1590
+ if (!strcmp(*cmd, pair.items[0].string)) {
1591
+ *cmd = pair.items[1].string;
1592
+ if (is_available)
1593
+ *is_available = 1;
1594
+ string_list_clear(&list, 0);
1595
+ UNLEAK(testing);
1596
+ return 1;
1597
+ }
1598
+ }
1599
+
1600
+ string_list_clear(&list, 0);
1601
+ free(testing);
1602
+ return 1;
1603
+}
1604
+
1605
+static int is_launchctl_available(void)
1606
+{
1607
+ const char *cmd = "launchctl";
1608
+ int is_available;
1609
+ if (get_schedule_cmd(&cmd, &is_available))
1610
+ return is_available;
1611
+
1612
+#ifdef __APPLE__
1613
+ return 1;
1614
+#else
1615
+ return 0;
1616
+#endif
1617
+}
1618
+
1619
static char *launchctl_service_name(const char *frequency)
1620
{
1621
struct strbuf label = STRBUF_INIT;
1642
return xstrfmt("gui/%d", getuid());
1643
}
1644
1558
-static int launchctl_boot_plist(int enable, const char *filename, const char *cmd)
1645
+static int launchctl_boot_plist(int enable, const char *filename)
1646
{
1647
+ const char *cmd = "launchctl";
1648
int result;
1649
struct child_process child = CHILD_PROCESS_INIT;
1650
char *uid = launchctl_get_uid();
1651
1652
+ get_schedule_cmd(&cmd, NULL);
1653
strvec_split(&child.args, cmd);
1565
- if (enable)
1566
- strvec_push(&child.args, "bootstrap");
1567
- else
1568
- strvec_push(&child.args, "bootout");
1569
- strvec_push(&child.args, uid);
1570
- strvec_push(&child.args, filename);
1654
+ strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
1655
+ filename, NULL);
1656
1657
child.no_stderr = 1;
1658
child.no_stdout = 1;
1666
return result;
1667
}
1668
1584
-static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd)
1669
+static int launchctl_remove_plist(enum schedule_priority schedule)
1670
{
1671
const char *frequency = get_frequency(schedule);
1672
char *name = launchctl_service_name(frequency);
1673
char *filename = launchctl_service_filename(name);
1589
- int result = launchctl_boot_plist(0, filename, cmd);
1674
+ int result = launchctl_boot_plist(0, filename);
1675
unlink(filename);
1676
free(filename);
1677
free(name);
1678
return result;
1679
}
1680
1596
-static int launchctl_remove_plists(const char *cmd)
1681
+static int launchctl_remove_plists(void)
1682
{
1598
- return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) ||
1599
- launchctl_remove_plist(SCHEDULE_DAILY, cmd) ||
1600
- launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
1683
+ return launchctl_remove_plist(SCHEDULE_HOURLY) ||
1684
+ launchctl_remove_plist(SCHEDULE_DAILY) ||
1685
+ launchctl_remove_plist(SCHEDULE_WEEKLY);
1686
}
1687
1603
-static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
1688
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
1689
{
1690
FILE *plist;
1691
int i;
1754
fclose(plist);
1755
1756
/* bootout might fail if not already running, so ignore */
1672
- launchctl_boot_plist(0, filename, cmd);
1673
- if (launchctl_boot_plist(1, filename, cmd))
1757
+ launchctl_boot_plist(0, filename);
1758
+ if (launchctl_boot_plist(1, filename))
1759
die(_("failed to bootstrap service %s"), filename);
1760
1761
free(filename);
1763
return 0;
1764
}
1765
1681
-static int launchctl_add_plists(const char *cmd)
1766
+static int launchctl_add_plists(void)
1767
{
1768
const char *exec_path = git_exec_path();
1769
1685
- return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) ||
1686
- launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) ||
1687
- launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd);
1770
+ return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
1771
+ launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
1772
+ launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
1773
}
1774
1690
-static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd)
1775
+static int launchctl_update_schedule(int run_maintenance, int fd)
1776
{
1777
if (run_maintenance)
1693
- return launchctl_add_plists(cmd);
1778
+ return launchctl_add_plists();
1779
else
1695
- return launchctl_remove_plists(cmd);
1780
+ return launchctl_remove_plists();
1781
+}
1782
+
1783
+static int is_schtasks_available(void)
1784
+{
1785
+ const char *cmd = "schtasks";
1786
+ int is_available;
1787
+ if (get_schedule_cmd(&cmd, &is_available))
1788
+ return is_available;
1789
+
1790
+#ifdef GIT_WINDOWS_NATIVE
1791
+ return 1;
1792
+#else
1793
+ return 0;
1794
+#endif
1795
}
1796
1797
static char *schtasks_task_name(const char *frequency)
1801
return strbuf_detach(&label, NULL);
1802
}
1803
1705
-static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd)
1804
+static int schtasks_remove_task(enum schedule_priority schedule)
1805
{
1806
+ const char *cmd = "schtasks";
1807
int result;
1808
struct strvec args = STRVEC_INIT;
1809
const char *frequency = get_frequency(schedule);
1810
char *name = schtasks_task_name(frequency);
1811
1812
+ get_schedule_cmd(&cmd, NULL);
1813
strvec_split(&args, cmd);
1814
strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
1815
1820
return result;
1821
}
1822
1722
-static int schtasks_remove_tasks(const char *cmd)
1823
+static int schtasks_remove_tasks(void)
1824
{
1724
- return schtasks_remove_task(SCHEDULE_HOURLY, cmd) ||
1725
- schtasks_remove_task(SCHEDULE_DAILY, cmd) ||
1726
- schtasks_remove_task(SCHEDULE_WEEKLY, cmd);
1825
+ return schtasks_remove_task(SCHEDULE_HOURLY) ||
1826
+ schtasks_remove_task(SCHEDULE_DAILY) ||
1827
+ schtasks_remove_task(SCHEDULE_WEEKLY);
1828
}
1829
1729
-static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd)
1830
+static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
1831
{
1832
+ const char *cmd = "schtasks";
1833
int result;
1834
struct child_process child = CHILD_PROCESS_INIT;
1835
const char *xml;
1838
char *name = schtasks_task_name(frequency);
1839
struct strbuf tfilename = STRBUF_INIT;
1840
1841
+ get_schedule_cmd(&cmd, NULL);
1842
+
1843
strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
1844
get_git_common_dir(), frequency);
1845
tfile = xmks_tempfile(tfilename.buf);
1944
return result;
1945
}
1946
1843
-static int schtasks_schedule_tasks(const char *cmd)
1947
+static int schtasks_schedule_tasks(void)
1948
{
1949
const char *exec_path = git_exec_path();
1950
1847
- return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) ||
1848
- schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) ||
1849
- schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd);
1951
+ return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
1952
+ schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
1953
+ schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
1954
}
1955
1852
-static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd)
1956
+static int schtasks_update_schedule(int run_maintenance, int fd)
1957
{
1958
if (run_maintenance)
1855
- return schtasks_schedule_tasks(cmd);
1959
+ return schtasks_schedule_tasks();
1960
else
1857
- return schtasks_remove_tasks(cmd);
1961
+ return schtasks_remove_tasks();
1962
+}
1963
+
1964
+static int is_crontab_available(void)
1965
+{
1966
+ const char *cmd = "crontab";
1967
+ int is_available;
1968
+ struct child_process child = CHILD_PROCESS_INIT;
1969
+
1970
+ if (get_schedule_cmd(&cmd, &is_available))
1971
+ return is_available;
1972
+
1973
+ strvec_split(&child.args, cmd);
1974
+ strvec_push(&child.args, "-l");
1975
+ child.no_stdin = 1;
1976
+ child.no_stdout = 1;
1977
+ child.no_stderr = 1;
1978
+ child.silent_exec_failure = 1;
1979
+
1980
+ if (start_command(&child))
1981
+ return 0;
1982
+ /* Ignore exit code, as an empty crontab will return error. */
1983
+ finish_command(&child);
1984
+ return 1;
1985
}
1986
1987
#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
1988
#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
1989
1863
-static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
1990
+static int crontab_update_schedule(int run_maintenance, int fd)
1991
{
1992
+ const char *cmd = "crontab";
1993
int result = 0;
1994
int in_old_region = 0;
1995
struct child_process crontab_list = CHILD_PROCESS_INIT;
1997
FILE *cron_list, *cron_in;
1998
struct strbuf line = STRBUF_INIT;
1999
2000
+ get_schedule_cmd(&cmd, NULL);
2001
strvec_split(&crontab_list.args, cmd);
2002
strvec_push(&crontab_list.args, "-l");
2003
crontab_list.in = -1;
2074
return result;
2075
}
2076
2077
+enum scheduler {
2078
+ SCHEDULER_INVALID = -1,
2079
+ SCHEDULER_AUTO,
2080
+ SCHEDULER_CRON,
2081
+ SCHEDULER_LAUNCHCTL,
2082
+ SCHEDULER_SCHTASKS,
2083
+};
2084
+
2085
+static const struct {
2086
+ const char *name;
2087
+ int (*is_available)(void);
2088
+ int (*update_schedule)(int run_maintenance, int fd);
2089
+} scheduler_fn[] = {
2090
+ [SCHEDULER_CRON] = {
2091
+ .name = "crontab",
2092
+ .is_available = is_crontab_available,
2093
+ .update_schedule = crontab_update_schedule,
2094
+ },
2095
+ [SCHEDULER_LAUNCHCTL] = {
2096
+ .name = "launchctl",
2097
+ .is_available = is_launchctl_available,
2098
+ .update_schedule = launchctl_update_schedule,
2099
+ },
2100
+ [SCHEDULER_SCHTASKS] = {
2101
+ .name = "schtasks",
2102
+ .is_available = is_schtasks_available,
2103
+ .update_schedule = schtasks_update_schedule,
2104
+ },
2105
+};
2106
+
2107
+static enum scheduler parse_scheduler(const char *value)
2108
+{
2109
+ if (!value)
2110
+ return SCHEDULER_INVALID;
2111
+ else if (!strcasecmp(value, "auto"))
2112
+ return SCHEDULER_AUTO;
2113
+ else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
2114
+ return SCHEDULER_CRON;
2115
+ else if (!strcasecmp(value, "launchctl"))
2116
+ return SCHEDULER_LAUNCHCTL;
2117
+ else if (!strcasecmp(value, "schtasks"))
2118
+ return SCHEDULER_SCHTASKS;
2119
+ else
2120
+ return SCHEDULER_INVALID;
2121
+}
2122
+
2123
+static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
2124
+ int unset)
2125
+{
2126
+ enum scheduler *scheduler = opt->value;
2127
+
2128
+ BUG_ON_OPT_NEG(unset);
2129
+
2130
+ *scheduler = parse_scheduler(arg);
2131
+ if (*scheduler == SCHEDULER_INVALID)
2132
+ return error(_("unrecognized --scheduler argument '%s'"), arg);
2133
+ return 0;
2134
+}
2135
+
2136
+struct maintenance_start_opts {
2137
+ enum scheduler scheduler;
2138
+};
2139
+
2140
+static enum scheduler resolve_scheduler(enum scheduler scheduler)
2141
+{
2142
+ if (scheduler != SCHEDULER_AUTO)
2143
+ return scheduler;
2144
+
2145
#if defined(__APPLE__)
1949
-static const char platform_scheduler[] = "launchctl";
2146
+ return SCHEDULER_LAUNCHCTL;
2147
+
2148
#elif defined(GIT_WINDOWS_NATIVE)
1951
-static const char platform_scheduler[] = "schtasks";
2149
+ return SCHEDULER_SCHTASKS;
2150
+
2151
#else
1953
-static const char platform_scheduler[] = "crontab";
2152
+ return SCHEDULER_CRON;
2153
#endif
2154
+}
2155
1956
-static int update_background_schedule(int enable)
2156
+static void validate_scheduler(enum scheduler scheduler)
2157
{
1958
- int result;
1959
- const char *scheduler = platform_scheduler;
1960
- const char *cmd = scheduler;
1961
- char *testing;
2158
+ if (scheduler == SCHEDULER_INVALID)
2159
+ BUG("invalid scheduler");
2160
+ if (scheduler == SCHEDULER_AUTO)
2161
+ BUG("resolve_scheduler should have been called before");
2162
+
2163
+ if (!scheduler_fn[scheduler].is_available())
2164
+ die(_("%s scheduler is not available"),
2165
+ scheduler_fn[scheduler].name);
2166
+}
2167
+
2168
+static int update_background_schedule(const struct maintenance_start_opts *opts,
2169
+ int enable)
2170
+{
2171
+ unsigned int i;
2172
+ int result = 0;
2173
struct lock_file lk;
2174
char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
2175
1965
- testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
1966
- if (testing) {
1967
- char *sep = strchr(testing, ':');
1968
- if (!sep)
1969
- die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing);
1970
- *sep = '\0';
1971
- scheduler = testing;
1972
- cmd = sep + 1;
2176
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
2177
+ free(lock_path);
2178
+ return error(_("another process is scheduling background maintenance"));
2179
}
2180
1975
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
1976
- result = error(_("another process is scheduling background maintenance"));
1977
- goto cleanup;
2181
+ for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
2182
+ if (enable && opts->scheduler == i)
2183
+ continue;
2184
+ if (!scheduler_fn[i].is_available())
2185
+ continue;
2186
+ scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
2187
}
2188
1980
- if (!strcmp(scheduler, "launchctl"))
1981
- result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd);
1982
- else if (!strcmp(scheduler, "schtasks"))
1983
- result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd);
1984
- else if (!strcmp(scheduler, "crontab"))
1985
- result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd);
1986
- else
1987
- die("unknown background scheduler: %s", scheduler);
2189
+ if (enable)
2190
+ result = scheduler_fn[opts->scheduler].update_schedule(
2191
+ 1, get_lock_file_fd(&lk));
2192
2193
rollback_lock_file(&lk);
2194
1991
-cleanup:
2195
free(lock_path);
1993
- free(testing);
2196
return result;
2197
}
2198
1997
-static int maintenance_start(void)
2199
+static const char *const builtin_maintenance_start_usage[] = {
2200
+ N_("git maintenance start [--scheduler=<scheduler>]"),
2201
+ NULL
2202
+};
2203
+
2204
+static int maintenance_start(int argc, const char **argv, const char *prefix)
2205
{
2206
+ struct maintenance_start_opts opts = { 0 };
2207
+ struct option options[] = {
2208
+ OPT_CALLBACK_F(
2209
+ 0, "scheduler", &opts.scheduler, N_("scheduler"),
2210
+ N_("scheduler to trigger git maintenance run"),
2211
+ PARSE_OPT_NONEG, maintenance_opt_scheduler),
2212
+ OPT_END()
2213
+ };
2214
+
2215
+ argc = parse_options(argc, argv, prefix, options,
2216
+ builtin_maintenance_start_usage, 0);
2217
+ if (argc)
2218
+ usage_with_options(builtin_maintenance_start_usage, options);
2219
+
2220
+ opts.scheduler = resolve_scheduler(opts.scheduler);
2221
+ validate_scheduler(opts.scheduler);
2222
+
2223
if (maintenance_register())
2224
warning(_("failed to add repo to global config"));
2001
-
2002
- return update_background_schedule(1);
2225
+ return update_background_schedule(&opts, 1);
2226
}
2227
2228
static int maintenance_stop(void)
2229
{
2007
- return update_background_schedule(0);
2230
+ return update_background_schedule(NULL, 0);
2231
}
2232
2233
static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
2241
if (!strcmp(argv[1], "run"))
2242
return maintenance_run(argc - 1, argv + 1, prefix);
2243
if (!strcmp(argv[1], "start"))
2021
- return maintenance_start();
2244
+ return maintenance_start(argc - 1, argv + 1, prefix);
2245
if (!strcmp(argv[1], "stop"))
2246
return maintenance_stop();
2247
if (!strcmp(argv[1], "register"))