aboutsummaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2022-03-25 16:05:52 -0700
committerJunio C Hamano <gitster@pobox.com>2022-03-25 16:05:52 -0700
commit852e2c84f847d99e29fd08feb8cea881dca63c79 (patch)
tree20190f12233f00373df4760c8ddb300f848d19d4 /builtin
parent45fe28c951c3e70666ee4ef8379772851a8e4d32 (diff)
parenta3dfe97f418762ccf1d0601c5cce40c77046d4fc (diff)
downloadgit-852e2c84f847d99e29fd08feb8cea881dca63c79.tar.xz
Merge branch 'jh/builtin-fsmonitor-part2' into jh/builtin-fsmonitor-part3
* jh/builtin-fsmonitor-part2: (150 commits) t7527: test status with untracked-cache and fsmonitor--daemon fsmonitor: force update index after large responses fsmonitor--daemon: use a cookie file to sync with file system fsmonitor--daemon: periodically truncate list of modified files t/perf/p7519: add fsmonitor--daemon test cases t/perf/p7519: speed up test on Windows t/perf/p7519: fix coding style t/helper/test-chmtime: skip directories on Windows t/perf: avoid copying builtin fsmonitor files into test repo t7527: create test for fsmonitor--daemon t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon help: include fsmonitor--daemon feature flag in version info fsmonitor--daemon: implement handle_client callback compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows fsmonitor--daemon: create token-based changed path cache fsmonitor--daemon: define token-ids fsmonitor--daemon: add pathname classification fsmonitor--daemon: implement 'start' command ...
Diffstat (limited to 'builtin')
-rw-r--r--builtin/am.c9
-rw-r--r--builtin/bisect--helper.c6
-rw-r--r--builtin/blame.c8
-rw-r--r--builtin/branch.c70
-rw-r--r--builtin/checkout-index.c41
-rw-r--r--builtin/checkout.c10
-rw-r--r--builtin/clean.c3
-rw-r--r--builtin/clone.c49
-rw-r--r--builtin/commit.c36
-rw-r--r--builtin/count-objects.c2
-rw-r--r--builtin/difftool.c5
-rw-r--r--builtin/fast-export.c2
-rw-r--r--builtin/fast-import.c1
-rw-r--r--builtin/fetch.c20
-rw-r--r--builtin/fsmonitor--daemon.c1479
-rw-r--r--builtin/grep.c35
-rw-r--r--builtin/hash-object.c11
-rw-r--r--builtin/help.c4
-rw-r--r--builtin/log.c18
-rw-r--r--builtin/ls-remote.c3
-rw-r--r--builtin/ls-tree.c2
-rw-r--r--builtin/merge-base.c6
-rw-r--r--builtin/merge.c6
-rw-r--r--builtin/mktag.c2
-rw-r--r--builtin/mktree.c2
-rw-r--r--builtin/name-rev.c2
-rw-r--r--builtin/notes.c6
-rw-r--r--builtin/pack-objects.c10
-rw-r--r--builtin/patch-id.c9
-rw-r--r--builtin/prune-packed.c2
-rw-r--r--builtin/pull.c12
-rw-r--r--builtin/push.c2
-rw-r--r--builtin/rebase.c103
-rw-r--r--builtin/receive-pack.c9
-rw-r--r--builtin/reflog.c2
-rw-r--r--builtin/remote.c2
-rw-r--r--builtin/replace.c2
-rw-r--r--builtin/reset.c13
-rw-r--r--builtin/rev-list.c2
-rw-r--r--builtin/send-pack.c2
-rw-r--r--builtin/shortlog.c1
-rw-r--r--builtin/show-branch.c1
-rw-r--r--builtin/sparse-checkout.c36
-rw-r--r--builtin/stripspace.c4
-rw-r--r--builtin/submodule--helper.c70
-rw-r--r--builtin/tag.c1
-rw-r--r--builtin/update-index.c24
-rw-r--r--builtin/update-server-info.c2
-rw-r--r--builtin/worktree.c63
49 files changed, 2000 insertions, 210 deletions
diff --git a/builtin/am.c b/builtin/am.c
index 7de2c89ef2..0f4111bafa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -34,6 +34,7 @@
#include "string-list.h"
#include "packfile.h"
#include "repository.h"
+#include "pretty.h"
/**
* Returns the length of the first line of msg.
@@ -199,7 +200,7 @@ static int am_option_parse_empty(const struct option *opt,
else if (!strcmp(arg, "keep"))
*opt_value = KEEP_EMPTY_COMMIT;
else
- return error(_("Invalid value for --empty: %s"), arg);
+ return error(_("invalid value for '%s': '%s'"), "--empty", arg);
return 0;
}
@@ -2239,7 +2240,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
* when you add new options
*/
else
- return error(_("Invalid value for --patch-format: %s"), arg);
+ return error(_("invalid value for '%s': '%s'"),
+ "--patch-format", arg);
return 0;
}
@@ -2282,7 +2284,8 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar
break;
}
if (new_value >= ARRAY_SIZE(valid_modes))
- return error(_("Invalid value for --show-current-patch: %s"), arg);
+ return error(_("invalid value for '%s': '%s'"),
+ "--show-current-patch", arg);
}
if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode)
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 28a2e6a575..f962dbd430 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -22,15 +22,15 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-reset [<commit>]"),
- N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
+ "git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]",
N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
- N_("git bisect--helper --bisect-next"),
+ "git bisect--helper --bisect-next",
N_("git bisect--helper --bisect-state (bad|new) [<rev>]"),
N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
N_("git bisect--helper --bisect-replay <filename>"),
N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"),
- N_("git bisect--helper --bisect-visualize"),
+ "git bisect--helper --bisect-visualize",
N_("git bisect--helper --bisect-run <cmd>..."),
NULL
};
diff --git a/builtin/blame.c b/builtin/blame.c
index 7fafeac408..8d15b68afc 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -721,8 +721,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "color.blame.repeatedlines")) {
if (color_parse_mem(value, strlen(value), repeated_meta_color))
- warning(_("invalid color '%s' in color.blame.repeatedLines"),
- value);
+ warning(_("invalid value for '%s': '%s'"),
+ "color.blame.repeatedLines", value);
return 0;
}
if (!strcmp(var, "color.blame.highlightrecent")) {
@@ -739,7 +739,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
coloring_mode &= ~(OUTPUT_COLOR_LINE |
OUTPUT_SHOW_AGE_WITH_COLOR);
} else {
- warning(_("invalid value for blame.coloring"));
+ warning(_("invalid value for '%s': '%s'"),
+ "blame.coloring", value);
return 0;
}
}
@@ -934,6 +935,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
parse_done:
+ revision_opts_finish(&revs);
no_whole_file_rename = !revs.diffopt.flags.follow_renames;
xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
revs.diffopt.flags.follow_renames = 0;
diff --git a/builtin/branch.c b/builtin/branch.c
index 4ce2a24754..5d00d0b8d3 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -27,7 +27,8 @@
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
- N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-l] [<pattern>...]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {
static const char *head;
static struct object_id head_oid;
+static int recurse_submodules = 0;
+static int submodule_propagate_branches = 0;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
@@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
+ if (!strcmp(var, "submodule.recurse")) {
+ recurse_submodules = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcasecmp(var, "submodule.propagateBranches")) {
+ submodule_propagate_branches = git_config_bool(var, value);
+ return 0;
+ }
+
return git_color_default_config(var, value, cb);
}
@@ -621,14 +633,16 @@ static int edit_branch_description(const char *branch_name)
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
- int show_current = 0;
- int reflog = 0, edit_description = 0;
- int quiet = 0, unset_upstream = 0;
+ /* possible actions */
+ int delete = 0, rename = 0, copy = 0, list = 0,
+ unset_upstream = 0, show_current = 0, edit_description = 0;
const char *new_upstream = NULL;
+ int noncreate_actions = 0;
+ /* possible options */
+ int reflog = 0, quiet = 0, icase = 0, force = 0,
+ recurse_submodules_explicit = 0;
enum branch_track track;
struct ref_filter filter;
- int icase = 0;
static struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
@@ -677,6 +691,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), parse_opt_object_name),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
@@ -713,10 +728,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;
- if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
- list + edit_description + unset_upstream > 1)
+ noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
+ !!show_current + !!list + !!edit_description +
+ !!unset_upstream;
+ if (noncreate_actions > 1)
usage_with_options(builtin_branch_usage, options);
+ if (recurse_submodules_explicit) {
+ if (!submodule_propagate_branches)
+ die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
+ if (noncreate_actions)
+ die(_("--recurse-submodules can only be used to create branches"));
+ }
+
+ recurse_submodules =
+ (recurse_submodules || recurse_submodules_explicit) &&
+ submodule_propagate_branches;
+
if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
filter.ignore_case = icase;
@@ -828,12 +856,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!ref_exists(branch->refname))
die(_("branch '%s' does not exist"), branch->name);
- /*
- * create_branch takes care of setting up the tracking
- * info and making sure new_upstream is correct
- */
- create_branch(the_repository, branch->name, new_upstream,
- 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
+ dwim_and_setup_tracking(the_repository, branch->name,
+ new_upstream, BRANCH_TRACK_OVERRIDE,
+ quiet);
} else if (unset_upstream) {
struct branch *branch = branch_get(argv[0]);
struct strbuf buf = STRBUF_INIT;
@@ -857,7 +882,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf);
- } else if (argc > 0 && argc <= 2) {
+ } else if (!noncreate_actions && argc > 0 && argc <= 2) {
+ const char *branch_name = argv[0];
+ const char *start_name = argc == 2 ? argv[1] : head;
+
if (filter.kind != FILTER_REFS_BRANCHES)
die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n"
"Did you mean to use: -a|-r --list <pattern>?"));
@@ -865,10 +893,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
- create_branch(the_repository,
- argv[0], (argc == 2) ? argv[1] : head,
- force, 0, reflog, quiet, track);
-
+ if (recurse_submodules) {
+ create_branches_recursively(the_repository, branch_name,
+ start_name, NULL, force,
+ reflog, quiet, track, 0);
+ return 0;
+ }
+ create_branch(the_repository, branch_name, start_name, force, 0,
+ reflog, quiet, track, 0);
} else
usage_with_options(builtin_branch_usage, options);
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index e21620d964..97e06e8c52 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -7,6 +7,7 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
+#include "dir.h"
#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
@@ -17,6 +18,7 @@
#define CHECKOUT_ALL 4
static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */
+static int ignore_skip_worktree; /* default to 0 */
static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
@@ -65,6 +67,8 @@ static int checkout_file(const char *name, const char *prefix)
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
int has_same_name = 0;
+ int is_file = 0;
+ int is_skipped = 1;
int did_checkout = 0;
int errs = 0;
@@ -78,6 +82,12 @@ static int checkout_file(const char *name, const char *prefix)
break;
has_same_name = 1;
pos++;
+ if (S_ISSPARSEDIR(ce->ce_mode))
+ break;
+ is_file = 1;
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ break;
+ is_skipped = 0;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
@@ -106,6 +116,11 @@ static int checkout_file(const char *name, const char *prefix)
fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name)
fprintf(stderr, "is not in the cache");
+ else if (!is_file)
+ fprintf(stderr, "is a sparse directory");
+ else if (is_skipped)
+ fprintf(stderr, "has skip-worktree enabled; "
+ "use '--ignore-skip-worktree-bits' to checkout");
else if (checkout_stage)
fprintf(stderr, "does not exist at stage %d",
checkout_stage);
@@ -121,10 +136,27 @@ static int checkout_all(const char *prefix, int prefix_length)
int i, errs = 0;
struct cache_entry *last_ce = NULL;
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];
+
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ if (!ce_skip_worktree(ce))
+ BUG("sparse directory '%s' does not have skip-worktree set", ce->name);
+
+ /*
+ * If the current entry is a sparse directory and skip-worktree
+ * entries are being checked out, expand the index and continue
+ * the loop on the current index position (now pointing to the
+ * first entry inside the expanded sparse directory).
+ */
+ if (ignore_skip_worktree) {
+ ensure_full_index(&the_index);
+ ce = active_cache[i];
+ }
+ }
+
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ continue;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
@@ -185,6 +217,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
+ OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree,
+ N_("do not skip files with skip-worktree set")),
OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")),
@@ -212,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
prefix_length = prefix ? strlen(prefix) : 0;
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (read_cache() < 0) {
die("invalid cache");
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f6e65fedfa..d9b31bbb6d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -909,7 +909,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
opts->quiet,
- opts->track);
+ opts->track,
+ 0);
free(new_branch_info->name);
free(new_branch_info->refname);
new_branch_info->name = xstrdup(opts->new_branch);
@@ -1607,9 +1608,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->show_progress = -1;
git_config(git_checkout_config, opts);
-
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
opts->track = BRANCH_TRACK_UNSPECIFIED;
diff --git a/builtin/clean.c b/builtin/clean.c
index 3ff02bbbff..5466636e66 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -1009,6 +1009,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
}
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (read_cache() < 0)
die(_("index file corrupt"));
diff --git a/builtin/clone.c b/builtin/clone.c
index 9c29093b35..a572cda503 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -72,6 +72,8 @@ static int option_dissociate;
static int max_jobs = -1;
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
static struct list_objects_filter_options filter_options;
+static int option_filter_submodules = -1; /* unspecified */
+static int config_filter_submodules = -1; /* unspecified */
static struct string_list server_options = STRING_LIST_INIT_NODUP;
static int option_remote_submodules;
@@ -151,6 +153,8 @@ static struct option builtin_clone_options[] = {
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "also-filter-submodules", &option_filter_submodules,
+ N_("apply partial clone filters to submodules")),
OPT_BOOL(0, "remote-submodules", &option_remote_submodules,
N_("any cloned submodules will use their remote-tracking branch")),
OPT_BOOL(0, "sparse", &option_sparse_checkout,
@@ -651,7 +655,7 @@ static int git_sparse_checkout_init(const char *repo)
return result;
}
-static int checkout(int submodule_progress)
+static int checkout(int submodule_progress, int filter_submodules)
{
struct object_id oid;
char *head;
@@ -730,6 +734,10 @@ static int checkout(int submodule_progress)
strvec_push(&args, "--no-fetch");
}
+ if (filter_submodules && filter_options.choice)
+ strvec_pushf(&args, "--filter=%s",
+ expand_list_objects_filter_spec(&filter_options));
+
if (option_single_branch >= 0)
strvec_push(&args, option_single_branch ?
"--single-branch" :
@@ -750,6 +758,8 @@ static int git_clone_config(const char *k, const char *v, void *cb)
}
if (!strcmp(k, "clone.rejectshallow"))
config_reject_shallow = git_config_bool(k, v);
+ if (!strcmp(k, "clone.filtersubmodules"))
+ config_filter_submodules = git_config_bool(k, v);
return git_default_config(k, v, cb);
}
@@ -872,6 +882,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
struct remote *remote;
int err = 0, complete_refs_before_fetch = 1;
int submodule_progress;
+ int filter_submodules = 0;
struct transport_ls_refs_options transport_ls_refs_options =
TRANSPORT_LS_REFS_OPTIONS_INIT;
@@ -1068,6 +1079,27 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
reject_shallow = option_reject_shallow;
/*
+ * If option_filter_submodules is specified from CLI option,
+ * ignore config_filter_submodules from git_clone_config.
+ */
+ if (config_filter_submodules != -1)
+ filter_submodules = config_filter_submodules;
+ if (option_filter_submodules != -1)
+ filter_submodules = option_filter_submodules;
+
+ /*
+ * Exit if the user seems to be doing something silly with submodule
+ * filter flags (but not with filter configs, as those should be
+ * set-and-forget).
+ */
+ if (option_filter_submodules > 0 && !filter_options.choice)
+ die(_("the option '%s' requires '%s'"),
+ "--also-filter-submodules", "--filter");
+ if (option_filter_submodules > 0 && !option_recurse_submodules.nr)
+ die(_("the option '%s' requires '%s'"),
+ "--also-filter-submodules", "--recurse-submodules");
+
+ /*
* apply the remote name provided by --origin only after this second
* call to git_config, to ensure it overrides all config-based values.
*/
@@ -1235,7 +1267,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
else {
const char *branch;
- char *ref;
+ const char *ref;
+ char *ref_free = NULL;
if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
@@ -1251,17 +1284,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
skip_prefix(transport_ls_refs_options.unborn_head_target,
"refs/heads/", &branch)) {
ref = transport_ls_refs_options.unborn_head_target;
- transport_ls_refs_options.unborn_head_target = NULL;
create_symref("HEAD", ref, reflog_msg.buf);
} else {
branch = git_default_branch_name(0);
- ref = xstrfmt("refs/heads/%s", branch);
+ ref_free = xstrfmt("refs/heads/%s", branch);
+ ref = ref_free;
}
if (!option_bare)
install_branch_config(0, branch, remote_name, ref);
-
- free(ref);
+ free(ref_free);
}
write_refspec_config(src_ref_prefix, our_head_points_at,
@@ -1300,7 +1332,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
junk_mode = JUNK_LEAVE_REPO;
- err = checkout(submodule_progress);
+ err = checkout(submodule_progress, filter_submodules);
free(remote_name);
strbuf_release(&reflog_msg);
@@ -1313,7 +1345,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
UNLEAK(repo);
junk_mode = JUNK_LEAVE_ALL;
- strvec_clear(&transport_ls_refs_options.ref_prefixes);
- free(transport_ls_refs_options.unborn_head_target);
+ transport_ls_refs_options_release(&transport_ls_refs_options);
return err;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index b9ed0374e3..8b8bdad395 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -37,6 +37,7 @@
#include "help.h"
#include "commit-reach.h"
#include "commit-graph.h"
+#include "pretty.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [<options>] [--] <pathspec>..."),
@@ -1242,8 +1243,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
struct commit *current_head,
struct wt_status *s)
{
- int f = 0;
-
argc = parse_options(argc, argv, prefix, options, usage, 0);
finalize_deferred_config(s);
@@ -1251,7 +1250,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
force_author = find_author_by_nickname(force_author);
if (force_author && renew_authorship)
- die(_("Using both --reset-author and --author does not make sense"));
+ die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
if (logfile || have_option_m || use_message)
use_editor = 0;
@@ -1268,20 +1267,16 @@ static int parse_and_validate_options(int argc, const char *argv[],
die(_("You are in the middle of a rebase -- cannot amend."));
}
if (fixup_message && squash_message)
- die(_("Options --squash and --fixup cannot be used together"));
- if (use_message)
- f++;
- if (edit_message)
- f++;
- if (fixup_message)
- f++;
- if (logfile)
- f++;
- if (f > 1)
- die(_("Only one of -c/-C/-F/--fixup can be used."));
- if (have_option_m && (edit_message || use_message || logfile))
- die((_("Option -m cannot be combined with -c/-C/-F.")));
- if (f || have_option_m)
+ die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup");
+ die_for_incompatible_opt4(!!use_message, "-C",
+ !!edit_message, "-c",
+ !!logfile, "-F",
+ !!fixup_message, "--fixup");
+ die_for_incompatible_opt4(have_option_m, "-m",
+ !!edit_message, "-c",
+ !!use_message, "-C",
+ !!logfile, "-F");
+ if (use_message || edit_message || logfile ||fixup_message || have_option_m)
template_file = NULL;
if (edit_message)
use_message = edit_message;
@@ -1306,9 +1301,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (patch_interactive)
interactive = 1;
- if (also + only + all + interactive > 1)
- die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
-
+ die_for_incompatible_opt4(also, "-i/--include",
+ only, "-o/--only",
+ all, "-a/--all",
+ interactive, "--interactive/-p/--patch");
if (fixup_message) {
/*
* We limit --fixup's suboptions to only alpha characters.
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index 3fae474f6f..07b9419596 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -87,7 +87,7 @@ static int print_alternate(struct object_directory *odb, void *data)
}
static char const * const count_objects_usage[] = {
- N_("git count-objects [-v] [-H | --human-readable]"),
+ "git count-objects [-v] [-H | --human-readable]",
NULL
};
diff --git a/builtin/difftool.c b/builtin/difftool.c
index c79fbbf67e..faa3507369 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -732,8 +732,9 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
} else if (dir_diff)
die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index");
- if (use_gui_tool + !!difftool_cmd + !!extcmd > 1)
- die(_("options '%s', '%s', and '%s' cannot be used together"), "--gui", "--tool", "--extcmd");
+ die_for_incompatible_opt3(use_gui_tool, "--gui",
+ !!difftool_cmd, "--tool",
+ !!extcmd, "--extcmd");
if (use_gui_tool)
setenv("GIT_MERGETOOL_GUI", "true", 1);
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 9f1c730e58..510139e9b5 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -26,7 +26,7 @@
#include "commit-slab.h"
static const char *fast_export_usage[] = {
- N_("git fast-export [rev-list-opts]"),
+ N_("git fast-export [<rev-list-opts>]"),
NULL
};
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index 2b2e28bad7..28f2b9cc91 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -19,6 +19,7 @@
#include "mem-pool.h"
#include "commit-reach.h"
#include "khash.h"
+#include "date.h"
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 6f5e157863..95832ba1df 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -764,8 +764,8 @@ static void prepare_format_display(struct ref *ref_map)
else if (!strcasecmp(format, "compact"))
compact_format = 1;
else
- die(_("configuration fetch.output contains invalid value %s"),
- format);
+ die(_("invalid value for '%s': '%s'"),
+ "fetch.output", format);
for (rm = ref_map; rm; rm = rm->next) {
if (rm->status == REF_STATUS_REJECT_SHALLOW ||
@@ -1094,12 +1094,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
struct ref *rm;
char *url;
int want_status;
- int summary_width = transport_summary_width(ref_map);
+ int summary_width = 0;
rc = open_fetch_head(&fetch_head);
if (rc)
return -1;
+ if (verbosity >= 0)
+ summary_width = transport_summary_width(ref_map);
+
if (raw_url)
url = transport_anonymize_url(raw_url);
else
@@ -1345,7 +1348,6 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
int url_len, i, result = 0;
struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);
char *url;
- int summary_width = transport_summary_width(stale_refs);
const char *dangling_msg = dry_run
? _(" (%s will become dangling)")
: _(" (%s has become dangling)");
@@ -1374,6 +1376,8 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
}
if (verbosity >= 0) {
+ int summary_width = transport_summary_width(stale_refs);
+
for (ref = stale_refs; ref; ref = ref->next) {
struct strbuf sb = STRBUF_INIT;
if (!shown_url) {
@@ -1594,7 +1598,7 @@ static int do_fetch(struct transport *transport,
} else
remote_refs = NULL;
- strvec_clear(&transport_ls_refs_options.ref_prefixes);
+ transport_ls_refs_options_release(&transport_ls_refs_options);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
@@ -2017,8 +2021,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
git_config(git_fetch_config, NULL);
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c
new file mode 100644
index 0000000000..46be55a461
--- /dev/null
+++ b/builtin/fsmonitor--daemon.c
@@ -0,0 +1,1479 @@
+#include "builtin.h"
+#include "config.h"
+#include "parse-options.h"
+#include "fsmonitor.h"
+#include "fsmonitor-ipc.h"
+#include "compat/fsmonitor/fsm-listen.h"
+#include "fsmonitor--daemon.h"
+#include "simple-ipc.h"
+#include "khash.h"
+#include "pkt-line.h"
+
+static const char * const builtin_fsmonitor__daemon_usage[] = {
+ N_("git fsmonitor--daemon start [<options>]"),
+ N_("git fsmonitor--daemon run [<options>]"),
+ N_("git fsmonitor--daemon stop"),
+ N_("git fsmonitor--daemon status"),
+ NULL
+};
+
+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
+/*
+ * Global state loaded from config.
+ */
+#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"
+static int fsmonitor__ipc_threads = 8;
+
+#define FSMONITOR__START_TIMEOUT "fsmonitor.starttimeout"
+static int fsmonitor__start_timeout_sec = 60;
+
+#define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"
+static int fsmonitor__announce_startup = 0;
+
+static int fsmonitor_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
+ int i = git_config_int(var, value);
+ if (i < 1)
+ return error(_("value of '%s' out of range: %d"),
+ FSMONITOR__IPC_THREADS, i);
+ fsmonitor__ipc_threads = i;
+ return 0;
+ }
+
+ if (!strcmp(var, FSMONITOR__START_TIMEOUT)) {
+ int i = git_config_int(var, value);
+ if (i < 0)
+ return error(_("value of '%s' out of range: %d"),
+ FSMONITOR__START_TIMEOUT, i);
+ fsmonitor__start_timeout_sec = i;
+ return 0;
+ }
+
+ if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {
+ int is_bool;
+ int i = git_config_bool_or_int(var, value, &is_bool);
+ if (i < 0)
+ return error(_("value of '%s' not bool or int: %d"),
+ var, i);
+ fsmonitor__announce_startup = i;
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
+
+/*
+ * Acting as a CLIENT.
+ *
+ * Send a "quit" command to the `git-fsmonitor--daemon` (if running)
+ * and wait for it to shutdown.
+ */
+static int do_as_client__send_stop(void)
+{
+ struct strbuf answer = STRBUF_INIT;
+ int ret;
+
+ ret = fsmonitor_ipc__send_command("quit", &answer);
+
+ /* The quit command does not return any response data. */
+ strbuf_release(&answer);
+
+ if (ret)
+ return ret;
+
+ trace2_region_enter("fsm_client", "polling-for-daemon-exit", NULL);
+ while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
+ sleep_millisec(50);
+ trace2_region_leave("fsm_client", "polling-for-daemon-exit", NULL);
+
+ return 0;
+}
+
+static int do_as_client__status(void)
+{
+ enum ipc_active_state state = fsmonitor_ipc__get_state();
+
+ switch (state) {
+ case IPC_STATE__LISTENING:
+ printf(_("fsmonitor-daemon is watching '%s'\n"),
+ the_repository->worktree);
+ return 0;
+
+ default:
+ printf(_("fsmonitor-daemon is not watching '%s'\n"),
+ the_repository->worktree);
+ return 1;
+ }
+}
+
+enum fsmonitor_cookie_item_result {
+ FCIR_ERROR = -1, /* could not create cookie file ? */
+ FCIR_INIT,
+ FCIR_SEEN,
+ FCIR_ABORT,
+};
+
+struct fsmonitor_cookie_item {
+ struct hashmap_entry entry;
+ char *name;
+ enum fsmonitor_cookie_item_result result;
+};
+
+static int cookies_cmp(const void *data, const struct hashmap_entry *he1,
+ const struct hashmap_entry *he2, const void *keydata)
+{
+ const struct fsmonitor_cookie_item *a =
+ container_of(he1, const struct fsmonitor_cookie_item, entry);
+ const struct fsmonitor_cookie_item *b =
+ container_of(he2, const struct fsmonitor_cookie_item, entry);
+
+ return strcmp(a->name, keydata ? keydata : b->name);
+}
+
+static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie(
+ struct fsmonitor_daemon_state *state)
+{
+ /* assert current thread holding state->main_lock */
+
+ int fd;
+ struct fsmonitor_cookie_item *cookie;
+ struct strbuf cookie_pathname = STRBUF_INIT;
+ struct strbuf cookie_filename = STRBUF_INIT;
+ enum fsmonitor_cookie_item_result result;
+ int my_cookie_seq;
+
+ CALLOC_ARRAY(cookie, 1);
+
+ my_cookie_seq = state->cookie_seq++;
+
+ strbuf_addf(&cookie_filename, "%i-%i", getpid(), my_cookie_seq);
+
+ strbuf_addbuf(&cookie_pathname, &state->path_cookie_prefix);
+ strbuf_addbuf(&cookie_pathname, &cookie_filename);
+
+ cookie->name = strbuf_detach(&cookie_filename, NULL);
+ cookie->result = FCIR_INIT;
+ hashmap_entry_init(&cookie->entry, strhash(cookie->name));
+
+ hashmap_add(&state->cookies, &cookie->entry);
+
+ trace_printf_key(&trace_fsmonitor, "cookie-wait: '%s' '%s'",
+ cookie->name, cookie_pathname.buf);
+
+ /*
+ * Create the cookie file on disk and then wait for a notification
+ * that the listener thread has seen it.
+ */
+ fd = open(cookie_pathname.buf, O_WRONLY | O_CREAT | O_EXCL, 0600);
+ if (fd < 0) {
+ error_errno(_("could not create fsmonitor cookie '%s'"),
+ cookie->name);
+
+ cookie->result = FCIR_ERROR;
+ goto done;
+ }
+
+ /*
+ * Technically, close() and unlink() can fail, but we don't
+ * care here. We only created the file to trigger a watch
+ * event from the FS to know that when we're up to date.
+ */
+ close(fd);
+ unlink(cookie_pathname.buf);
+
+ /*
+ * Technically, this is an infinite wait (well, unless another
+ * thread sends us an abort). I'd like to change this to
+ * use `pthread_cond_timedwait()` and return an error/timeout
+ * and let the caller do the trivial response thing, but we
+ * don't have that routine in our thread-utils.
+ *
+ * After extensive beta testing I'm not really worried about
+ * this. Also note that the above open() and unlink() calls
+ * will cause at least two FS events on that path, so the odds
+ * of getting stuck are pretty slim.
+ */
+ while (cookie->result == FCIR_INIT)
+ pthread_cond_wait(&state->cookies_cond,
+ &state->main_lock);
+
+done:
+ hashmap_remove(&state->cookies, &cookie->entry, NULL);
+
+ result = cookie->result;
+
+ free(cookie->name);
+ free(cookie);
+ strbuf_release(&cookie_pathname);
+
+ return result;
+}
+
+/*
+ * Mark these cookies as _SEEN and wake up the corresponding client threads.
+ */
+static void with_lock__mark_cookies_seen(struct fsmonitor_daemon_state *state,
+ const struct string_list *cookie_names)
+{
+ /* assert current thread holding state->main_lock */
+
+ int k;
+ int nr_seen = 0;
+
+ for (k = 0; k < cookie_names->nr; k++) {
+ struct fsmonitor_cookie_item key;
+ struct fsmonitor_cookie_item *cookie;
+
+ key.name = cookie_names->items[k].string;
+ hashmap_entry_init(&key.entry, strhash(key.name));
+
+ cookie = hashmap_get_entry(&state->cookies, &key, entry, NULL);
+ if (cookie) {
+ trace_printf_key(&trace_fsmonitor, "cookie-seen: '%s'",
+ cookie->name);
+ cookie->result = FCIR_SEEN;
+ nr_seen++;
+ }
+ }
+
+ if (nr_seen)
+ pthread_cond_broadcast(&state->cookies_cond);
+}
+
+/*
+ * Set _ABORT on all pending cookies and wake up all client threads.
+ */
+static void with_lock__abort_all_cookies(struct fsmonitor_daemon_state *state)
+{
+ /* assert current thread holding state->main_lock */
+
+ struct hashmap_iter iter;
+ struct fsmonitor_cookie_item *cookie;
+ int nr_aborted = 0;
+
+ hashmap_for_each_entry(&state->cookies, &iter, cookie, entry) {
+ trace_printf_key(&trace_fsmonitor, "cookie-abort: '%s'",
+ cookie->name);
+ cookie->result = FCIR_ABORT;
+ nr_aborted++;
+ }
+
+ if (nr_aborted)
+ pthread_cond_broadcast(&state->cookies_cond);
+}
+
+/*
+ * Requests to and from a FSMonitor Protocol V2 provider use an opaque
+ * "token" as a virtual timestamp. Clients can request a summary of all
+ * created/deleted/modified files relative to a token. In the response,
+ * clients receive a new token for the next (relative) request.
+ *
+ *
+ * Token Format
+ * ============
+ *
+ * The contents of the token are private and provider-specific.
+ *
+ * For the built-in fsmonitor--daemon, we define a token as follows:
+ *
+ * "builtin" ":" <token_id> ":" <sequence_nr>
+ *
+ * The "builtin" prefix is used as a namespace to avoid conflicts
+ * with other providers (such as Watchman).
+ *
+ * The <token_id> is an arbitrary OPAQUE string, such as a GUID,
+ * UUID, or {timestamp,pid}. It is used to group all filesystem
+ * events that happened while the daemon was monitoring (and in-sync
+ * with the filesystem).
+ *
+ * Unlike FSMonitor Protocol V1, it is not defined as a timestamp
+ * and does not define less-than/greater-than relationships.
+ * (There are too many race conditions to rely on file system
+ * event timestamps.)
+ *
+ * The <sequence_nr> is a simple integer incremented whenever the
+ * daemon needs to make its state public. For example, if 1000 file
+ * system events come in, but no clients have requested the data,
+ * the daemon can continue to accumulate file changes in the same
+ * bin and does not need to advance the sequence number. However,
+ * as soon as a client does arrive, the daemon needs to start a new
+ * bin and increment the sequence number.
+ *
+ * The sequence number serves as the boundary between 2 sets
+ * of bins -- the older ones that the client has already seen
+ * and the newer ones that it hasn't.
+ *
+ * When a new <token_id> is created, the <sequence_nr> is reset to
+ * zero.
+ *
+ *
+ * About Token Ids
+ * ===============
+ *
+ * A new token_id is created:
+ *
+ * [1] each time the daemon is started.
+ *
+ * [2] any time that the daemon must re-sync with the filesystem
+ * (such as when the kernel drops or we miss events on a very
+ * active volume).
+ *
+ * [3] in response to a client "flush" command (for dropped event
+ * testing).
+ *
+ * When a new token_id is created, the daemon is free to discard all
+ * cached filesystem events associated with any previous token_ids.
+ * Events associated with a non-current token_id will never be sent
+ * to a client. A token_id change implicitly means that the daemon
+ * has gap in its event history.
+ *
+ * Therefore, clients that present a token with a stale (non-current)
+ * token_id will always be given a trivial response.
+ */
+struct fsmonitor_token_data {
+ struct strbuf token_id;
+ struct fsmonitor_batch *batch_head;
+ struct fsmonitor_batch *batch_tail;
+ uint64_t client_ref_count;
+};
+
+struct fsmonitor_batch {
+ struct fsmonitor_batch *next;
+ uint64_t batch_seq_nr;
+ const char **interned_paths;
+ size_t nr, alloc;
+ time_t pinned_time;
+};
+
+static struct fsmonitor_token_data *fsmonitor_new_token_data(void)
+{
+ static int test_env_value = -1;
+ static uint64_t flush_count = 0;
+ struct fsmonitor_token_data *token;
+ struct fsmonitor_batch *batch;
+
+ CALLOC_ARRAY(token, 1);
+ batch = fsmonitor_batch__new();
+
+ strbuf_init(&token->token_id, 0);
+ token->batch_head = batch;
+ token->batch_tail = batch;
+ token->client_ref_count = 0;
+
+ if (test_env_value < 0)
+ test_env_value = git_env_bool("GIT_TEST_FSMONITOR_TOKEN", 0);
+
+ if (!test_env_value) {
+ struct timeval tv;
+ struct tm tm;
+ time_t secs;
+
+ gettimeofday(&tv, NULL);
+ secs = tv.tv_sec;
+ gmtime_r(&secs, &tm);
+
+ strbuf_addf(&token->token_id,
+ "%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ",
+ flush_count++,
+ getpid(),
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ (long)tv.tv_usec);
+ } else {
+ strbuf_addf(&token->token_id, "test_%08x", test_env_value++);
+ }
+
+ /*
+ * We created a new <token_id> and are starting a new series
+ * of tokens with a zero <seq_nr>.
+ *
+ * Since clients cannot guess our new (non test) <token_id>
+ * they will always receive a trivial response (because of the
+ * mismatch on the <token_id>). The trivial response will
+ * tell them our new <token_id> so that subsequent requests
+ * will be relative to our new series. (And when sending that
+ * response, we pin the current head of the batch list.)
+ *
+ * Even if the client correctly guesses the <token_id>, their
+ * request of "builtin:<token_id>:0" asks for all changes MORE
+ * RECENT than batch/bin 0.
+ *
+ * This implies that it is a waste to accumulate paths in the
+ * initial batch/bin (because they will never be transmitted).
+ *
+ * So the daemon could be running for days and watching the
+ * file system, but doesn't need to actually accumulate any
+ * paths UNTIL we need to set a reference point for a later
+ * relative request.
+ *
+ * However, it is very useful for testing to always have a
+ * reference point set. Pin batch 0 to force early file system
+ * events to accumulate.
+ */
+ if (test_env_value)
+ batch->pinned_time = time(NULL);
+
+ return token;
+}
+
+struct fsmonitor_batch *fsmonitor_batch__new(void)
+{
+ struct fsmonitor_batch *batch;
+
+ CALLOC_ARRAY(batch, 1);
+
+ return batch;
+}
+
+void fsmonitor_batch__free_list(struct fsmonitor_batch *batch)
+{
+ while (batch) {
+ struct fsmonitor_batch *next = batch->next;
+
+ /*
+ * The actual strings within the array of this batch
+ * are interned, so we don't own them. We only own
+ * the array.
+ */
+ free(batch->interned_paths);
+ free(batch);
+
+ batch = next;
+ }
+}
+
+void fsmonitor_batch__add_path(struct fsmonitor_batch *batch,
+ const char *path)
+{
+ const char *interned_path = strintern(path);
+
+ trace_printf_key(&trace_fsmonitor, "event: %s", interned_path);
+
+ ALLOC_GROW(batch->interned_paths, batch->nr + 1, batch->alloc);
+ batch->interned_paths[batch->nr++] = interned_path;
+}
+
+static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest,
+ const struct fsmonitor_batch *batch_src)
+{
+ size_t k;
+
+ ALLOC_GROW(batch_dest->interned_paths,
+ batch_dest->nr + batch_src->nr + 1,
+ batch_dest->alloc);
+
+ for (k = 0; k < batch_src->nr; k++)
+ batch_dest->interned_paths[batch_dest->nr++] =
+ batch_src->interned_paths[k];
+}
+
+/*
+ * To keep the batch list from growing unbounded in response to filesystem
+ * activity, we try to truncate old batches from the end of the list as
+ * they become irrelevant.
+ *
+ * We assume that the .git/index will be updated with the most recent token
+ * any time the index is updated. And future commands will only ask for
+ * recent changes *since* that new token. So as tokens advance into the
+ * future, older batch items will never be requested/needed. So we can
+ * truncate them without loss of functionality.
+ *
+ * However, multiple commands may be talking to the daemon concurrently
+ * or perform a slow command, so a little "token skew" is possible.
+ * Therefore, we want this to be a little bit lazy and have a generous
+ * delay.
+ *
+ * The current reader thread walked backwards in time from `token->batch_head`
+ * back to `batch_marker` somewhere in the middle of the batch list.
+ *
+ * Let's walk backwards in time from that marker an arbitrary delay
+ * and truncate the list there. Note that these timestamps are completely
+ * artificial (based on when we pinned the batch item) and not on any
+ * filesystem activity.
+ *
+ * Return the obsolete portion of the list after we have removed it from
+ * the official list so that the caller can free it after leaving the lock.
+ */
+#define MY_TIME_DELAY_SECONDS (5 * 60) /* seconds */
+
+static struct fsmonitor_batch *with_lock__truncate_old_batches(
+ struct fsmonitor_daemon_state *state,
+ const struct fsmonitor_batch *batch_marker)
+{
+ /* assert current thread holding state->main_lock */
+
+ const struct fsmonitor_batch *batch;
+ struct fsmonitor_batch *remainder;
+
+ if (!batch_marker)
+ return NULL;
+
+ trace_printf_key(&trace_fsmonitor, "Truncate: mark (%"PRIu64",%"PRIu64")",
+ batch_marker->batch_seq_nr,
+ (uint64_t)batch_marker->pinned_time);
+
+ for (batch = batch_marker; batch; batch = batch->next) {
+ time_t t;
+
+ if (!batch->pinned_time) /* an overflow batch */
+ continue;
+
+ t = batch->pinned_time + MY_TIME_DELAY_SECONDS;
+ if (t > batch_marker->pinned_time) /* too close to marker */
+ continue;
+
+ goto truncate_past_here;
+ }
+
+ return NULL;
+
+truncate_past_here:
+ state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch;
+
+ remainder = ((struct fsmonitor_batch *)batch)->next;
+ ((struct fsmonitor_batch *)batch)->next = NULL;
+
+ return remainder;
+}
+
+static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)
+{
+ if (!token)
+ return;
+
+ assert(token->client_ref_count == 0);
+
+ strbuf_release(&token->token_id);
+
+ fsmonitor_batch__free_list(token->batch_head);
+
+ free(token);
+}
+
+/*
+ * Flush all of our cached data about the filesystem. Call this if we
+ * lose sync with the filesystem and miss some notification events.
+ *
+ * [1] If we are missing events, then we no longer have a complete
+ * history of the directory (relative to our current start token).
+ * We should create a new token and start fresh (as if we just
+ * booted up).
+ *
+ * [2] Some of those lost events may have been for cookie files. We
+ * should assume the worst and abort them rather letting them starve.
+ *
+ * If there are no concurrent threads reading the current token data
+ * series, we can free it now. Otherwise, let the last reader free
+ * it.
+ *
+ * Either way, the old token data series is no longer associated with
+ * our state data.
+ */
+static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state)
+{
+ /* assert current thread holding state->main_lock */
+
+ struct fsmonitor_token_data *free_me = NULL;
+ struct fsmonitor_token_data *new_one = NULL;
+
+ new_one = fsmonitor_new_token_data();
+
+ if (state->current_token_data->client_ref_count == 0)
+ free_me = state->current_token_data;
+ state->current_token_data = new_one;
+
+ fsmonitor_free_token_data(free_me);
+
+ with_lock__abort_all_cookies(state);
+}
+
+void fsmonitor_force_resync(struct fsmonitor_daemon_state *state)
+{
+ pthread_mutex_lock(&state->main_lock);
+ with_lock__do_force_resync(state);
+ pthread_mutex_unlock(&state->main_lock);
+}
+
+/*
+ * Format an opaque token string to send to the client.
+ */
+static void with_lock__format_response_token(
+ struct strbuf *response_token,
+ const struct strbuf *response_token_id,
+ const struct fsmonitor_batch *batch)
+{
+ /* assert current thread holding state->main_lock */
+
+ strbuf_reset(response_token);
+ strbuf_addf(response_token, "builtin:%s:%"PRIu64,
+ response_token_id->buf, batch->batch_seq_nr);
+}
+
+/*
+ * Parse an opaque token from the client.
+ * Returns -1 on error.
+ */
+static int fsmonitor_parse_client_token(const char *buf_token,
+ struct strbuf *requested_token_id,
+ uint64_t *seq_nr)
+{
+ const char *p;
+ char *p_end;
+
+ strbuf_reset(requested_token_id);
+ *seq_nr = 0;
+
+ if (!skip_prefix(buf_token, "builtin:", &p))
+ return -1;
+
+ while (*p && *p != ':')
+ strbuf_addch(requested_token_id, *p++);
+ if (!*p++)
+ return -1;
+
+ *seq_nr = (uint64_t)strtoumax(p, &p_end, 10);
+ if (*p_end)
+ return -1;
+
+ return 0;
+}
+
+KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal)
+
+static int do_handle_client(struct fsmonitor_daemon_state *state,
+ const char *command,
+ ipc_server_reply_cb *reply,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct fsmonitor_token_data *token_data = NULL;
+ struct strbuf response_token = STRBUF_INIT;
+ struct strbuf requested_token_id = STRBUF_INIT;
+ struct strbuf payload = STRBUF_INIT;
+ uint64_t requested_oldest_seq_nr = 0;
+ uint64_t total_response_len = 0;
+ const char *p;
+ const struct fsmonitor_batch *batch_head;
+ const struct fsmonitor_batch *batch;
+ struct fsmonitor_batch *remainder = NULL;
+ intmax_t count = 0, duplicates = 0;
+ kh_str_t *shown;
+ int hash_ret;
+ int do_trivial = 0;
+ int do_flush = 0;
+ int do_cookie = 0;
+ enum fsmonitor_cookie_item_result cookie_result;
+
+ /*
+ * We expect `command` to be of the form:
+ *
+ * <command> := quit NUL
+ * | flush NUL
+ * | <V1-time-since-epoch-ns> NUL
+ * | <V2-opaque-fsmonitor-token> NUL
+ */
+
+ if (!strcmp(command, "quit")) {
+ /*
+ * A client has requested over the socket/pipe that the
+ * daemon shutdown.
+ *
+ * Tell the IPC thread pool to shutdown (which completes
+ * the await in the main thread (which can stop the
+ * fsmonitor listener thread)).
+ *
+ * There is no reply to the client.
+ */
+ return SIMPLE_IPC_QUIT;
+
+ } else if (!strcmp(command, "flush")) {
+ /*
+ * Flush all of our cached data and generate a new token
+ * just like if we lost sync with the filesystem.
+ *
+ * Then send a trivial response using the new token.
+ */
+ do_flush = 1;
+ do_trivial = 1;
+
+ } else if (!skip_prefix(command, "builtin:", &p)) {
+ /* assume V1 timestamp or garbage */
+
+ char *p_end;
+
+ strtoumax(command, &p_end, 10);
+ trace_printf_key(&trace_fsmonitor,
+ ((*p_end) ?
+ "fsmonitor: invalid command line '%s'" :
+ "fsmonitor: unsupported V1 protocol '%s'"),
+ command);
+ do_trivial = 1;
+
+ } else {
+ /* We have "builtin:*" */
+ if (fsmonitor_parse_client_token(command, &requested_token_id,
+ &requested_oldest_seq_nr)) {
+ trace_printf_key(&trace_fsmonitor,
+ "fsmonitor: invalid V2 protocol token '%s'",
+ command);
+ do_trivial = 1;
+
+ } else {
+ /*
+ * We have a V2 valid token:
+ * "builtin:<token_id>:<seq_nr>"
+ */
+ do_cookie = 1;
+ }
+ }
+
+ pthread_mutex_lock(&state->main_lock);
+
+ if (!state->current_token_data)
+ BUG("fsmonitor state does not have a current token");
+
+ /*
+ * Write a cookie file inside the directory being watched in
+ * an effort to flush out existing filesystem events that we
+ * actually care about. Suspend this client thread until we
+ * see the filesystem events for this cookie file.
+ *
+ * Creating the cookie lets us guarantee that our FS listener
+ * thread has drained the kernel queue and we are caught up
+ * with the kernel.
+ *
+ * If we cannot create the cookie (or otherwise guarantee that
+ * we are caught up), we send a trivial response. We have to
+ * assume that there might be some very, very recent activity
+ * on the FS still in flight.
+ */
+ if (do_cookie) {
+ cookie_result = with_lock__wait_for_cookie(state);
+ if (cookie_result != FCIR_SEEN) {
+ error(_("fsmonitor: cookie_result '%d' != SEEN"),
+ cookie_result);
+ do_trivial = 1;
+ }
+ }
+
+ if (do_flush)
+ with_lock__do_force_resync(state);
+
+ /*
+ * We mark the current head of the batch list as "pinned" so
+ * that the listener thread will treat this item as read-only
+ * (and prevent any more paths from being added to it) from
+ * now on.
+ */
+ token_data = state->current_token_data;
+ batch_head = token_data->batch_head;
+ ((struct fsmonitor_batch *)batch_head)->pinned_time = time(NULL);
+
+ /*
+ * FSMonitor Protocol V2 requires that we send a response header
+ * with a "new current token" and then all of the paths that changed
+ * since the "requested token". We send the seq_nr of the just-pinned
+ * head batch so that future requests from a client will be relative
+ * to it.
+ */
+ with_lock__format_response_token(&response_token,
+ &token_data->token_id, batch_head);
+
+ reply(reply_data, response_token.buf, response_token.len + 1);
+ total_response_len += response_token.len + 1;
+
+ trace2_data_string("fsmonitor", the_repository, "response/token",
+ response_token.buf);
+ trace_printf_key(&trace_fsmonitor, "response token: %s",
+ response_token.buf);
+
+ if (!do_trivial) {
+ if (strcmp(requested_token_id.buf, token_data->token_id.buf)) {
+ /*
+ * The client last spoke to a different daemon
+ * instance -OR- the daemon had to resync with
+ * the filesystem (and lost events), so reject.
+ */
+ trace2_data_string("fsmonitor", the_repository,
+ "response/token", "different");
+ do_trivial = 1;
+
+ } else if (requested_oldest_seq_nr <
+ token_data->batch_tail->batch_seq_nr) {
+ /*
+ * The client wants older events than we have for
+ * this token_id. This means that the end of our
+ * batch list was truncated and we cannot give the
+ * client a complete snapshot relative to their
+ * request.
+ */
+ trace_printf_key(&trace_fsmonitor,
+ "client requested truncated data");
+ do_trivial = 1;
+ }
+ }
+
+ if (do_trivial) {
+ pthread_mutex_unlock(&state->main_lock);
+
+ reply(reply_data, "/", 2);
+
+ trace2_data_intmax("fsmonitor", the_repository,
+ "response/trivial", 1);
+
+ goto cleanup;
+ }
+
+ /*
+ * We're going to hold onto a pointer to the current
+ * token-data while we walk the list of batches of files.
+ * During this time, we will NOT be under the lock.
+ * So we ref-count it.
+ *
+ * This allows the listener thread to continue prepending
+ * new batches of items to the token-data (which we'll ignore).
+ *
+ * AND it allows the listener thread to do a token-reset
+ * (and install a new `current_token_data`).
+ */
+ token_data->client_ref_count++;
+
+ pthread_mutex_unlock(&state->main_lock);
+
+ /*
+ * The client request is relative to the token that they sent,
+ * so walk the batch list backwards from the current head back
+ * to the batch (sequence number) they named.
+ *
+ * We use khash to de-dup the list of pathnames.
+ *
+ * NEEDSWORK: each batch contains a list of interned strings,
+ * so we only need to do pointer comparisons here to build the
+ * hash table. Currently, we're still comparing the string
+ * values.
+ */
+ shown = kh_init_str();
+ for (batch = batch_head;
+ batch && batch->batch_seq_nr > requested_oldest_seq_nr;
+ batch = batch->next) {
+ size_t k;
+
+ for (k = 0; k < batch->nr; k++) {
+ const char *s = batch->interned_paths[k];
+ size_t s_len;
+
+ if (kh_get_str(shown, s) != kh_end(shown))
+ duplicates++;
+ else {
+ kh_put_str(shown, s, &hash_ret);
+
+ trace_printf_key(&trace_fsmonitor,
+ "send[%"PRIuMAX"]: %s",
+ count, s);
+
+ /* Each path gets written with a trailing NUL */
+ s_len = strlen(s) + 1;
+
+ if (payload.len + s_len >=
+ LARGE_PACKET_DATA_MAX) {
+ reply(reply_data, payload.buf,
+ payload.len);
+ total_response_len += payload.len;
+ strbuf_reset(&payload);
+ }
+
+ strbuf_add(&payload, s, s_len);
+ count++;
+ }
+ }
+ }
+
+ if (payload.len) {
+ reply(reply_data, payload.buf, payload.len);
+ total_response_len += payload.len;
+ }
+
+ kh_release_str(shown);
+
+ pthread_mutex_lock(&state->main_lock);
+
+ if (token_data->client_ref_count > 0)
+ token_data->client_ref_count--;
+
+ if (token_data->client_ref_count == 0) {
+ if (token_data != state->current_token_data) {
+ /*
+ * The listener thread did a token-reset while we were
+ * walking the batch list. Therefore, this token is
+ * stale and can be discarded completely. If we are
+ * the last reader thread using this token, we own
+ * that work.
+ */
+ fsmonitor_free_token_data(token_data);
+ } else if (batch) {
+ /*
+ * We are holding the lock and are the only
+ * reader of the ref-counted portion of the
+ * list, so we get the honor of seeing if the
+ * list can be truncated to save memory.
+ *
+ * The main loop did not walk to the end of the
+ * list, so this batch is the first item in the
+ * batch-list that is older than the requested
+ * end-point sequence number. See if the tail
+ * end of the list is obsolete.
+ */
+ remainder = with_lock__truncate_old_batches(state,
+ batch);
+ }
+ }
+
+ pthread_mutex_unlock(&state->main_lock);
+
+ if (remainder)
+ fsmonitor_batch__free_list(remainder);
+
+ trace2_data_intmax("fsmonitor", the_repository, "response/length", total_response_len);
+ trace2_data_intmax("fsmonitor", the_repository, "response/count/files", count);
+ trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates);
+
+cleanup:
+ strbuf_release(&response_token);
+ strbuf_release(&requested_token_id);
+ strbuf_release(&payload);
+
+ return 0;
+}
+
+static ipc_server_application_cb handle_client;
+
+static int handle_client(void *data,
+ const char *command, size_t command_len,
+ ipc_server_reply_cb *reply,
+ struct ipc_server_reply_data *reply_data)
+{
+ struct fsmonitor_daemon_state *state = data;
+ int result;
+
+ /*
+ * The Simple IPC API now supports {char*, len} arguments, but
+ * FSMonitor always uses proper null-terminated strings, so
+ * we can ignore the command_len argument. (Trust, but verify.)
+ */
+ if (command_len != strlen(command))
+ BUG("FSMonitor assumes text messages");
+
+ trace_printf_key(&trace_fsmonitor, "requested token: %s", command);
+
+ trace2_region_enter("fsmonitor", "handle_client", the_repository);
+ trace2_data_string("fsmonitor", the_repository, "request", command);
+
+ result = do_handle_client(state, command, reply, reply_data);
+
+ trace2_region_leave("fsmonitor", "handle_client", the_repository);
+
+ return result;
+}
+
+#define FSMONITOR_DIR "fsmonitor--daemon"
+#define FSMONITOR_COOKIE_DIR "cookies"
+#define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/")
+
+enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative(
+ const char *rel)
+{
+ if (fspathncmp(rel, ".git", 4))
+ return IS_WORKDIR_PATH;
+ rel += 4;
+
+ if (!*rel)
+ return IS_DOT_GIT;
+ if (*rel != '/')
+ return IS_WORKDIR_PATH; /* e.g. .gitignore */
+ rel++;
+
+ if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX,
+ strlen(FSMONITOR_COOKIE_PREFIX)))
+ return IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX;
+
+ return IS_INSIDE_DOT_GIT;
+}
+
+enum fsmonitor_path_type fsmonitor_classify_path_gitdir_relative(
+ const char *rel)
+{
+ if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX,
+ strlen(FSMONITOR_COOKIE_PREFIX)))
+ return IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX;
+
+ return IS_INSIDE_GITDIR;
+}
+
+static enum fsmonitor_path_type try_classify_workdir_abs_path(
+ struct fsmonitor_daemon_state *state,
+ const char *path)
+{
+ const char *rel;
+
+ if (fspathncmp(path, state->path_worktree_watch.buf,
+ state->path_worktree_watch.len))
+ return IS_OUTSIDE_CONE;
+
+ rel = path + state->path_worktree_watch.len;
+
+ if (!*rel)
+ return IS_WORKDIR_PATH; /* it is the root dir exactly */
+ if (*rel != '/')
+ return IS_OUTSIDE_CONE;
+ rel++;
+
+ return fsmonitor_classify_path_workdir_relative(rel);
+}
+
+enum fsmonitor_path_type fsmonitor_classify_path_absolute(
+ struct fsmonitor_daemon_state *state,
+ const char *path)
+{
+ const char *rel;
+ enum fsmonitor_path_type t;
+
+ t = try_classify_workdir_abs_path(state, path);
+ if (state->nr_paths_watching == 1)
+ return t;
+ if (t != IS_OUTSIDE_CONE)
+ return t;
+
+ if (fspathncmp(path, state->path_gitdir_watch.buf,
+ state->path_gitdir_watch.len))
+ return IS_OUTSIDE_CONE;
+
+ rel = path + state->path_gitdir_watch.len;
+
+ if (!*rel)
+ return IS_GITDIR; /* it is the <gitdir> exactly */
+ if (*rel != '/')
+ return IS_OUTSIDE_CONE;
+ rel++;
+
+ return fsmonitor_classify_path_gitdir_relative(rel);
+}
+
+/*
+ * We try to combine small batches at the front of the batch-list to avoid
+ * having a long list. This hopefully makes it a little easier when we want
+ * to truncate and maintain the list. However, we don't want the paths array
+ * to just keep growing and growing with realloc, so we insert an arbitrary
+ * limit.
+ */
+#define MY_COMBINE_LIMIT (1024)
+
+void fsmonitor_publish(struct fsmonitor_daemon_state *state,
+ struct fsmonitor_batch *batch,
+ const struct string_list *cookie_names)
+{
+ if (!batch && !cookie_names->nr)
+ return;
+
+ pthread_mutex_lock(&state->main_lock);
+
+ if (batch) {
+ struct fsmonitor_batch *head;
+
+ head = state->current_token_data->batch_head;
+ if (!head) {
+ BUG("token does not have batch");
+ } else if (head->pinned_time) {
+ /*
+ * We cannot alter the current batch list
+ * because:
+ *
+ * [a] it is being transmitted to at least one
+ * client and the handle_client() thread has a
+ * ref-count, but not a lock on the batch list
+ * starting with this item.
+ *
+ * [b] it has been transmitted in the past to
+ * at least one client such that future
+ * requests are relative to this head batch.
+ *
+ * So, we can only prepend a new batch onto
+ * the front of the list.
+ */
+ batch->batch_seq_nr = head->batch_seq_nr + 1;
+ batch->next = head;
+ state->current_token_data->batch_head = batch;
+ } else if (!head->batch_seq_nr) {
+ /*
+ * Batch 0 is unpinned. See the note in
+ * `fsmonitor_new_token_data()` about why we
+ * don't need to accumulate these paths.
+ */
+ fsmonitor_batch__free_list(batch);
+ } else if (head->nr + batch->nr > MY_COMBINE_LIMIT) {
+ /*
+ * The head batch in the list has never been
+ * transmitted to a client, but folding the
+ * contents of the new batch onto it would
+ * exceed our arbitrary limit, so just prepend
+ * the new batch onto the list.
+ */
+ batch->batch_seq_nr = head->batch_seq_nr + 1;
+ batch->next = head;
+ state->current_token_data->batch_head = batch;
+ } else {
+ /*
+ * We are free to add the paths in the given
+ * batch onto the end of the current head batch.
+ */
+ fsmonitor_batch__combine(head, batch);
+ fsmonitor_batch__free_list(batch);
+ }
+ }
+
+ if (cookie_names->nr)
+ with_lock__mark_cookies_seen(state, cookie_names);
+
+ pthread_mutex_unlock(&state->main_lock);
+}
+
+static void *fsm_listen__thread_proc(void *_state)
+{
+ struct fsmonitor_daemon_state *state = _state;
+
+ trace2_thread_start("fsm-listen");
+
+ trace_printf_key(&trace_fsmonitor, "Watching: worktree '%s'",
+ state->path_worktree_watch.buf);
+ if (state->nr_paths_watching > 1)
+ trace_printf_key(&trace_fsmonitor, "Watching: gitdir '%s'",
+ state->path_gitdir_watch.buf);
+
+ fsm_listen__loop(state);
+
+ pthread_mutex_lock(&state->main_lock);
+ if (state->current_token_data &&
+ state->current_token_data->client_ref_count == 0)
+ fsmonitor_free_token_data(state->current_token_data);
+ state->current_token_data = NULL;
+ pthread_mutex_unlock(&state->main_lock);
+
+ trace2_thread_exit();
+ return NULL;
+}
+
+static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)
+{
+ struct ipc_server_opts ipc_opts = {
+ .nr_threads = fsmonitor__ipc_threads,
+
+ /*
+ * We know that there are no other active threads yet,
+ * so we can let the IPC layer temporarily chdir() if
+ * it needs to when creating the server side of the
+ * Unix domain socket.
+ */
+ .uds_disallow_chdir = 0
+ };
+
+ /*
+ * Start the IPC thread pool before the we've started the file
+ * system event listener thread so that we have the IPC handle
+ * before we need it.
+ */
+ if (ipc_server_run_async(&state->ipc_server_data,
+ fsmonitor_ipc__get_path(), &ipc_opts,
+ handle_client, state))
+ return error_errno(
+ _("could not start IPC thread pool on '%s'"),
+ fsmonitor_ipc__get_path());
+
+ /*
+ * Start the fsmonitor listener thread to collect filesystem
+ * events.
+ */
+ if (pthread_create(&state->listener_thread, NULL,
+ fsm_listen__thread_proc, state) < 0) {
+ ipc_server_stop_async(state->ipc_server_data);
+ ipc_server_await(state->ipc_server_data);
+
+ return error(_("could not start fsmonitor listener thread"));
+ }
+
+ /*
+ * The daemon is now fully functional in background threads.
+ * Wait for the IPC thread pool to shutdown (whether by client
+ * request or from filesystem activity).
+ */
+ ipc_server_await(state->ipc_server_data);
+
+ /*
+ * The fsmonitor listener thread may have received a shutdown
+ * event from the IPC thread pool, but it doesn't hurt to tell
+ * it again. And wait for it to shutdown.
+ */
+ fsm_listen__stop_async(state);
+ pthread_join(state->listener_thread, NULL);
+
+ return state->error_code;
+}
+
+static int fsmonitor_run_daemon(void)
+{
+ struct fsmonitor_daemon_state state;
+ int err;
+
+ memset(&state, 0, sizeof(state));
+
+ hashmap_init(&state.cookies, cookies_cmp, NULL, 0);
+ pthread_mutex_init(&state.main_lock, NULL);
+ pthread_cond_init(&state.cookies_cond, NULL);
+ state.error_code = 0;
+ state.current_token_data = fsmonitor_new_token_data();
+
+ /* Prepare to (recursively) watch the <worktree-root> directory. */
+ strbuf_init(&state.path_worktree_watch, 0);
+ strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree()));
+ state.nr_paths_watching = 1;
+
+ /*
+ * We create and delete cookie files somewhere inside the .git
+ * directory to help us keep sync with the file system. If
+ * ".git" is not a directory, then <gitdir> is not inside the
+ * cone of <worktree-root>, so set up a second watch to watch
+ * the <gitdir> so that we get events for the cookie files.
+ */
+ strbuf_init(&state.path_gitdir_watch, 0);
+ strbuf_addbuf(&state.path_gitdir_watch, &state.path_worktree_watch);
+ strbuf_addstr(&state.path_gitdir_watch, "/.git");
+ if (!is_directory(state.path_gitdir_watch.buf)) {
+ strbuf_reset(&state.path_gitdir_watch);
+ strbuf_addstr(&state.path_gitdir_watch, absolute_path(get_git_dir()));
+ state.nr_paths_watching = 2;
+ }
+
+ /*
+ * We will write filesystem syncing cookie files into
+ * <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>.
+ *
+ * The extra layers of subdirectories here keep us from
+ * changing the mtime on ".git/" or ".git/foo/" when we create
+ * or delete cookie files.
+ *
+ * There have been problems with some IDEs that do a
+ * non-recursive watch of the ".git/" directory and run a
+ * series of commands any time something happens.
+ *
+ * For example, if we place our cookie files directly in
+ * ".git/" or ".git/foo/" then a `git status` (or similar
+ * command) from the IDE will cause a cookie file to be
+ * created in one of those dirs. This causes the mtime of
+ * those dirs to change. This triggers the IDE's watch
+ * notification. This triggers the IDE to run those commands
+ * again. And the process repeats and the machine never goes
+ * idle.
+ *
+ * Adding the extra layers of subdirectories prevents the
+ * mtime of ".git/" and ".git/foo" from changing when a
+ * cookie file is created.
+ */
+ strbuf_init(&state.path_cookie_prefix, 0);
+ strbuf_addbuf(&state.path_cookie_prefix, &state.path_gitdir_watch);
+
+ strbuf_addch(&state.path_cookie_prefix, '/');
+ strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_DIR);
+ mkdir(state.path_cookie_prefix.buf, 0777);
+
+ strbuf_addch(&state.path_cookie_prefix, '/');
+ strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_COOKIE_DIR);
+ mkdir(state.path_cookie_prefix.buf, 0777);
+
+ strbuf_addch(&state.path_cookie_prefix, '/');
+
+ /*
+ * Confirm that we can create platform-specific resources for the
+ * filesystem listener before we bother starting all the threads.
+ */
+ if (fsm_listen__ctor(&state)) {
+ err = error(_("could not initialize listener thread"));
+ goto done;
+ }
+
+ err = fsmonitor_run_daemon_1(&state);
+
+done:
+ pthread_cond_destroy(&state.cookies_cond);
+ pthread_mutex_destroy(&state.main_lock);
+ fsm_listen__dtor(&state);
+
+ ipc_server_free(state.ipc_server_data);
+
+ strbuf_release(&state.path_worktree_watch);
+ strbuf_release(&state.path_gitdir_watch);
+ strbuf_release(&state.path_cookie_prefix);
+
+ return err;
+}
+
+static int try_to_run_foreground_daemon(int detach_console)
+{
+ /*
+ * Technically, we don't need to probe for an existing daemon
+ * process, since we could just call `fsmonitor_run_daemon()`
+ * and let it fail if the pipe/socket is busy.
+ *
+ * However, this method gives us a nicer error message for a
+ * common error case.
+ */
+ if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
+ die(_("fsmonitor--daemon is already running '%s'"),
+ the_repository->worktree);
+
+ if (fsmonitor__announce_startup) {
+ fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"),
+ the_repository->worktree);
+ fflush(stderr);
+ }
+
+#ifdef GIT_WINDOWS_NATIVE
+ if (detach_console)
+ FreeConsole();
+#endif
+
+ return !!fsmonitor_run_daemon();
+}
+
+static start_bg_wait_cb bg_wait_cb;
+
+static int bg_wait_cb(const struct child_process *cp, void *cb_data)
+{
+ enum ipc_active_state s = fsmonitor_ipc__get_state();
+
+ switch (s) {
+ case IPC_STATE__LISTENING:
+ /* child is "ready" */
+ return 0;
+
+ case IPC_STATE__NOT_LISTENING:
+ case IPC_STATE__PATH_NOT_FOUND:
+ /* give child more time */
+ return 1;
+
+ default:
+ case IPC_STATE__INVALID_PATH:
+ case IPC_STATE__OTHER_ERROR:
+ /* all the time in world won't help */
+ return -1;
+ }
+}
+
+static int try_to_start_background_daemon(void)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ enum start_bg_result sbgr;
+
+ /*
+ * Before we try to create a background daemon process, see
+ * if a daemon process is already listening. This makes it
+ * easier for us to report an already-listening error to the
+ * console, since our spawn/daemon can only report the success
+ * of creating the background process (and not whether it
+ * immediately exited).
+ */
+ if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
+ die(_("fsmonitor--daemon is already running '%s'"),
+ the_repository->worktree);
+
+ if (fsmonitor__announce_startup) {
+ fprintf(stderr, _("starting fsmonitor-daemon in '%s'\n"),
+ the_repository->worktree);
+ fflush(stderr);
+ }
+
+ cp.git_cmd = 1;
+
+ strvec_push(&cp.args, "fsmonitor--daemon");
+ strvec_push(&cp.args, "run");
+ strvec_push(&cp.args, "--detach");
+ strvec_pushf(&cp.args, "--ipc-threads=%d", fsmonitor__ipc_threads);
+
+ cp.no_stdin = 1;
+ cp.no_stdout = 1;
+ cp.no_stderr = 1;
+
+ sbgr = start_bg_command(&cp, bg_wait_cb, NULL,
+ fsmonitor__start_timeout_sec);
+
+ switch (sbgr) {
+ case SBGR_READY:
+ return 0;
+
+ default:
+ case SBGR_ERROR:
+ case SBGR_CB_ERROR:
+ return error(_("daemon failed to start"));
+
+ case SBGR_TIMEOUT:
+ return error(_("daemon not online yet"));
+
+ case SBGR_DIED:
+ return error(_("daemon terminated"));
+ }
+}
+
+int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
+{
+ const char *subcmd;
+ int detach_console = 0;
+
+ struct option options[] = {
+ OPT_BOOL(0, "detach", &detach_console, N_("detach from console")),
+ OPT_INTEGER(0, "ipc-threads",
+ &fsmonitor__ipc_threads,
+ N_("use <n> ipc worker threads")),
+ OPT_INTEGER(0, "start-timeout",
+ &fsmonitor__start_timeout_sec,
+ N_("max seconds to wait for background daemon startup")),
+
+ OPT_END()
+ };
+
+ git_config(fsmonitor_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_fsmonitor__daemon_usage, 0);
+ if (argc != 1)
+ usage_with_options(builtin_fsmonitor__daemon_usage, options);
+ subcmd = argv[0];
+
+ if (fsmonitor__ipc_threads < 1)
+ die(_("invalid 'ipc-threads' value (%d)"),
+ fsmonitor__ipc_threads);
+
+ if (!strcmp(subcmd, "start"))
+ return !!try_to_start_background_daemon();
+
+ if (!strcmp(subcmd, "run"))
+ return !!try_to_run_foreground_daemon(detach_console);
+
+ if (!strcmp(subcmd, "stop"))
+ return !!do_as_client__send_stop();
+
+ if (!strcmp(subcmd, "status"))
+ return !!do_as_client__status();
+
+ die(_("Unhandled subcommand '%s'"), subcmd);
+}
+
+#else
+int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_fsmonitor__daemon_usage, options);
+
+ die(_("fsmonitor--daemon not supported on this platform"));
+}
+#endif
diff --git a/builtin/grep.c b/builtin/grep.c
index 9e34a820ad..f1a924eade 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -26,6 +26,8 @@
#include "object-store.h"
#include "packfile.h"
+static const char *grep_prefix;
+
static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
NULL
@@ -284,7 +286,7 @@ static int wait_all(void)
static int grep_cmd_config(const char *var, const char *value, void *cb)
{
int st = grep_config(var, value, cb);
- if (git_color_default_config(var, value, cb) < 0)
+ if (git_color_default_config(var, value, NULL) < 0)
st = -1;
if (!strcmp(var, "grep.threads")) {
@@ -315,11 +317,11 @@ static void grep_source_name(struct grep_opt *opt, const char *filename,
strbuf_reset(out);
if (opt->null_following_name) {
- if (opt->relative && opt->prefix_length) {
+ if (opt->relative && grep_prefix) {
struct strbuf rel_buf = STRBUF_INIT;
const char *rel_name =
relative_path(filename + tree_name_len,
- opt->prefix, &rel_buf);
+ grep_prefix, &rel_buf);
if (tree_name_len)
strbuf_add(out, filename, tree_name_len);
@@ -332,8 +334,8 @@ static void grep_source_name(struct grep_opt *opt, const char *filename,
return;
}
- if (opt->relative && opt->prefix_length)
- quote_path(filename + tree_name_len, opt->prefix, out, 0);
+ if (opt->relative && grep_prefix)
+ quote_path(filename + tree_name_len, grep_prefix, out, 0);
else
quote_c_style(filename + tree_name_len, out, NULL, 0);
@@ -843,7 +845,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
int i;
int dummy;
int use_index = 1;
- int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
int allow_revs;
struct option options[] = {
@@ -877,16 +878,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
NULL, 1 },
OPT_GROUP(""),
- OPT_SET_INT('E', "extended-regexp", &pattern_type_arg,
+ OPT_SET_INT('E', "extended-regexp", &opt.pattern_type_option,
N_("use extended POSIX regular expressions"),
GREP_PATTERN_TYPE_ERE),
- OPT_SET_INT('G', "basic-regexp", &pattern_type_arg,
+ OPT_SET_INT('G', "basic-regexp", &opt.pattern_type_option,
N_("use basic POSIX regular expressions (default)"),
GREP_PATTERN_TYPE_BRE),
- OPT_SET_INT('F', "fixed-strings", &pattern_type_arg,
+ OPT_SET_INT('F', "fixed-strings", &opt.pattern_type_option,
N_("interpret patterns as fixed strings"),
GREP_PATTERN_TYPE_FIXED),
- OPT_SET_INT('P', "perl-regexp", &pattern_type_arg,
+ OPT_SET_INT('P', "perl-regexp", &opt.pattern_type_option,
N_("use Perl-compatible regular expressions"),
GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
@@ -962,9 +963,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOCOMPLETE),
OPT_END()
};
+ grep_prefix = prefix;
- git_config(grep_cmd_config, NULL);
- grep_init(&opt, the_repository, prefix);
+ grep_init(&opt, the_repository);
+ git_config(grep_cmd_config, &opt);
/*
* If there is no -- then the paths must exist in the working
@@ -979,7 +981,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, grep_usage,
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_STOP_AT_NON_OPTION);
- grep_commit_pattern_type(pattern_type_arg, &opt);
if (use_index && !startup_info->have_repository) {
int fallback = 0;
@@ -1167,11 +1168,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!show_in_pager && !opt.status_only)
setup_pager();
- if (!use_index && (untracked || cached))
- die(_("--cached or --untracked cannot be used with --no-index"));
-
- if (untracked && cached)
- die(_("--untracked cannot be used with --cached"));
+ die_for_incompatible_opt3(!use_index, "--no-index",
+ untracked, "--untracked",
+ cached, "--cached");
if (!use_index || untracked) {
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index c7b3ad74c6..0837849288 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -81,7 +81,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
{
static const char * const hash_object_usage[] = {
N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."),
- N_("git hash-object --stdin-paths"),
+ "git hash-object --stdin-paths",
NULL
};
const char *type = blob_type;
@@ -92,6 +92,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
int nongit = 0;
unsigned flags = HASH_FORMAT_CHECK;
const char *vpath = NULL;
+ char *vpath_free = NULL;
const struct option hash_object_options[] = {
OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
OPT_BIT('w', NULL, &flags, N_("write the object into the object database"),
@@ -114,8 +115,10 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
else
prefix = setup_git_directory_gently(&nongit);
- if (vpath && prefix)
- vpath = prefix_filename(prefix, vpath);
+ if (vpath && prefix) {
+ vpath_free = prefix_filename(prefix, vpath);
+ vpath = vpath_free;
+ }
git_config(git_default_config, NULL);
@@ -156,5 +159,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
if (stdin_paths)
hash_stdin_paths(type, no_filters, flags, literally);
+ free(vpath_free);
+
return 0;
}
diff --git a/builtin/help.c b/builtin/help.c
index d387131dd8..b4f2ad3f94 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -77,8 +77,8 @@ static struct option builtin_help_options[] = {
static const char * const builtin_help_usage[] = {
N_("git help [-a|--all] [--[no-]verbose]]\n"
" [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
- N_("git help [-g|--guides]"),
- N_("git help [-c|--config]"),
+ "git help [-g|--guides]",
+ "git help [-c|--config]",
NULL
};
diff --git a/builtin/log.c b/builtin/log.c
index 093d0d2655..c211d66d1d 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -533,8 +533,6 @@ static int git_log_config(const char *var, const char *value, void *cb)
return 0;
}
- if (grep_config(var, value, cb) < 0)
- return -1;
if (git_gpg_config(var, value, cb) < 0)
return -1;
return git_diff_ui_config(var, value, cb);
@@ -549,6 +547,8 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.diff = 1;
rev.simplify_history = 0;
memset(&opt, 0, sizeof(opt));
@@ -663,6 +663,8 @@ int cmd_show(int argc, const char **argv, const char *prefix)
memset(&match_all, 0, sizeof(match_all));
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.diff = 1;
rev.always_show_header = 1;
rev.no_walk = 1;
@@ -746,6 +748,8 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &rev, prefix);
init_reflog_walk(&rev.reflog_info);
+ git_config(grep_config, &rev.grep_filter);
+
rev.verbose_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
@@ -779,6 +783,8 @@ int cmd_log(int argc, const char **argv, const char *prefix)
git_config(git_log_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.always_show_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
@@ -1861,10 +1867,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
extra_hdr.strdup_strings = 1;
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;
+
init_log_defaults();
init_display_notes(&notes_opt);
git_config(git_format_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ git_config(grep_config, &rev.grep_filter);
+
rev.show_notes = show_notes;
memcpy(&rev.notes_opt, &notes_opt, sizeof(notes_opt));
rev.commit_format = CMIT_FMT_EMAIL;
@@ -1993,8 +2002,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (rev.show_notes)
load_display_notes(&rev.notes_opt);
- if (use_stdout + rev.diffopt.close_file + !!output_directory > 1)
- die(_("options '%s', '%s', and '%s' cannot be used together"), "--stdout", "--output", "--output-directory");
+ die_for_incompatible_opt3(use_stdout, "--stdout",
+ rev.diffopt.close_file, "--output",
+ !!output_directory, "--output-directory");
if (use_stdout) {
setup_pager();
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 44448fa61d..d856085e94 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -155,6 +155,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
ref_array_clear(&ref_array);
if (transport_disconnect(transport))
- return 1;
+ status = 1;
+ transport_ls_refs_options_release(&transport_options);
return status;
}
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3a442631c7..6cb554cbb0 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -150,7 +150,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
ls_tree_prefix = prefix;
- if (prefix && *prefix)
+ if (prefix)
chomp_prefix = strlen(prefix);
argc = parse_options(argc, argv, prefix, ls_tree_options,
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 6719ac198d..26b84980db 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -159,12 +159,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
if (argc < 2)
usage_with_options(merge_base_usage, options);
if (show_all)
- die("--is-ancestor cannot be used with --all");
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--is-ancestor", "--all");
return handle_is_ancestor(argc, argv);
}
if (cmdmode == 'r' && show_all)
- die("--independent cannot be used with --all");
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--independent", "--all");
if (cmdmode == 'o')
return handle_octopus(argc, argv, show_all);
diff --git a/builtin/merge.c b/builtin/merge.c
index 133831d42f..a94a03384a 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1568,8 +1568,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (autostash)
create_autostash(the_repository,
- git_path_merge_autostash(the_repository),
- "merge");
+ git_path_merge_autostash(the_repository));
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
@@ -1640,8 +1639,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (autostash)
create_autostash(the_repository,
- git_path_merge_autostash(the_repository),
- "merge");
+ git_path_merge_autostash(the_repository));
/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 3b2dbbb37e..c7b905c614 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -7,7 +7,7 @@
#include "config.h"
static char const * const builtin_mktag_usage[] = {
- N_("git mktag"),
+ "git mktag",
NULL
};
static int option_strict = 1;
diff --git a/builtin/mktree.c b/builtin/mktree.c
index ae78ca1c02..8bdaada922 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid)
}
static const char *mktree_usage[] = {
- N_("git mktree [-z] [--missing] [--batch]"),
+ "git mktree [-z] [--missing] [--batch]",
NULL
};
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 138e3c30a2..929591269d 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -473,7 +473,7 @@ static void show_name(const struct object *obj,
static char const * const name_rev_usage[] = {
N_("git name-rev [<options>] <commit>..."),
N_("git name-rev [<options>] --all"),
- N_("git name-rev [<options>] --stdin"),
+ N_("git name-rev [<options>] --annotate-stdin"),
NULL
};
diff --git a/builtin/notes.c b/builtin/notes.c
index 05d60483e8..f99593a185 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -32,8 +32,8 @@ static const char * const git_notes_usage[] = {
N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"),
N_("git notes [--ref <notes-ref>] show [<object>]"),
N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"),
- N_("git notes merge --commit [-v | -q]"),
- N_("git notes merge --abort [-v | -q]"),
+ "git notes merge --commit [-v | -q]",
+ "git notes merge --abort [-v | -q]",
N_("git notes [--ref <notes-ref>] remove [<object>...]"),
N_("git notes [--ref <notes-ref>] prune [-n] [-v]"),
N_("git notes [--ref <notes-ref>] get-ref"),
@@ -89,7 +89,7 @@ static const char * const git_notes_prune_usage[] = {
};
static const char * const git_notes_get_ref_usage[] = {
- N_("git notes get-ref"),
+ "git notes get-ref",
NULL
};
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index ba2006f221..178e611f09 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -3504,7 +3504,7 @@ static int option_parse_missing_action(const struct option *opt,
return 0;
}
- die(_("invalid value for --missing"));
+ die(_("invalid value for '%s': '%s'"), "--missing", arg);
return 0;
}
@@ -3976,9 +3976,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;
sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
- prepare_repo_settings(the_repository);
- if (sparse < 0)
- sparse = the_repository->settings.pack_use_sparse;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ if (sparse < 0)
+ sparse = the_repository->settings.pack_use_sparse;
+ }
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 822ffff51f..881fcf3273 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
n = strspn(q, digits);
if (q[n] == ',') {
q += n + 1;
+ *p_before = atoi(q);
n = strspn(q, digits);
+ } else {
+ *p_before = 1;
}
+
if (n == 0 || q[n] != ' ' || q[n+1] != '+')
return 0;
@@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
n = strspn(r, digits);
if (r[n] == ',') {
r += n + 1;
+ *p_after = atoi(r);
n = strspn(r, digits);
+ } else {
+ *p_after = 1;
}
if (n == 0)
return 0;
- *p_before = atoi(q);
- *p_after = atoi(r);
return 1;
}
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index b7b9281a8c..da3273a268 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -3,7 +3,7 @@
#include "prune-packed.h"
static const char * const prune_packed_usage[] = {
- N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
+ "git prune-packed [-n | --dry-run] [-q | --quiet]",
NULL
};
diff --git a/builtin/pull.c b/builtin/pull.c
index 8f37880a48..4d667abc19 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -42,9 +42,9 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,
return v;
if (fatal)
- die(_("Invalid value for %s: %s"), key, value);
+ die(_("invalid value for '%s': '%s'"), key, value);
else
- error(_("Invalid value for %s: %s"), key, value);
+ error(_("invalid value for '%s': '%s'"), key, value);
return REBASE_INVALID;
}
@@ -318,7 +318,7 @@ static const char *config_get_ff(void)
if (!strcmp(value, "only"))
return "--ff-only";
- die(_("Invalid value for pull.ff: %s"), value);
+ die(_("invalid value for '%s': '%s'"), "pull.ff", value);
}
/**
@@ -994,8 +994,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
set_reflog_message(argc, argv);
git_config(git_pull_config, NULL);
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
diff --git a/builtin/push.c b/builtin/push.c
index 359db90321..cad997965a 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -486,7 +486,7 @@ static int git_push_config(const char *k, const char *v, void *cb)
if (value && !strcasecmp(value, "if-asked"))
set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED);
else
- return error("Invalid value for '%s'", k);
+ return error(_("invalid value for '%s'"), k);
}
}
} else if (!strcmp(k, "push.recursesubmodules")) {
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 2e6d8fa34e..b29ad2b65e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -37,7 +37,7 @@ static char const * const builtin_rebase_usage[] = {
"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
"--root [<branch>]"),
- N_("git rebase --continue | --abort | --skip | --edit-todo"),
+ "git rebase --continue | --abort | --skip | --edit-todo",
NULL
};
@@ -571,7 +571,8 @@ static int finish_rebase(struct rebase_options *opts)
static int move_to_original_branch(struct rebase_options *opts)
{
- struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ struct reset_head_opts ropts = { 0 };
int ret;
if (!opts->head_name)
@@ -580,16 +581,17 @@ static int move_to_original_branch(struct rebase_options *opts)
if (!opts->onto)
BUG("move_to_original_branch without onto");
- strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
+ strbuf_addf(&branch_reflog, "rebase finished: %s onto %s",
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
- ret = reset_head(the_repository, NULL, "", opts->head_name,
- RESET_HEAD_REFS_ONLY,
- orig_head_reflog.buf, head_reflog.buf,
- DEFAULT_REFLOG_ACTION);
+ ropts.branch = opts->head_name;
+ ropts.flags = RESET_HEAD_REFS_ONLY;
+ ropts.branch_msg = branch_reflog.buf;
+ ropts.head_msg = head_reflog.buf;
+ ret = reset_head(the_repository, &ropts);
- strbuf_release(&orig_head_reflog);
+ strbuf_release(&branch_reflog);
strbuf_release(&head_reflog);
return ret;
}
@@ -671,13 +673,15 @@ static int run_am(struct rebase_options *opts)
status = run_command(&format_patch);
if (status) {
+ struct reset_head_opts ropts = { 0 };
unlink(rebased_patches);
free(rebased_patches);
strvec_clear(&am.args);
- reset_head(the_repository, &opts->orig_head, "checkout",
- opts->head_name, 0,
- "HEAD", NULL, DEFAULT_REFLOG_ACTION);
+ ropts.oid = &opts->orig_head;
+ ropts.branch = opts->head_name;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ reset_head(the_repository, &ropts);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
@@ -813,6 +817,26 @@ static int rebase_config(const char *var, const char *value, void *data)
return git_default_config(var, value, data);
}
+static int checkout_up_to_date(struct rebase_options *options)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct reset_head_opts ropts = { 0 };
+ int ret = 0;
+
+ strbuf_addf(&buf, "%s: checkout %s",
+ getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
+ options->switch_to);
+ ropts.oid = &options->orig_head;
+ ropts.branch = options->head_name;
+ ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ ropts.head_msg = buf.buf;
+ if (reset_head(the_repository, &ropts) < 0)
+ ret = error(_("could not switch to %s"), options->switch_to);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
/*
* Determines whether the commits in from..to are linear, i.e. contain
* no merge commits. This function *expects* `from` to be an ancestor of
@@ -1018,6 +1042,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
int reschedule_failed_exec = -1;
int allow_preemptive_ff = 1;
int preserve_merges_selected = 0;
+ struct reset_head_opts ropts = { 0 };
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
@@ -1255,9 +1280,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
-
- if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
- NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
+ ropts.flags = RESET_HEAD_HARD;
+ if (reset_head(the_repository, &ropts) < 0)
die(_("could not discard worktree changes"));
remove_branch_state(the_repository, 0);
if (read_basic_state(&options))
@@ -1274,9 +1298,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (read_basic_state(&options))
exit(1);
- if (reset_head(the_repository, &options.orig_head, "reset",
- options.head_name, RESET_HEAD_HARD,
- NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
+ ropts.oid = &options.orig_head;
+ ropts.branch = options.head_name;
+ ropts.flags = RESET_HEAD_HARD;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ if (reset_head(the_repository, &ropts) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
remove_branch_state(the_repository, 0);
@@ -1642,10 +1668,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (repo_read_index(the_repository) < 0)
die(_("could not read index"));
- if (options.autostash) {
- create_autostash(the_repository, state_dir_path("autostash", &options),
- DEFAULT_REFLOG_ACTION);
- }
+ if (options.autostash)
+ create_autostash(the_repository,
+ state_dir_path("autostash", &options));
+
if (require_clean_work_tree(the_repository, "rebase",
_("Please commit or stash them."), 1, 1)) {
@@ -1674,21 +1700,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (!(options.flags & REBASE_FORCE)) {
/* Lazily switch to the target branch if needed... */
if (options.switch_to) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s: checkout %s",
- getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
- options.switch_to);
- if (reset_head(the_repository,
- &options.orig_head, "checkout",
- options.head_name,
- RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, buf.buf,
- DEFAULT_REFLOG_ACTION) < 0) {
- ret = error(_("could not switch to "
- "%s"),
- options.switch_to);
+ ret = checkout_up_to_date(&options);
+ if (ret)
goto cleanup;
- }
}
if (!(options.flags & REBASE_NO_QUIET))
@@ -1755,10 +1769,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
- if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
- RESET_HEAD_DETACH | RESET_ORIG_HEAD |
- RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, msg.buf, DEFAULT_REFLOG_ACTION))
+ ropts.oid = &options.onto->object.oid;
+ ropts.orig_head = &options.orig_head,
+ ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ ropts.head_msg = msg.buf;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ if (reset_head(the_repository, &ropts))
die(_("Could not detach HEAD"));
strbuf_release(&msg);
@@ -1773,9 +1790,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "rebase finished: %s onto %s",
options.head_name ? options.head_name : "detached HEAD",
oid_to_hex(&options.onto->object.oid));
- reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
- RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
- DEFAULT_REFLOG_ACTION);
+ memset(&ropts, 0, sizeof(ropts));
+ ropts.branch = options.head_name;
+ ropts.flags = RESET_HEAD_REFS_ONLY;
+ ropts.head_msg = msg.buf;
+ reset_head(the_repository, &ropts);
strbuf_release(&msg);
ret = finish_rebase(&options);
goto cleanup;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c427ca09aa..d10aeb7e78 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1962,6 +1962,15 @@ static void execute_commands(struct command *commands,
}
/*
+ * If there is no command ready to run, should return directly to destroy
+ * temporary data in the quarantine area.
+ */
+ for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next)
+ ; /* nothing */
+ if (!cmd)
+ return;
+
+ /*
* Now we'll start writing out refs, which means the objects need
* to be in their final positions so that other processes can see them.
*/
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 85b838720c..c8f2b31873 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -818,7 +818,7 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
*/
static const char reflog_usage[] =
-N_("git reflog [ show | expire | delete | exists ]");
+"git reflog [ show | expire | delete | exists ]";
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
diff --git a/builtin/remote.c b/builtin/remote.c
index 299c466116..6f27ddc47b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -14,7 +14,7 @@
#include "commit-reach.h"
static const char * const builtin_remote_usage[] = {
- N_("git remote [-v | --verbose]"),
+ "git remote [-v | --verbose]",
N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"),
N_("git remote rename <old> <new>"),
N_("git remote remove <name>"),
diff --git a/builtin/replace.c b/builtin/replace.c
index 6ff1734d58..ac92337c0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -22,7 +22,7 @@ static const char * const git_replace_usage[] = {
N_("git replace [-f] <object> <replacement>"),
N_("git replace [-f] --edit <object>"),
N_("git replace [-f] --graft <commit> [<parent>...]"),
- N_("git replace [-f] --convert-graft-file"),
+ "git replace [-f] --convert-graft-file",
N_("git replace -d <object>..."),
N_("git replace [--format=<format>] [-l [<pattern>]]"),
NULL
diff --git a/builtin/reset.c b/builtin/reset.c
index b97745ee94..6e65e90c5d 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -204,10 +204,16 @@ static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
/*
* Special case: if the pattern is a path inside the cone
* followed by only wildcards, the pattern cannot match
- * partial sparse directories, so we don't expand the index.
+ * partial sparse directories, so we know we don't need to
+ * expand the index.
+ *
+ * Examples:
+ * - in-cone/foo***: doesn't need expanded index
+ * - not-in-cone/bar*: may need expanded index
+ * - **.c: may need expanded index
*/
- if (path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
- strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len)
+ if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
+ path_in_cone_mode_sparse_checkout(item.original, &the_index))
continue;
for (pos = 0; pos < active_nr; pos++) {
@@ -274,7 +280,6 @@ static int read_from_tree(const struct pathspec *pathspec,
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- clear_pathspec(&opt.pathspec);
return 0;
}
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 777558e9b0..38528c7f15 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -20,7 +20,7 @@
#include "packfile.h"
static const char rev_list_usage[] =
-"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+"git rev-list [<options>] <commit-id>... [-- <path>...]\n"
" limiting output:\n"
" --max-count=<n>\n"
" --max-age=<epoch>\n"
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 69c432ef1a..64962be016 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -145,7 +145,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)
if (value && !strcasecmp(value, "if-asked"))
args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
else
- return error("Invalid value for '%s'", k);
+ return error(_("invalid value for '%s'"), k);
}
}
}
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index e7f7af5de3..228d782754 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -388,6 +388,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
parse_revision_opt(&rev, &ctx, options, shortlog_usage);
}
parse_done:
+ revision_opts_finish(&rev);
argc = parse_options_end(&ctx);
if (nongit && argc > 1) {
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index e12c5e80e3..330b0553b9 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -8,6 +8,7 @@
#include "parse-options.h"
#include "dir.h"
#include "commit-slab.h"
+#include "date.h"
static const char* show_branch_usage[] = {
N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index a311483a7d..9c338d33ea 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -15,6 +15,7 @@
#include "wt-status.h"
#include "quote.h"
#include "sparse-index.h"
+#include "worktree.h"
static const char *empty_base = "";
@@ -43,7 +44,7 @@ static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
}
static char const * const builtin_sparse_checkout_list_usage[] = {
- N_("git sparse-checkout list"),
+ "git sparse-checkout list",
NULL
};
@@ -361,26 +362,23 @@ enum sparse_checkout_mode {
static int set_config(enum sparse_checkout_mode mode)
{
- const char *config_path;
-
- if (upgrade_repository_format(1) < 0)
- die(_("unable to upgrade repository format to enable worktreeConfig"));
- if (git_config_set_gently("extensions.worktreeConfig", "true")) {
- error(_("failed to set extensions.worktreeConfig setting"));
+ /* Update to use worktree config, if not already. */
+ if (init_worktree_config(the_repository)) {
+ error(_("failed to initialize worktree config"));
return 1;
}
- config_path = git_path("config.worktree");
- git_config_set_in_file_gently(config_path,
- "core.sparseCheckout",
- mode ? "true" : NULL);
-
- git_config_set_in_file_gently(config_path,
- "core.sparseCheckoutCone",
- mode == MODE_CONE_PATTERNS ? "true" : NULL);
+ if (repo_config_set_worktree_gently(the_repository,
+ "core.sparseCheckout",
+ mode ? "true" : "false") ||
+ repo_config_set_worktree_gently(the_repository,
+ "core.sparseCheckoutCone",
+ mode == MODE_CONE_PATTERNS ?
+ "true" : "false"))
+ return 1;
if (mode == MODE_NO_PATTERNS)
- set_sparse_index_config(the_repository, 0);
+ return set_sparse_index_config(the_repository, 0);
return 0;
}
@@ -421,7 +419,7 @@ static int update_modes(int *cone_mode, int *sparse_index)
}
static char const * const builtin_sparse_checkout_init_usage[] = {
- N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
+ "git sparse-checkout init [--cone] [--[no-]sparse-index]",
NULL
};
@@ -767,7 +765,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
}
static char const * const builtin_sparse_checkout_reapply_usage[] = {
- N_("git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]"),
+ "git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]",
NULL
};
@@ -805,7 +803,7 @@ static int sparse_checkout_reapply(int argc, const char **argv)
}
static char const * const builtin_sparse_checkout_disable_usage[] = {
- N_("git sparse-checkout disable"),
+ "git sparse-checkout disable",
NULL
};
diff --git a/builtin/stripspace.c b/builtin/stripspace.c
index be33eb83c1..1e34cf2beb 100644
--- a/builtin/stripspace.c
+++ b/builtin/stripspace.c
@@ -15,8 +15,8 @@ static void comment_lines(struct strbuf *buf)
}
static const char * const stripspace_usage[] = {
- N_("git stripspace [-s | --strip-comments]"),
- N_("git stripspace [-c | --comment-lines]"),
+ "git stripspace [-s | --strip-comments]",
+ "git stripspace [-c | --comment-lines]",
NULL
};
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c5d3fc3817..eeacefcc38 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -20,6 +20,8 @@
#include "diff.h"
#include "object-store.h"
#include "advice.h"
+#include "branch.h"
+#include "list-objects-filter-options.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@@ -1630,6 +1632,7 @@ struct module_clone_data {
const char *name;
const char *url;
const char *depth;
+ struct list_objects_filter_options *filter_options;
struct string_list reference;
unsigned int quiet: 1;
unsigned int progress: 1;
@@ -1796,6 +1799,10 @@ static int clone_submodule(struct module_clone_data *clone_data)
strvec_push(&cp.args, "--dissociate");
if (sm_gitdir && *sm_gitdir)
strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL);
+ if (clone_data->filter_options && clone_data->filter_options->choice)
+ strvec_pushf(&cp.args, "--filter=%s",
+ expand_list_objects_filter_spec(
+ clone_data->filter_options));
if (clone_data->single_branch >= 0)
strvec_push(&cp.args, clone_data->single_branch ?
"--single-branch" :
@@ -1852,6 +1859,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
{
int dissociate = 0, quiet = 0, progress = 0, require_init = 0;
struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
+ struct list_objects_filter_options filter_options;
struct option module_clone_options[] = {
OPT_STRING(0, "prefix", &clone_data.prefix,
@@ -1881,17 +1889,19 @@ static int module_clone(int argc, const char **argv, const char *prefix)
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &clone_data.single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
"[--reference <repository>] [--name <name>] [--depth <depth>] "
- "[--single-branch] "
+ "[--single-branch] [--filter <filter-spec>]"
"--url <url> --path <path>"),
NULL
};
+ memset(&filter_options, 0, sizeof(filter_options));
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
@@ -1899,12 +1909,14 @@ static int module_clone(int argc, const char **argv, const char *prefix)
clone_data.quiet = !!quiet;
clone_data.progress = !!progress;
clone_data.require_init = !!require_init;
+ clone_data.filter_options = &filter_options;
if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))
usage_with_options(git_submodule_helper_usage,
module_clone_options);
clone_submodule(&clone_data);
+ list_objects_filter_release(&filter_options);
return 0;
}
@@ -1994,6 +2006,7 @@ struct submodule_update_clone {
const char *recursive_prefix;
const char *prefix;
int single_branch;
+ struct list_objects_filter_options *filter_options;
/* to be consumed by git-submodule.sh */
struct update_clone_data *update_clone;
@@ -2154,6 +2167,9 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
strvec_pushl(&child->args, "--prefix", suc->prefix, NULL);
if (suc->recommend_shallow && sub->recommend_shallow == 1)
strvec_push(&child->args, "--depth=1");
+ if (suc->filter_options && suc->filter_options->choice)
+ strvec_pushf(&child->args, "--filter=%s",
+ expand_list_objects_filter_spec(suc->filter_options));
if (suc->require_init)
strvec_push(&child->args, "--require-init");
strvec_pushl(&child->args, "--path", sub->path, NULL);
@@ -2498,6 +2514,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
const char *update = NULL;
struct pathspec pathspec;
struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+ struct list_objects_filter_options filter_options;
+ int ret;
struct option module_update_clone_options[] = {
OPT_STRING(0, "prefix", &prefix,
@@ -2528,6 +2546,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &suc.single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
@@ -2540,20 +2559,26 @@ static int update_clone(int argc, const char **argv, const char *prefix)
update_clone_config_from_gitmodules(&suc.max_jobs);
git_config(git_update_clone_config, &suc.max_jobs);
+ memset(&filter_options, 0, sizeof(filter_options));
argc = parse_options(argc, argv, prefix, module_update_clone_options,
git_submodule_helper_usage, 0);
+ suc.filter_options = &filter_options;
if (update)
if (parse_submodule_update_strategy(update, &suc.update) < 0)
die(_("bad value for update parameter"));
- if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+ if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) {
+ list_objects_filter_release(&filter_options);
return 1;
+ }
if (pathspec.nr)
suc.warn_if_uninitialized = 1;
- return update_submodules(&suc);
+ ret = update_submodules(&suc);
+ list_objects_filter_release(&filter_options);
+ return ret;
}
static int run_update_procedure(int argc, const char **argv, const char *prefix)
@@ -2883,7 +2908,7 @@ static int module_config(int argc, const char **argv, const char *prefix)
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper config <name> [<value>]"),
N_("git submodule--helper config --unset <name>"),
- N_("git submodule--helper config --check-writeable"),
+ "git submodule--helper config --check-writeable",
NULL
};
@@ -2984,6 +3009,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
return !!ret;
}
+static int module_create_branch(int argc, const char **argv, const char *prefix)
+{
+ enum branch_track track;
+ int quiet = 0, force = 0, reflog = 0, dry_run = 0;
+
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("print only error messages")),
+ OPT__FORCE(&force, N_("force creation"), 0),
+ OPT_BOOL(0, "create-reflog", &reflog,
+ N_("create the branch's reflog")),
+ OPT_SET_INT('t', "track", &track,
+ N_("set up tracking mode (see git-pull(1))"),
+ BRANCH_TRACK_EXPLICIT),
+ OPT__DRY_RUN(&dry_run,
+ N_("show whether the branch would be created")),
+ OPT_END()
+ };
+ const char *const usage[] = {
+ N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"),
+ NULL
+ };
+
+ git_config(git_default_config, NULL);
+ track = git_branch_track;
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 3)
+ usage_with_options(usage, options);
+
+ if (!quiet && !dry_run)
+ printf_ln(_("creating branch '%s'"), argv[0]);
+
+ create_branches_recursively(the_repository, argv[0], argv[1], argv[2],
+ force, reflog, quiet, track, dry_run);
+ return 0;
+}
struct add_data {
const char *prefix;
const char *branch;
@@ -3390,6 +3451,7 @@ static struct cmd_struct commands[] = {
{"config", module_config, 0},
{"set-url", module_set_url, 0},
{"set-branch", module_set_branch, 0},
+ {"create-branch", module_create_branch, 0},
};
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --git a/builtin/tag.c b/builtin/tag.c
index 134b3f1edf..2479da0704 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -20,6 +20,7 @@
#include "oid-array.h"
#include "column.h"
#include "ref-filter.h"
+#include "date.h"
static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 7e0a0d9bf8..876112abb2 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -606,7 +606,7 @@ static struct cache_entry *read_one_ent(const char *which,
error("%s: not in %s branch.", path, which);
return NULL;
}
- if (mode == S_IFDIR) {
+ if (!the_index.sparse_index && mode == S_IFDIR) {
if (which)
error("%s: not a blob in %s branch.", path, which);
return NULL;
@@ -743,8 +743,6 @@ static int do_reupdate(int ac, const char **av,
*/
has_head = 0;
redo:
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++) {
const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
@@ -761,6 +759,16 @@ static int do_reupdate(int ac, const char **av,
discard_cache_entry(old);
continue; /* unchanged */
}
+
+ /* At this point, we know the contents of the sparse directory are
+ * modified with respect to HEAD, so we expand the index and restart
+ * to process each path individually
+ */
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ ensure_full_index(&the_index);
+ goto redo;
+ }
+
/* Be careful. The working tree may not have the
* path anymore, in which case, under 'allow_remove',
* or worse yet 'allow_replace', active_nr may decrease.
@@ -1088,6 +1096,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
+ prepare_repo_settings(r);
+ the_repository->settings.command_requires_full_index = 0;
+
/* we will diagnose later if it turns out that we need to update it */
newfd = hold_locked_index(&lock_file, 0);
if (newfd < 0)
@@ -1225,14 +1236,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
}
if (fsmonitor > 0) {
- if (git_config_get_fsmonitor() == 0)
+ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
+ if (fsm_mode == FSMONITOR_MODE_DISABLED) {
warning(_("core.fsmonitor is unset; "
"set it if you really want to "
"enable fsmonitor"));
+ }
add_fsmonitor(&the_index);
report(_("fsmonitor enabled"));
} else if (!fsmonitor) {
- if (git_config_get_fsmonitor() == 1)
+ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
+ if (fsm_mode > FSMONITOR_MODE_DISABLED)
warning(_("core.fsmonitor is set; "
"remove it if you really want to "
"disable fsmonitor"));
diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c
index 4321a34456..880fffec58 100644
--- a/builtin/update-server-info.c
+++ b/builtin/update-server-info.c
@@ -4,7 +4,7 @@
#include "parse-options.h"
static const char * const update_server_info_usage[] = {
- N_("git update-server-info [--force]"),
+ "git update-server-info [--force]",
NULL
};
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 0d0809276f..e23e6343d0 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -335,6 +335,69 @@ static int add_worktree(const char *path, const char *refname,
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
+ /*
+ * If the current worktree has sparse-checkout enabled, then copy
+ * the sparse-checkout patterns from the current worktree.
+ */
+ if (core_apply_sparse_checkout) {
+ char *from_file = git_pathdup("info/sparse-checkout");
+ char *to_file = xstrfmt("%s/info/sparse-checkout",
+ sb_repo.buf);
+
+ if (file_exists(from_file)) {
+ if (safe_create_leading_directories(to_file) ||
+ copy_file(to_file, from_file, 0666))
+ error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"),
+ from_file, to_file);
+ }
+
+ free(from_file);
+ free(to_file);
+ }
+
+ /*
+ * If we are using worktree config, then copy all current config
+ * values from the current worktree into the new one, that way the
+ * new worktree behaves the same as this one.
+ */
+ if (repository_format_worktree_config) {
+ char *from_file = git_pathdup("config.worktree");
+ char *to_file = xstrfmt("%s/config.worktree",
+ sb_repo.buf);
+
+ if (file_exists(from_file)) {
+ struct config_set cs = { { 0 } };
+ const char *core_worktree;
+ int bare;
+
+ if (safe_create_leading_directories(to_file) ||
+ copy_file(to_file, from_file, 0666)) {
+ error(_("failed to copy worktree config from '%s' to '%s'"),
+ from_file, to_file);
+ goto worktree_copy_cleanup;
+ }
+
+ git_configset_init(&cs);
+ git_configset_add_file(&cs, from_file);
+
+ if (!git_configset_get_bool(&cs, "core.bare", &bare) &&
+ bare &&
+ git_config_set_multivar_in_file_gently(
+ to_file, "core.bare", NULL, "true", 0))
+ error(_("failed to unset 'core.bare' in '%s'"), to_file);
+ if (!git_configset_get_value(&cs, "core.worktree", &core_worktree) &&
+ git_config_set_in_file_gently(to_file,
+ "core.worktree", NULL))
+ error(_("failed to unset 'core.worktree' in '%s'"), to_file);
+
+ git_configset_clear(&cs);
+ }
+
+worktree_copy_cleanup:
+ free(from_file);
+ free(to_file);
+ }
+
strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
cp.git_cmd = 1;