diff options
Diffstat (limited to 'builtin')
37 files changed, 1320 insertions, 641 deletions
diff --git a/builtin/am.c b/builtin/am.c index f22c73a05b..8355e3566f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2284,10 +2284,10 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("skip the current patch"), RESUME_SKIP), OPT_CMDMODE(0, "abort", &resume.mode, - N_("restore the original branch and abort the patching operation."), + N_("restore the original branch and abort the patching operation"), RESUME_ABORT), OPT_CMDMODE(0, "quit", &resume.mode, - N_("abort the patching operation but keep HEAD where it is."), + N_("abort the patching operation but keep HEAD where it is"), RESUME_QUIT), { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode, "(diff|raw)", diff --git a/builtin/blame.c b/builtin/blame.c index 6f7e32411a..b66e938022 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -866,33 +866,33 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *revs_file = NULL; const char *contents_from = NULL; const struct option options[] = { - OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), - OPT_BOOL('b', NULL, &blank_boundary, N_("Do not show object names of boundary commits (Default: off)")), - OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), - OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), - OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")), - OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), - OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), - OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), - OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN), - OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), - OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), - OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), - OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), - OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), - OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), - OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), - OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")), - OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")), + OPT_BOOL(0, "incremental", &incremental, N_("show blame entries as we find them, incrementally")), + OPT_BOOL('b', NULL, &blank_boundary, N_("do not show object names of boundary commits (Default: off)")), + OPT_BOOL(0, "root", &show_root, N_("do not treat root commits as boundaries (Default: off)")), + OPT_BOOL(0, "show-stats", &show_stats, N_("show work cost statistics")), + OPT_BOOL(0, "progress", &show_progress, N_("force progress reporting")), + OPT_BIT(0, "score-debug", &output_option, N_("show output score for blame entries"), OUTPUT_SHOW_SCORE), + OPT_BIT('f', "show-name", &output_option, N_("show original filename (Default: auto)"), OUTPUT_SHOW_NAME), + OPT_BIT('n', "show-number", &output_option, N_("show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), + OPT_BIT('p', "porcelain", &output_option, N_("show in a format designed for machine consumption"), OUTPUT_PORCELAIN), + OPT_BIT(0, "line-porcelain", &output_option, N_("show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), + OPT_BIT('c', NULL, &output_option, N_("use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), + OPT_BIT('t', NULL, &output_option, N_("show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), + OPT_BIT('l', NULL, &output_option, N_("show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), + OPT_BIT('s', NULL, &output_option, N_("suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), + OPT_BIT('e', "show-email", &output_option, N_("show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), + OPT_BIT('w', NULL, &xdl_opts, N_("ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("ignore <rev> when blaming")), + OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("ignore revisions from <file>")), OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), - OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), - OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), - OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), - OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), - OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback), + OPT_BIT(0, "minimal", &xdl_opts, N_("spend extra cycles to find better match"), XDF_NEED_MINIMAL), + OPT_STRING('S', NULL, &revs_file, N_("file"), N_("use revisions from <file> instead of calling git-rev-list")), + OPT_STRING(0, "contents", &contents_from, N_("file"), N_("use <file>'s contents as the final image")), + OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), + OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback), OPT_STRING_LIST('L', NULL, &range_list, N_("range"), - N_("Process only line range <start>,<end> or function :<funcname>")), + N_("process only line range <start>,<end> or function :<funcname>")), OPT__ABBREV(&abbrev), OPT_END() }; @@ -1151,7 +1151,7 @@ parse_done: sb.xdl_opts = xdl_opts; sb.no_whole_file_rename = no_whole_file_rename; - read_mailmap(&mailmap, NULL); + read_mailmap(&mailmap); sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π diff --git a/builtin/branch.c b/builtin/branch.c index efb30b8820..8c0b428104 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -538,7 +538,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); - if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && + (!head || strcmp(oldname, head) || !is_null_oid(&head_oid)) && + rename_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch rename failed")); if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch copy failed")); @@ -724,7 +726,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) print_current_branch_name(); return 0; } else if (list) { - /* git branch --local also shows HEAD when it is detached */ + /* git branch --list also shows HEAD when it is detached */ if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) filter.kind |= FILTER_REFS_DETACHED_HEAD; filter.name_patterns = argv; @@ -737,7 +739,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) */ if (!sorting) sorting = ref_default_sorting(); - ref_sorting_icase_all(sorting, icase); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); + ref_sorting_set_sort_flags_all( + sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1); print_ref_list(&filter, sorting, &format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); @@ -829,10 +833,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("Branch '%s' has no upstream information"), branch->name); strbuf_addf(&buf, "branch.%s.remote", branch->name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.merge", branch->name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_release(&buf); } else if (argc > 0 && argc <= 2) { if (filter.kind != FILTER_REFS_BRANCHES) diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 3ad4b9b62e..ad3cc9c02f 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -125,6 +125,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) struct strbuf report_path = STRBUF_INIT; int report = -1; time_t now = time(NULL); + struct tm tm; char *option_output = NULL; char *option_suffix = "%Y-%m-%d-%H%M"; const char *user_relative_path = NULL; @@ -147,7 +148,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) strbuf_complete(&report_path, '/'); strbuf_addstr(&report_path, "git-bugreport-"); - strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0); + strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0); strbuf_addstr(&report_path, ".txt"); switch (safe_create_leading_directories(report_path.buf)) { diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index cdce144f3b..7dc47e4793 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -47,7 +47,7 @@ int cmd_check_mailmap(int argc, const char **argv, const char *prefix) if (argc == 0 && !use_stdin) die(_("no contacts specified")); - read_mailmap(&mailmap, NULL); + read_mailmap(&mailmap); for (i = 0; i < argc; ++i) check_mailmap(&mailmap, argv[i]); diff --git a/builtin/checkout.c b/builtin/checkout.c index 9b82119129..c9ba23c279 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -480,9 +480,11 @@ static int checkout_paths(const struct checkout_opts *opts, * with the hex of the commit (whether it's in `...` form or * not) for the run_add_interactive() machinery to work * properly. However, there is special logic for the HEAD case - * so we mustn't replace that. + * so we mustn't replace that. Also, when we were given a + * tree-object, new_branch_info->commit would be NULL, but we + * do not have to do any replacement, either. */ - if (rev && strcmp(rev, "HEAD")) + if (rev && new_branch_info->commit && strcmp(rev, "HEAD")) rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid); if (opts->checkout_index && opts->checkout_worktree) diff --git a/builtin/clone.c b/builtin/clone.c index a0841923cf..e335734b4c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1293,8 +1293,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix) break; } - if (!is_local && !complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + if (!is_local && !complete_refs_before_fetch) { + err = transport_fetch_refs(transport, mapped_refs); + if (err) + goto cleanup; + } remote_head = find_ref_by_name(refs, "HEAD"); remote_head_points_at = @@ -1323,7 +1326,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head = NULL; option_no_checkout = 1; if (!option_bare) { - const char *branch = git_default_branch_name(); + const char *branch = git_default_branch_name(0); char *ref = xstrfmt("refs/heads/%s", branch); install_branch_config(0, branch, remote_name, ref); @@ -1339,8 +1342,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (is_local) clone_local(path, git_dir); - else if (refs && complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + else if (refs && complete_refs_before_fetch) { + err = transport_fetch_refs(transport, mapped_refs); + if (err) + goto cleanup; + } update_remote_refs(refs, mapped_refs, remote_head_points_at, branch_top.buf, reflog_msg.buf, transport, @@ -1367,6 +1373,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) junk_mode = JUNK_LEAVE_REPO; err = checkout(submodule_progress); +cleanup: free(remote_name); strbuf_release(&reflog_msg); strbuf_release(&branch_top); diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 78fa08f43a..cd86315221 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -78,7 +78,7 @@ static int graph_verify(int argc, const char **argv) static struct option builtin_commit_graph_verify_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), - N_("The object directory to store the graph")), + N_("the object directory to store the graph")), OPT_BOOL(0, "shallow", &opts.shallow, N_("if the commit-graph is split, only verify the tip file")), OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), @@ -208,7 +208,7 @@ static int graph_write(int argc, const char **argv) static struct option builtin_commit_graph_write_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), - N_("The object directory to store the graph")), + N_("the object directory to store the graph")), OPT_BOOL(0, "reachable", &opts.reachable, N_("start walk at all refs")), OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, @@ -314,7 +314,7 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix) static struct option builtin_commit_graph_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), - N_("The object directory to store the graph")), + N_("the object directory to store the graph")), OPT_END(), }; diff --git a/builtin/commit.c b/builtin/commit.c index 505fe60956..739110c5a7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1039,7 +1039,7 @@ static const char *find_author_by_nickname(const char *name) av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); revs.mailmap = &mailmap; - read_mailmap(revs.mailmap, NULL); + read_mailmap(revs.mailmap); if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); diff --git a/builtin/config.c b/builtin/config.c index 963d65fd3f..f71fa39b38 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -14,6 +14,7 @@ static const char *const builtin_config_usage[] = { static char *key; static regex_t *key_regexp; +static const char *value_pattern; static regex_t *regexp; static int show_keys; static int omit_values; @@ -34,6 +35,7 @@ static int respect_includes_opt = -1; static struct config_options config_options; static int show_origin; static int show_scope; +static int fixed_value; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -133,17 +135,18 @@ static struct option builtin_config_options[] = { OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), - OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), - OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), - OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP), + OPT_BIT(0, "get", &actions, N_("get value: name [value-pattern]"), ACTION_GET), + OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-pattern]"), ACTION_GET_ALL), + OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-pattern]"), ACTION_GET_REGEXP), OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH), - OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL), + OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value-pattern]"), ACTION_REPLACE_ALL), OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), - OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET), - OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL), + OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-pattern]"), ACTION_UNSET), + OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-pattern]"), ACTION_UNSET_ALL), OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), + OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")), OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), @@ -296,6 +299,8 @@ static int collect_config(const char *key_, const char *value_, void *cb) return 0; if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) return 0; + if (fixed_value && strcmp(value_pattern, (value_?value_:""))) + return 0; if (regexp != NULL && (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) return 0; @@ -306,7 +311,7 @@ static int collect_config(const char *key_, const char *value_, void *cb) return format_config(&values->items[values->nr++], key_, value_); } -static int get_value(const char *key_, const char *regex_) +static int get_value(const char *key_, const char *regex_, unsigned flags) { int ret = CONFIG_GENERIC_ERROR; struct strbuf_list values = {NULL}; @@ -343,7 +348,9 @@ static int get_value(const char *key_, const char *regex_) } } - if (regex_) { + if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE)) + value_pattern = regex_; + else if (regex_) { if (regex_[0] == '!') { do_not_match = 1; regex_++; @@ -631,6 +638,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; char *value; + int flags = 0; given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); @@ -766,6 +774,42 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_builtin_config(); } + /* check usage of --fixed-value */ + if (fixed_value) { + int allowed_usage = 0; + + switch (actions) { + /* git config --get <name> <value-pattern> */ + case ACTION_GET: + /* git config --get-all <name> <value-pattern> */ + case ACTION_GET_ALL: + /* git config --get-regexp <name-pattern> <value-pattern> */ + case ACTION_GET_REGEXP: + /* git config --unset <name> <value-pattern> */ + case ACTION_UNSET: + /* git config --unset-all <name> <value-pattern> */ + case ACTION_UNSET_ALL: + allowed_usage = argc > 1 && !!argv[1]; + break; + + /* git config <name> <value> <value-pattern> */ + case ACTION_SET_ALL: + /* git config --replace-all <name> <value> <value-pattern> */ + case ACTION_REPLACE_ALL: + allowed_usage = argc > 2 && !!argv[2]; + break; + + /* other options don't allow --fixed-value */ + } + + if (!allowed_usage) { + error(_("--fixed-value only applies with 'value-pattern'")); + usage_builtin_config(); + } + + flags |= CONFIG_FLAGS_FIXED_VALUE; + } + if (actions & PAGING_ACTIONS) setup_auto_pager("config", 1); @@ -827,7 +871,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) value = normalize_value(argv[0], argv[1]); UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], value, argv[2], 0); + argv[0], value, argv[2], + flags); } else if (actions == ACTION_ADD) { check_write(); @@ -836,7 +881,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, - CONFIG_REGEX_NONE, 0); + CONFIG_REGEX_NONE, + flags); } else if (actions == ACTION_REPLACE_ALL) { check_write(); @@ -844,23 +890,24 @@ int cmd_config(int argc, const char **argv, const char *prefix) value = normalize_value(argv[0], argv[1]); UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], value, argv[2], 1); + argv[0], value, argv[2], + flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_ALL) { do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_REGEXP) { show_keys = 1; use_key_regexp = 1; do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_URLMATCH) { check_argc(argc, 2, 2); @@ -871,7 +918,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 1, 2); if (argc == 2) return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], 0); + argv[0], NULL, argv[1], + flags); else return git_config_set_in_file_gently(given_config_source.file, argv[0], NULL); @@ -880,7 +928,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 1, 2); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], 1); + argv[0], NULL, argv[1], + flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_RENAME_SECTION) { int ret; diff --git a/builtin/credential-store.c b/builtin/credential-store.c index 5331ab151a..ae3c1ba75f 100644 --- a/builtin/credential-store.c +++ b/builtin/credential-store.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "credential.h" #include "string-list.h" @@ -58,8 +59,11 @@ static void print_line(struct strbuf *buf) static void rewrite_credential_file(const char *fn, struct credential *c, struct strbuf *extra) { - if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0) - die_errno("unable to get credential storage lock"); + int timeout_ms = 1000; + + git_config_get_int("credentialstore.locktimeoutms", &timeout_ms); + if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0) + die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms); if (extra) print_line(extra); parse_credential_file(fn, c, NULL, print_line); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index d2e33f5005..85a76e0ef8 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -923,7 +923,6 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name) if (!tag) die("Tag %s points nowhere?", e->name); return (struct commit *)tag; - break; } default: return NULL; @@ -1206,32 +1205,32 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) N_("select handling of commit messages in an alternate encoding"), parse_opt_reencode_mode), OPT_STRING(0, "export-marks", &export_filename, N_("file"), - N_("Dump marks to this file")), + N_("dump marks to this file")), OPT_STRING(0, "import-marks", &import_filename, N_("file"), - N_("Import marks from this file")), + N_("import marks from this file")), OPT_STRING(0, "import-marks-if-exists", &import_filename_if_exists, N_("file"), - N_("Import marks from this file if it exists")), + N_("import marks from this file if it exists")), OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger, - N_("Fake a tagger when tags lack one")), + N_("fake a tagger when tags lack one")), OPT_BOOL(0, "full-tree", &full_tree, - N_("Output full tree for each commit")), + N_("output full tree for each commit")), OPT_BOOL(0, "use-done-feature", &use_done_feature, - N_("Use the done feature to terminate the stream")), - OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), + N_("use the done feature to terminate the stream")), + OPT_BOOL(0, "no-data", &no_data, N_("skip output of blob data")), OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), - N_("Apply refspec to exported refs")), + N_("apply refspec to exported refs")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"), N_("convert <from> to <to> in anonymized output"), PARSE_OPT_NONEG, parse_opt_anonymize_map), OPT_BOOL(0, "reference-excluded-parents", - &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")), + &reference_excluded_commits, N_("reference parents which are not in fast-export stream by object id")), OPT_BOOL(0, "show-original-ids", &show_original_ids, - N_("Show original object ids of blobs/commits")), + N_("show original object ids of blobs/commits")), OPT_BOOL(0, "mark-tags", &mark_tags, - N_("Label tags with mark ids")), + N_("label tags with mark ids")), OPT_END() }; diff --git a/builtin/fetch.c b/builtin/fetch.c index ecf8537605..91f3d20696 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -63,6 +63,7 @@ static int enable_auto_gc = 1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; static int max_jobs = -1, submodule_fetch_jobs_config = -1; static int fetch_parallel_config = 1; +static int atomic_fetch; static enum transport_family family; static const char *depth; static const char *deepen_since; @@ -144,6 +145,8 @@ static struct option builtin_fetch_options[] = { N_("set upstream for git pull/fetch")), OPT_BOOL('a', "append", &append, N_("append to .git/FETCH_HEAD instead of overwriting")), + OPT_BOOL(0, "atomic", &atomic_fetch, + N_("use atomic transaction to update references")), OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), N_("path to upload pack on remote end")), OPT__FORCE(&force, N_("force overwrite of local reference"), 0), @@ -583,13 +586,14 @@ static struct ref *get_ref_map(struct remote *remote, static int s_update_ref(const char *action, struct ref *ref, + struct ref_transaction *transaction, int check_old) { char *msg; char *rla = getenv("GIT_REFLOG_ACTION"); - struct ref_transaction *transaction; + struct ref_transaction *our_transaction = NULL; struct strbuf err = STRBUF_INIT; - int ret, df_conflict = 0; + int ret; if (dry_run) return 0; @@ -597,31 +601,47 @@ static int s_update_ref(const char *action, rla = default_rla.buf; msg = xstrfmt("%s: %s", rla, action); - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, ref->name, - &ref->new_oid, - check_old ? &ref->old_oid : NULL, - 0, msg, &err)) - goto fail; + /* + * If no transaction was passed to us, we manage the transaction + * ourselves. Otherwise, we trust the caller to handle the transaction + * lifecycle. + */ + if (!transaction) { + transaction = our_transaction = ref_transaction_begin(&err); + if (!transaction) { + ret = STORE_REF_ERROR_OTHER; + goto out; + } + } - ret = ref_transaction_commit(transaction, &err); + ret = ref_transaction_update(transaction, ref->name, &ref->new_oid, + check_old ? &ref->old_oid : NULL, + 0, msg, &err); if (ret) { - df_conflict = (ret == TRANSACTION_NAME_CONFLICT); - goto fail; + ret = STORE_REF_ERROR_OTHER; + goto out; } - ref_transaction_free(transaction); - strbuf_release(&err); - free(msg); - return 0; -fail: - ref_transaction_free(transaction); - error("%s", err.buf); + if (our_transaction) { + switch (ref_transaction_commit(our_transaction, &err)) { + case 0: + break; + case TRANSACTION_NAME_CONFLICT: + ret = STORE_REF_ERROR_DF_CONFLICT; + goto out; + default: + ret = STORE_REF_ERROR_OTHER; + goto out; + } + } + +out: + ref_transaction_free(our_transaction); + if (ret) + error("%s", err.buf); strbuf_release(&err); free(msg); - return df_conflict ? STORE_REF_ERROR_DF_CONFLICT - : STORE_REF_ERROR_OTHER; + return ret; } static int refcol_width = 10; @@ -759,6 +779,7 @@ static void format_display(struct strbuf *display, char code, } static int update_local_ref(struct ref *ref, + struct ref_transaction *transaction, const char *remote, const struct ref *remote_ref, struct strbuf *display, @@ -799,7 +820,7 @@ static int update_local_ref(struct ref *ref, starts_with(ref->name, "refs/tags/")) { if (force || ref->force) { int r; - r = s_update_ref("updating tag", ref, 0); + r = s_update_ref("updating tag", ref, transaction, 0); format_display(display, r ? '!' : 't', _("[tag update]"), r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -836,7 +857,7 @@ static int update_local_ref(struct ref *ref, what = _("[new ref]"); } - r = s_update_ref(msg, ref, 0); + r = s_update_ref(msg, ref, transaction, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -858,7 +879,7 @@ static int update_local_ref(struct ref *ref, strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, ".."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - r = s_update_ref("fast-forward", ref, 1); + r = s_update_ref("fast-forward", ref, transaction, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -870,7 +891,7 @@ static int update_local_ref(struct ref *ref, strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, "..."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - r = s_update_ref("forced-update", ref, 1); + r = s_update_ref("forced-update", ref, transaction, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), remote, pretty_ref, summary_width); @@ -897,6 +918,89 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid) return 0; } +struct fetch_head { + FILE *fp; + struct strbuf buf; +}; + +static int open_fetch_head(struct fetch_head *fetch_head) +{ + const char *filename = git_path_fetch_head(the_repository); + + if (write_fetch_head) { + fetch_head->fp = fopen(filename, "a"); + if (!fetch_head->fp) + return error_errno(_("cannot open %s"), filename); + strbuf_init(&fetch_head->buf, 0); + } else { + fetch_head->fp = NULL; + } + + return 0; +} + +static void append_fetch_head(struct fetch_head *fetch_head, + const struct object_id *old_oid, + enum fetch_head_status fetch_head_status, + const char *note, + const char *url, size_t url_len) +{ + char old_oid_hex[GIT_MAX_HEXSZ + 1]; + const char *merge_status_marker; + size_t i; + + if (!fetch_head->fp) + return; + + switch (fetch_head_status) { + case FETCH_HEAD_NOT_FOR_MERGE: + merge_status_marker = "not-for-merge"; + break; + case FETCH_HEAD_MERGE: + merge_status_marker = ""; + break; + default: + /* do not write anything to FETCH_HEAD */ + return; + } + + strbuf_addf(&fetch_head->buf, "%s\t%s\t%s", + oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + strbuf_addstr(&fetch_head->buf, "\\n"); + else + strbuf_addch(&fetch_head->buf, url[i]); + strbuf_addch(&fetch_head->buf, '\n'); + + /* + * When using an atomic fetch, we do not want to update FETCH_HEAD if + * any of the reference updates fails. We thus have to write all + * updates to a buffer first and only commit it as soon as all + * references have been successfully updated. + */ + if (!atomic_fetch) { + strbuf_write(&fetch_head->buf, fetch_head->fp); + strbuf_reset(&fetch_head->buf); + } +} + +static void commit_fetch_head(struct fetch_head *fetch_head) +{ + if (!fetch_head->fp || !atomic_fetch) + return; + strbuf_write(&fetch_head->buf, fetch_head->fp); +} + +static void close_fetch_head(struct fetch_head *fetch_head) +{ + if (!fetch_head->fp) + return; + + fclose(fetch_head->fp); + strbuf_release(&fetch_head->buf); +} + static const char warn_show_forced_updates[] = N_("Fetch normally indicates which branches had a forced update,\n" "but that check has been disabled. To re-enable, use '--show-forced-updates'\n" @@ -909,22 +1013,20 @@ N_("It took %.2f seconds to check forced updates. You can use\n" static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref *ref_map) { - FILE *fp; + struct fetch_head fetch_head; struct commit *commit; int url_len, i, rc = 0; - struct strbuf note = STRBUF_INIT; + struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct ref_transaction *transaction = NULL; const char *what, *kind; struct ref *rm; char *url; - const char *filename = (!write_fetch_head - ? "/dev/null" - : git_path_fetch_head(the_repository)); int want_status; int summary_width = transport_summary_width(ref_map); - fp = fopen(filename, "a"); - if (!fp) - return error_errno(_("cannot open %s"), filename); + rc = open_fetch_head(&fetch_head); + if (rc) + return -1; if (raw_url) url = transport_anonymize_url(raw_url); @@ -941,6 +1043,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } } + if (atomic_fetch) { + transaction = ref_transaction_begin(&err); + if (!transaction) { + error("%s", err.buf); + goto abort; + } + } + prepare_format_display(ref_map); /* @@ -953,7 +1063,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, want_status++) { for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; - const char *merge_status_marker = ""; if (rm->status == REF_STATUS_REJECT_SHALLOW) { if (want_status == FETCH_HEAD_MERGE) @@ -1011,31 +1120,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_addf(¬e, "%s ", kind); strbuf_addf(¬e, "'%s' of ", what); } - switch (rm->fetch_head_status) { - case FETCH_HEAD_NOT_FOR_MERGE: - merge_status_marker = "not-for-merge"; - /* fall-through */ - case FETCH_HEAD_MERGE: - fprintf(fp, "%s\t%s\t%s", - oid_to_hex(&rm->old_oid), - merge_status_marker, - note.buf); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); - break; - default: - /* do not write anything to FETCH_HEAD */ - break; - } + + append_fetch_head(&fetch_head, &rm->old_oid, + rm->fetch_head_status, + note.buf, url, url_len); strbuf_reset(¬e); if (ref) { - rc |= update_local_ref(ref, what, rm, ¬e, - summary_width); + rc |= update_local_ref(ref, transaction, what, + rm, ¬e, summary_width); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1060,6 +1153,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } } + if (!rc && transaction) { + rc = ref_transaction_commit(transaction, &err); + if (rc) { + error("%s", err.buf); + goto abort; + } + } + + if (!rc) + commit_fetch_head(&fetch_head); + if (rc & STORE_REF_ERROR_DF_CONFLICT) error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " @@ -1076,8 +1180,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, abort: strbuf_release(¬e); + strbuf_release(&err); + ref_transaction_free(transaction); free(url); - fclose(fp); + close_fetch_head(&fetch_head); return rc; } @@ -1887,6 +1993,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) die(_("--filter can only be used with the remote " "configured in extensions.partialclone")); + if (atomic_fetch) + die(_("--atomic can only be used when fetching " + "from one remote")); + if (stdin_refspecs) die(_("--stdin can only be used when fetching " "from one remote")); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 9d1ecda2b8..cb9c81a046 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -70,7 +70,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); - ref_sorting_icase_all(sorting, icase); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; filter.name_patterns = argv; diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c index 5bba623ff1..52be64a437 100644 --- a/builtin/for-each-repo.c +++ b/builtin/for-each-repo.c @@ -51,6 +51,13 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix) values = repo_config_get_value_multi(the_repository, config_key); + /* + * Do nothing on an empty list, which is equivalent to the case + * where the config variable does not exist at all. + */ + if (!values) + return 0; + for (i = 0; !result && i < values->nr; i++) result = run_command_on_repo(values->items[i].string, &args); diff --git a/builtin/fsck.c b/builtin/fsck.c index fbf26cafcf..821e7798c7 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -73,25 +73,7 @@ static const char *printable_type(const struct object_id *oid, static int fsck_config(const char *var, const char *value, void *cb) { - if (strcmp(var, "fsck.skiplist") == 0) { - const char *path; - struct strbuf sb = STRBUF_INIT; - - if (git_config_pathname(&path, var, value)) - return 1; - strbuf_addf(&sb, "skiplist=%s", path); - free((char *)path); - fsck_set_msg_types(&fsck_obj_options, sb.buf); - strbuf_release(&sb); - return 0; - } - - if (skip_prefix(var, "fsck.", &var)) { - fsck_set_msg_type(&fsck_obj_options, var, value); - return 0; - } - - return git_default_config(var, value, cb); + return fsck_config_internal(var, value, cb, &fsck_obj_options); } static int objerror(struct object *obj, const char *err) diff --git a/builtin/gc.c b/builtin/gc.c index 3d258b60c2..fe35b10fe9 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -92,7 +92,7 @@ static void process_log_file(void) */ int saved_errno = errno; fprintf(stderr, _("Failed to fstat %s: %s"), - get_tempfile_path(log_lock.tempfile), + get_lock_file_path(&log_lock), strerror(saved_errno)); fflush(stderr); commit_lock_file(&log_lock); @@ -534,7 +534,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) const char *name; pid_t pid; int daemonized = 0; - int keep_base_pack = -1; + int keep_largest_pack = -1; timestamp_t dummy; struct option builtin_gc_options[] = { @@ -548,7 +548,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) OPT_BOOL_F(0, "force", &force, N_("force running gc even if there may be another gc running"), PARSE_OPT_NOCOMPLETE), - OPT_BOOL(0, "keep-largest-pack", &keep_base_pack, + OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack, N_("repack all other packs except the largest pack")), OPT_END() }; @@ -625,8 +625,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) } else { struct string_list keep_pack = STRING_LIST_INIT_NODUP; - if (keep_base_pack != -1) { - if (keep_base_pack) + if (keep_largest_pack != -1) { + if (keep_largest_pack) find_base_packs(&keep_pack, 0); } else if (big_pack_threshold) { find_base_packs(&keep_pack, big_pack_threshold); @@ -1253,10 +1253,8 @@ static struct maintenance_task tasks[] = { static int compare_tasks_by_selection(const void *a_, const void *b_) { - const struct maintenance_task *a, *b; - - a = (const struct maintenance_task *)&a_; - b = (const struct maintenance_task *)&b_; + const struct maintenance_task *a = a_; + const struct maintenance_task *b = b_; return b->selected_order - a->selected_order; } @@ -1448,10 +1446,6 @@ static int maintenance_register(void) struct child_process config_set = CHILD_PROCESS_INIT; struct child_process config_get = CHILD_PROCESS_INIT; - /* There is no current repository, so skip registering it */ - if (!the_repository || !the_repository->gitdir) - return 0; - /* Disable foreground maintenance */ git_config_set("maintenance.auto", "false"); @@ -1462,7 +1456,8 @@ static int maintenance_register(void) git_config_set("maintenance.strategy", "incremental"); config_get.git_cmd = 1; - strvec_pushl(&config_get.args, "config", "--global", "--get", "maintenance.repo", + strvec_pushl(&config_get.args, "config", "--global", "--get", + "--fixed-value", "maintenance.repo", the_repository->worktree ? the_repository->worktree : the_repository->gitdir, NULL); @@ -1488,12 +1483,9 @@ static int maintenance_unregister(void) { struct child_process config_unset = CHILD_PROCESS_INIT; - if (!the_repository || !the_repository->gitdir) - return error(_("no current repository to unregister")); - config_unset.git_cmd = 1; strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "maintenance.repo", + "--fixed-value", "maintenance.repo", the_repository->worktree ? the_repository->worktree : the_repository->gitdir, NULL); @@ -1501,38 +1493,368 @@ static int maintenance_unregister(void) return run_command(&config_unset); } +static const char *get_frequency(enum schedule_priority schedule) +{ + switch (schedule) { + case SCHEDULE_HOURLY: + return "hourly"; + case SCHEDULE_DAILY: + return "daily"; + case SCHEDULE_WEEKLY: + return "weekly"; + default: + BUG("invalid schedule %d", schedule); + } +} + +static char *launchctl_service_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "org.git-scm.git.%s", frequency); + return strbuf_detach(&label, NULL); +} + +static char *launchctl_service_filename(const char *name) +{ + char *expanded; + struct strbuf filename = STRBUF_INIT; + strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name); + + expanded = expand_user_path(filename.buf, 1); + if (!expanded) + die(_("failed to expand path '%s'"), filename.buf); + + strbuf_release(&filename); + return expanded; +} + +static char *launchctl_get_uid(void) +{ + return xstrfmt("gui/%d", getuid()); +} + +static int launchctl_boot_plist(int enable, const char *filename, const char *cmd) +{ + int result; + struct child_process child = CHILD_PROCESS_INIT; + char *uid = launchctl_get_uid(); + + strvec_split(&child.args, cmd); + if (enable) + strvec_push(&child.args, "bootstrap"); + else + strvec_push(&child.args, "bootout"); + strvec_push(&child.args, uid); + strvec_push(&child.args, filename); + + child.no_stderr = 1; + child.no_stdout = 1; + + if (start_command(&child)) + die(_("failed to start launchctl")); + + result = finish_command(&child); + + free(uid); + return result; +} + +static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd) +{ + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + int result = launchctl_boot_plist(0, filename, cmd); + unlink(filename); + free(filename); + free(name); + return result; +} + +static int launchctl_remove_plists(const char *cmd) +{ + return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) || + launchctl_remove_plist(SCHEDULE_DAILY, cmd) || + launchctl_remove_plist(SCHEDULE_WEEKLY, cmd); +} + +static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd) +{ + FILE *plist; + int i; + const char *preamble, *repeat; + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + + if (safe_create_leading_directories(filename)) + die(_("failed to create directories for '%s'"), filename); + plist = xfopen(filename, "w"); + + preamble = "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">" + "<dict>\n" + "<key>Label</key><string>%s</string>\n" + "<key>ProgramArguments</key>\n" + "<array>\n" + "<string>%s/git</string>\n" + "<string>--exec-path=%s</string>\n" + "<string>for-each-repo</string>\n" + "<string>--config=maintenance.repo</string>\n" + "<string>maintenance</string>\n" + "<string>run</string>\n" + "<string>--schedule=%s</string>\n" + "</array>\n" + "<key>StartCalendarInterval</key>\n" + "<array>\n"; + fprintf(plist, preamble, name, exec_path, exec_path, frequency); + + switch (schedule) { + case SCHEDULE_HOURLY: + repeat = "<dict>\n" + "<key>Hour</key><integer>%d</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"; + for (i = 1; i <= 23; i++) + fprintf(plist, repeat, i); + break; + + case SCHEDULE_DAILY: + repeat = "<dict>\n" + "<key>Day</key><integer>%d</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"; + for (i = 1; i <= 6; i++) + fprintf(plist, repeat, i); + break; + + case SCHEDULE_WEEKLY: + fprintf(plist, + "<dict>\n" + "<key>Day</key><integer>0</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"); + break; + + default: + /* unreachable */ + break; + } + fprintf(plist, "</array>\n</dict>\n</plist>\n"); + fclose(plist); + + /* bootout might fail if not already running, so ignore */ + launchctl_boot_plist(0, filename, cmd); + if (launchctl_boot_plist(1, filename, cmd)) + die(_("failed to bootstrap service %s"), filename); + + free(filename); + free(name); + return 0; +} + +static int launchctl_add_plists(const char *cmd) +{ + const char *exec_path = git_exec_path(); + + return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) || + launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) || + launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd); +} + +static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + if (run_maintenance) + return launchctl_add_plists(cmd); + else + return launchctl_remove_plists(cmd); +} + +static char *schtasks_task_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "Git Maintenance (%s)", frequency); + return strbuf_detach(&label, NULL); +} + +static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd) +{ + int result; + struct strvec args = STRVEC_INIT; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + + strvec_split(&args, cmd); + strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL); + + result = run_command_v_opt(args.v, 0); + + strvec_clear(&args); + free(name); + return result; +} + +static int schtasks_remove_tasks(const char *cmd) +{ + return schtasks_remove_task(SCHEDULE_HOURLY, cmd) || + schtasks_remove_task(SCHEDULE_DAILY, cmd) || + schtasks_remove_task(SCHEDULE_WEEKLY, cmd); +} + +static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd) +{ + int result; + struct child_process child = CHILD_PROCESS_INIT; + const char *xml; + struct tempfile *tfile; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + struct strbuf tfilename = STRBUF_INIT; + + strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX", + get_git_common_dir(), frequency); + tfile = xmks_tempfile(tfilename.buf); + strbuf_release(&tfilename); + + if (!fdopen_tempfile(tfile, "w")) + die(_("failed to create temp xml file")); + + xml = "<?xml version=\"1.0\" ?>\n" + "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n" + "<Triggers>\n" + "<CalendarTrigger>\n"; + fputs(xml, tfile->fp); + + switch (schedule) { + case SCHEDULE_HOURLY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByDay>\n" + "<DaysInterval>1</DaysInterval>\n" + "</ScheduleByDay>\n" + "<Repetition>\n" + "<Interval>PT1H</Interval>\n" + "<Duration>PT23H</Duration>\n" + "<StopAtDurationEnd>false</StopAtDurationEnd>\n" + "</Repetition>\n"); + break; + + case SCHEDULE_DAILY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByWeek>\n" + "<DaysOfWeek>\n" + "<Monday />\n" + "<Tuesday />\n" + "<Wednesday />\n" + "<Thursday />\n" + "<Friday />\n" + "<Saturday />\n" + "</DaysOfWeek>\n" + "<WeeksInterval>1</WeeksInterval>\n" + "</ScheduleByWeek>\n"); + break; + + case SCHEDULE_WEEKLY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByWeek>\n" + "<DaysOfWeek>\n" + "<Sunday />\n" + "</DaysOfWeek>\n" + "<WeeksInterval>1</WeeksInterval>\n" + "</ScheduleByWeek>\n"); + break; + + default: + break; + } + + xml = "</CalendarTrigger>\n" + "</Triggers>\n" + "<Principals>\n" + "<Principal id=\"Author\">\n" + "<LogonType>InteractiveToken</LogonType>\n" + "<RunLevel>LeastPrivilege</RunLevel>\n" + "</Principal>\n" + "</Principals>\n" + "<Settings>\n" + "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n" + "<Enabled>true</Enabled>\n" + "<Hidden>true</Hidden>\n" + "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n" + "<WakeToRun>false</WakeToRun>\n" + "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n" + "<Priority>7</Priority>\n" + "</Settings>\n" + "<Actions Context=\"Author\">\n" + "<Exec>\n" + "<Command>\"%s\\git.exe\"</Command>\n" + "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n" + "</Exec>\n" + "</Actions>\n" + "</Task>\n"; + fprintf(tfile->fp, xml, exec_path, exec_path, frequency); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml", + get_tempfile_path(tfile), NULL); + close_tempfile_gently(tfile); + + child.no_stdout = 1; + child.no_stderr = 1; + + if (start_command(&child)) + die(_("failed to start schtasks")); + result = finish_command(&child); + + delete_tempfile(&tfile); + free(name); + return result; +} + +static int schtasks_schedule_tasks(const char *cmd) +{ + const char *exec_path = git_exec_path(); + + return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) || + schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) || + schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd); +} + +static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + if (run_maintenance) + return schtasks_schedule_tasks(cmd); + else + return schtasks_remove_tasks(cmd); +} + #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE" #define END_LINE "# END GIT MAINTENANCE SCHEDULE" -static int update_background_schedule(int run_maintenance) +static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd) { int result = 0; int in_old_region = 0; struct child_process crontab_list = CHILD_PROCESS_INIT; struct child_process crontab_edit = CHILD_PROCESS_INIT; FILE *cron_list, *cron_in; - const char *crontab_name; struct strbuf line = STRBUF_INIT; - struct lock_file lk; - char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); - if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) - return error(_("another process is scheduling background maintenance")); - - crontab_name = getenv("GIT_TEST_CRONTAB"); - if (!crontab_name) - crontab_name = "crontab"; - - strvec_split(&crontab_list.args, crontab_name); + strvec_split(&crontab_list.args, cmd); strvec_push(&crontab_list.args, "-l"); crontab_list.in = -1; - crontab_list.out = dup(lk.tempfile->fd); + crontab_list.out = dup(fd); crontab_list.git_cmd = 0; - if (start_command(&crontab_list)) { - result = error(_("failed to run 'crontab -l'; your system might not support 'cron'")); - goto cleanup; - } + if (start_command(&crontab_list)) + return error(_("failed to run 'crontab -l'; your system might not support 'cron'")); /* Ignore exit code, as an empty crontab will return error. */ finish_command(&crontab_list); @@ -1541,17 +1863,15 @@ static int update_background_schedule(int run_maintenance) * Read from the .lock file, filtering out the old * schedule while appending the new schedule. */ - cron_list = fdopen(lk.tempfile->fd, "r"); + cron_list = fdopen(fd, "r"); rewind(cron_list); - strvec_split(&crontab_edit.args, crontab_name); + strvec_split(&crontab_edit.args, cmd); crontab_edit.in = -1; crontab_edit.git_cmd = 0; - if (start_command(&crontab_edit)) { - result = error(_("failed to run 'crontab'; your system might not support 'cron'")); - goto cleanup; - } + if (start_command(&crontab_edit)) + return error(_("failed to run 'crontab'; your system might not support 'cron'")); cron_in = fdopen(crontab_edit.in, "w"); if (!cron_in) { @@ -1562,11 +1882,10 @@ static int update_background_schedule(int run_maintenance) while (!strbuf_getline_lf(&line, cron_list)) { if (!in_old_region && !strcmp(line.buf, BEGIN_LINE)) in_old_region = 1; - if (in_old_region) - continue; - fprintf(cron_in, "%s\n", line.buf); - if (in_old_region && !strcmp(line.buf, END_LINE)) + else if (in_old_region && !strcmp(line.buf, END_LINE)) in_old_region = 0; + else if (!in_old_region) + fprintf(cron_in, "%s\n", line.buf); } if (run_maintenance) { @@ -1596,14 +1915,54 @@ static int update_background_schedule(int run_maintenance) close(crontab_edit.in); done_editing: - if (finish_command(&crontab_edit)) { + if (finish_command(&crontab_edit)) result = error(_("'crontab' died")); - goto cleanup; + else + fclose(cron_list); + return result; +} + +#if defined(__APPLE__) +static const char platform_scheduler[] = "launchctl"; +#elif defined(GIT_WINDOWS_NATIVE) +static const char platform_scheduler[] = "schtasks"; +#else +static const char platform_scheduler[] = "crontab"; +#endif + +static int update_background_schedule(int enable) +{ + int result; + const char *scheduler = platform_scheduler; + const char *cmd = scheduler; + char *testing; + struct lock_file lk; + char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); + + testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER")); + if (testing) { + char *sep = strchr(testing, ':'); + if (!sep) + die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing); + *sep = '\0'; + scheduler = testing; + cmd = sep + 1; } - fclose(cron_list); -cleanup: + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) + return error(_("another process is scheduling background maintenance")); + + if (!strcmp(scheduler, "launchctl")) + result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else if (!strcmp(scheduler, "schtasks")) + result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else if (!strcmp(scheduler, "crontab")) + result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else + die("unknown background scheduler: %s", scheduler); + rollback_lock_file(&lk); + free(testing); return result; } diff --git a/builtin/grep.c b/builtin/grep.c index e58e57504c..ca259af441 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -950,7 +950,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; - init_grep_defaults(the_repository); git_config(grep_cmd_config, NULL); grep_init(&opt, the_repository, prefix); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 0d03cb442d..557bd2f348 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1597,7 +1597,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p, /* The address of the 4-byte offset table */ idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset)) - + p->num_objects /* CRC32 table */ + + (size_t)p->num_objects /* CRC32 table */ ); /* The address of the 8-byte offset table */ @@ -1641,7 +1641,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) /* * Get rid of the idx file as we do not need it anymore. * NEEDSWORK: extract this bit from free_pack_by_name() in - * sha1-file.c, perhaps? It shouldn't matter very much as we + * object-file.c, perhaps? It shouldn't matter very much as we * know we haven't installed this pack (hence we never have * read anything from it). */ diff --git a/builtin/init-db.c b/builtin/init-db.c index 01bc648d41..dcc45bef51 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -202,7 +202,8 @@ void initialize_repository_version(int hash_algo, int reinit) static int create_default_files(const char *template_path, const char *original_git_dir, const char *initial_branch, - const struct repository_format *fmt) + const struct repository_format *fmt, + int quiet) { struct stat st1; struct strbuf buf = STRBUF_INIT; @@ -267,7 +268,7 @@ static int create_default_files(const char *template_path, char *ref; if (!initial_branch) - initial_branch = git_default_branch_name(); + initial_branch = git_default_branch_name(quiet); ref = xstrfmt("refs/heads/%s", initial_branch); if (check_refname_format(ref, 0) < 0) @@ -438,7 +439,8 @@ int init_db(const char *git_dir, const char *real_git_dir, validate_hash_algorithm(&repo_fmt, hash); reinit = create_default_files(template_dir, original_git_dir, - initial_branch, &repo_fmt); + initial_branch, &repo_fmt, + flags & INIT_DB_QUIET); if (reinit && initial_branch) warning(_("re-init: ignored --initial-branch=%s"), initial_branch); diff --git a/builtin/log.c b/builtin/log.c index 08204e3196..fd282def43 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -133,7 +133,6 @@ static int log_line_range_callback(const struct option *option, const char *arg, static void init_log_defaults(void) { - init_grep_defaults(the_repository); init_diff_ui_defaults(); decoration_style = auto_decoration_style(); @@ -178,7 +177,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), - OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_BOOL(0, "use-mailmap", &mailmap, N_("use mail map file")), OPT_ALIAS(0, "mailmap", "use-mailmap"), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match <pattern>")), @@ -187,7 +186,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback), OPT_CALLBACK('L', NULL, &line_cb, "range:file", - N_("Trace the evolution of line range <start>,<end> or function :<funcname> in <file>"), + N_("trace the evolution of line range <start>,<end> or function :<funcname> in <file>"), log_line_range_callback), OPT_END() }; @@ -231,7 +230,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (mailmap) { rev->mailmap = xcalloc(1, sizeof(struct string_list)); - read_mailmap(rev->mailmap, NULL); + read_mailmap(rev->mailmap); } if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { @@ -1758,13 +1757,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, N_("max length of output filename")), OPT_CALLBACK_F(0, "rfc", &rev, NULL, - N_("Use [RFC PATCH] instead of [PATCH]"), + N_("use [RFC PATCH] instead of [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback), OPT_STRING(0, "cover-from-description", &cover_from_description_arg, N_("cover-from-description-mode"), N_("generate parts of a cover letter based on a branch's description")), OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"), - N_("Use [<prefix>] instead of [PATCH]"), + N_("use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback), OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in <dir>"), diff --git a/builtin/mktag.c b/builtin/mktag.c index 4982d3a93e..41a399a69e 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,179 +1,110 @@ #include "builtin.h" +#include "parse-options.h" #include "tag.h" #include "replace-object.h" #include "object-store.h" +#include "fsck.h" +#include "config.h" -/* - * A signature file has a very simple fixed format: four lines - * of "object <sha1>" + "type <typename>" + "tag <tagname>" + - * "tagger <committer>", followed by a blank line, a free-form tag - * message and a signature block that git itself doesn't care about, - * but that can be verified with gpg or similar. - * - * The first four lines are guaranteed to be at least 83 bytes: - * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the - * shortest possible type-line, "tag .\n" at 6 bytes is the shortest - * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is - * the shortest possible tagger-line. - */ - -/* - * We refuse to tag something we can't verify. Just because. - */ -static int verify_object(const struct object_id *oid, const char *expected_type) +static char const * const builtin_mktag_usage[] = { + N_("git mktag"), + NULL +}; +static int option_strict = 1; + +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; + +static int mktag_config(const char *var, const char *value, void *cb) { - int ret = -1; - enum object_type type; - unsigned long size; - void *buffer = read_object_file(oid, &type, &size); - const struct object_id *repl = lookup_replace_object(the_repository, oid); - - if (buffer) { - if (type == type_from_string(expected_type)) { - ret = check_object_signature(the_repository, repl, - buffer, size, - expected_type); + return fsck_config_internal(var, value, cb, &fsck_options); +} + +static int mktag_fsck_error_func(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) +{ + switch (msg_type) { + case FSCK_WARN: + if (!option_strict) { + fprintf_ln(stderr, _("warning: tag input does not pass fsck: %s"), message); + return 0; + } - free(buffer); + /* fallthrough */ + case FSCK_ERROR: + /* + * We treat both warnings and errors as errors, things + * like missing "tagger" lines are "only" warnings + * under fsck, we've always considered them an error. + */ + fprintf_ln(stderr, _("error: tag input does not pass fsck: %s"), message); + return 1; + default: + BUG(_("%d (FSCK_IGNORE?) should never trigger this callback"), + msg_type); } - return ret; } -static int verify_tag(char *buffer, unsigned long size) +static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) { - int typelen; - char type[20]; - struct object_id oid; - const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb, *p; - size_t len; - - if (size < 84) - return error("wanna fool me ? you obviously got the size wrong !"); - - buffer[size] = 0; - - /* Verify object line */ - object = buffer; - if (memcmp(object, "object ", 7)) - return error("char%d: does not start with \"object \"", 0); - - if (parse_oid_hex(object + 7, &oid, &p)) - return error("char%d: could not get SHA1 hash", 7); - - /* Verify type line */ - type_line = p + 1; - if (memcmp(type_line - 1, "\ntype ", 6)) - return error("char%d: could not find \"\\ntype \"", 47); - - /* Verify tag-line */ - tag_line = strchr(type_line, '\n'); - if (!tag_line) - return error("char%"PRIuMAX": could not find next \"\\n\"", - (uintmax_t) (type_line - buffer)); - tag_line++; - if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char%"PRIuMAX": no \"tag \" found", - (uintmax_t) (tag_line - buffer)); - - /* Get the actual type */ - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= sizeof(type)) - return error("char%"PRIuMAX": type too long", - (uintmax_t) (type_line+5 - buffer)); - - memcpy(type, type_line+5, typelen); - type[typelen] = 0; - - /* Verify that the object matches */ - if (verify_object(&oid, type)) - return error("char%d: could not verify object %s", 7, oid_to_hex(&oid)); - - /* Verify the tag-name: we don't allow control characters or spaces in it */ - tag_line += 4; - for (;;) { - unsigned char c = *tag_line++; - if (c == '\n') - break; - if (c > ' ') - continue; - return error("char%"PRIuMAX": could not verify tag name", - (uintmax_t) (tag_line - buffer)); - } + int ret; + enum object_type type; + unsigned long size; + void *buffer; + const struct object_id *repl; + + buffer = read_object_file(tagged_oid, &type, &size); + if (!buffer) + die(_("could not read tagged object '%s'"), + oid_to_hex(tagged_oid)); + if (type != *tagged_type) + die(_("object '%s' tagged as '%s', but is a '%s' type"), + oid_to_hex(tagged_oid), + type_name(*tagged_type), type_name(type)); + + repl = lookup_replace_object(the_repository, tagged_oid); + ret = check_object_signature(the_repository, repl, + buffer, size, type_name(*tagged_type)); + free(buffer); - /* Verify the tagger line */ - tagger_line = tag_line; - - if (memcmp(tagger_line, "tagger ", 7)) - return error("char%"PRIuMAX": could not find \"tagger \"", - (uintmax_t) (tagger_line - buffer)); - - /* - * Check for correct form for name and email - * i.e. " <" followed by "> " on _this_ line - * No angle brackets within the name or email address fields. - * No spaces within the email address field. - */ - tagger_line += 7; - if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || - strpbrk(tagger_line, "<>\n") != lb+1 || - strpbrk(lb+2, "><\n ") != rb) - return error("char%"PRIuMAX": malformed tagger field", - (uintmax_t) (tagger_line - buffer)); - - /* Check for author name, at least one character, space is acceptable */ - if (lb == tagger_line) - return error("char%"PRIuMAX": missing tagger name", - (uintmax_t) (tagger_line - buffer)); - - /* timestamp, 1 or more digits followed by space */ - tagger_line = rb + 2; - if (!(len = strspn(tagger_line, "0123456789"))) - return error("char%"PRIuMAX": missing tag timestamp", - (uintmax_t) (tagger_line - buffer)); - tagger_line += len; - if (*tagger_line != ' ') - return error("char%"PRIuMAX": malformed tag timestamp", - (uintmax_t) (tagger_line - buffer)); - tagger_line++; - - /* timezone, 5 digits [+-]hhmm, max. 1400 */ - if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && - strspn(tagger_line+1, "0123456789") == 4 && - tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char%"PRIuMAX": malformed tag timezone", - (uintmax_t) (tagger_line - buffer)); - tagger_line += 6; - - /* Verify the blank line separating the header from the body */ - if (*tagger_line != '\n') - return error("char%"PRIuMAX": trailing garbage in tag header", - (uintmax_t) (tagger_line - buffer)); - - /* The actual stuff afterwards we don't care about.. */ - return 0; + return ret; } int cmd_mktag(int argc, const char **argv, const char *prefix) { + static struct option builtin_mktag_options[] = { + OPT_BOOL(0, "strict", &option_strict, + N_("enable more strict checking")), + OPT_END(), + }; struct strbuf buf = STRBUF_INIT; + struct object_id tagged_oid; + int tagged_type; struct object_id result; - if (argc != 1) - usage("git mktag"); + argc = parse_options(argc, argv, NULL, + builtin_mktag_options, + builtin_mktag_usage, 0); - if (strbuf_read(&buf, 0, 4096) < 0) { - die_errno("could not read from stdin"); - } + if (strbuf_read(&buf, 0, 0) < 0) + die_errno(_("could not read from stdin")); + + fsck_options.error_func = mktag_fsck_error_func; + fsck_set_msg_type(&fsck_options, "extraheaderentry", "warn"); + /* config might set fsck.extraHeaderEntry=* again */ + git_config(mktag_config, NULL); + if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options, + &tagged_oid, &tagged_type)) + die(_("tag on stdin did not pass our strict fsck check")); - /* Verify it for some basic sanity: it needs to start with - "object <sha1>\ntype\ntagger " */ - if (verify_tag(buf.buf, buf.len) < 0) - die("invalid tag signature file"); + if (verify_object_in_tag(&tagged_oid, &tagged_type)) + die(_("tag on stdin did not refer to a valid object")); if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0) - die("unable to write tag file"); + die(_("unable to write tag file")); strbuf_release(&buf); - printf("%s\n", oid_to_hex(&result)); + puts(oid_to_hex(&result)); return 0; } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 725dd04519..3fe71a8c01 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -7,7 +7,7 @@ #include "refs.h" #include "parse-options.h" #include "prio-queue.h" -#include "sha1-lookup.h" +#include "hash-lookup.h" #include "commit-slab.h" /* @@ -408,7 +408,7 @@ static const char *get_exact_ref_match(const struct object *o) tip_table.sorted = 1; } - found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr, + found = hash_pos(o->oid.hash, tip_table.table, tip_table.nr, nth_tip_table_ent); if (0 <= found) return tip_table.table[found].refname; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5617c01b5a..2a00358f34 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1104,7 +1104,6 @@ static void write_pack_file(void) stop_progress(&progress_state); bitmap_writer_show_progress(progress); - bitmap_writer_reuse_bitmaps(&to_pack); bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1); bitmap_writer_build(&to_pack); bitmap_writer_finish(written_list, nr_written, diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 178e3409b7..6e115a811a 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -236,7 +236,7 @@ static struct pack_list * pack_list_difference(const struct pack_list *A, static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) { - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; + size_t p1_off = 0, p2_off = 0, p1_step, p2_step; const unsigned char *p1_base, *p2_base; struct llist_item *p1_hint = NULL, *p2_hint = NULL; const unsigned int hashsz = the_hash_algo->rawsz; @@ -280,7 +280,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; + size_t p1_off = 0, p2_off = 0, p1_step, p2_step; const unsigned char *p1_base, *p2_base; const unsigned int hashsz = the_hash_algo->rawsz; @@ -473,6 +473,12 @@ static void cmp_local_packs(void) { struct pack_list *subset, *pl = local_packs; + /* only one packfile */ + if (!pl->next) { + llist_init(&pl->unique_objects); + return; + } + while ((subset = pl)) { while ((subset = subset->next)) cmp_two_packs(pl, subset); @@ -499,7 +505,7 @@ static void scan_alt_odb_packs(void) static struct pack_list * add_pack(struct packed_git *p) { struct pack_list l; - unsigned long off = 0, step; + size_t off = 0, step; const unsigned char *base; if (!p->pack_local && !(alt_odb || verbose)) @@ -554,6 +560,7 @@ static void load_all(void) int cmd_pack_redundant(int argc, const char **argv, const char *prefix) { int i; + int i_still_use_this = 0; struct pack_list *min = NULL, *red, *pl; struct llist *ignore; struct object_id *oid; @@ -580,12 +587,24 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) alt_odb = 1; continue; } + if (!strcmp(arg, "--i-still-use-this")) { + i_still_use_this = 1; + continue; + } if (*arg == '-') usage(pack_redundant_usage); else break; } + if (!i_still_use_this) { + fputs(_("'git pack-redundant' is nominated for removal.\n" + "If you still use this command, please add an extra\n" + "option, '--i-still-use-this', on the command line\n" + "and let us know you still use it by sending an e-mail\n" + "to <git@vger.kernel.org>. Thanks.\n"), stderr); + } + if (load_all_packs) load_all(); else diff --git a/builtin/pull.c b/builtin/pull.c index 17aa63cd35..e8927fc2ff 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -324,7 +324,7 @@ static const char *config_get_ff(void) * looks for the value of "pull.rebase". If both configuration keys do not * exist, returns REBASE_FALSE. */ -static enum rebase_type config_get_rebase(void) +static enum rebase_type config_get_rebase(int *rebase_unspecified) { struct branch *curr_branch = branch_get("HEAD"); const char *value; @@ -344,20 +344,7 @@ static enum rebase_type config_get_rebase(void) if (!git_config_get_value("pull.rebase", &value)) return parse_config_rebase("pull.rebase", value, 1); - if (opt_verbosity >= 0 && !opt_ff) { - warning(_("Pulling without specifying how to reconcile divergent branches is\n" - "discouraged. You can squelch this message by running one of the following\n" - "commands sometime before your next pull:\n" - "\n" - " git config pull.rebase false # merge (the default strategy)\n" - " git config pull.rebase true # rebase\n" - " git config pull.ff only # fast-forward only\n" - "\n" - "You can replace \"git config\" with \"git config --global\" to set a default\n" - "preference for all repositories. You can also pass --rebase, --no-rebase,\n" - "or --ff-only on the command line to override the configured default per\n" - "invocation.\n")); - } + *rebase_unspecified = 1; return REBASE_FALSE; } @@ -852,21 +839,42 @@ static int get_octopus_merge_base(struct object_id *merge_base, /** * Given the current HEAD oid, the merge head returned from git-fetch and the - * fork point calculated by get_rebase_fork_point(), runs git-rebase with the - * appropriate arguments and returns its exit status. + * fork point calculated by get_rebase_fork_point(), compute the <newbase> and + * <upstream> arguments to use for the upcoming git-rebase invocation. */ -static int run_rebase(const struct object_id *curr_head, +static int get_rebase_newbase_and_upstream(struct object_id *newbase, + struct object_id *upstream, + const struct object_id *curr_head, const struct object_id *merge_head, const struct object_id *fork_point) { - int ret; struct object_id oct_merge_base; - struct strvec args = STRVEC_INIT; if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point)) if (!is_null_oid(fork_point) && oideq(&oct_merge_base, fork_point)) fork_point = NULL; + if (fork_point && !is_null_oid(fork_point)) + oidcpy(upstream, fork_point); + else + oidcpy(upstream, merge_head); + + oidcpy(newbase, merge_head); + + return 0; +} + +/** + * Given the <newbase> and <upstream> calculated by + * get_rebase_newbase_and_upstream(), runs git-rebase with the + * appropriate arguments and returns its exit status. + */ +static int run_rebase(const struct object_id *newbase, + const struct object_id *upstream) +{ + int ret; + struct strvec args = STRVEC_INIT; + strvec_push(&args, "rebase"); /* Shared options */ @@ -894,18 +902,45 @@ static int run_rebase(const struct object_id *curr_head, warning(_("ignoring --verify-signatures for rebase")); strvec_push(&args, "--onto"); - strvec_push(&args, oid_to_hex(merge_head)); + strvec_push(&args, oid_to_hex(newbase)); - if (fork_point && !is_null_oid(fork_point)) - strvec_push(&args, oid_to_hex(fork_point)); - else - strvec_push(&args, oid_to_hex(merge_head)); + strvec_push(&args, oid_to_hex(upstream)); ret = run_command_v_opt(args.v, RUN_GIT_CMD); strvec_clear(&args); return ret; } +static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head) +{ + int ret; + struct commit_list *list = NULL; + struct commit *merge_head, *head; + + head = lookup_commit_reference(the_repository, orig_head); + commit_list_insert(head, &list); + merge_head = lookup_commit_reference(the_repository, orig_merge_head); + ret = repo_is_descendant_of(the_repository, merge_head, list); + free_commit_list(list); + return ret; +} + +static void show_advice_pull_non_ff(void) +{ + advise(_("Pulling without specifying how to reconcile divergent branches is\n" + "discouraged. You can squelch this message by running one of the following\n" + "commands sometime before your next pull:\n" + "\n" + " git config pull.rebase false # merge (the default strategy)\n" + " git config pull.rebase true # rebase\n" + " git config pull.ff only # fast-forward only\n" + "\n" + "You can replace \"git config\" with \"git config --global\" to set a default\n" + "preference for all repositories. You can also pass --rebase, --no-rebase,\n" + "or --ff-only on the command line to override the configured default per\n" + "invocation.\n")); +} + int cmd_pull(int argc, const char **argv, const char *prefix) { const char *repo, **refspecs; @@ -913,6 +948,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) struct object_id orig_head, curr_head; struct object_id rebase_fork_point; int autostash; + int rebase_unspecified = 0; + int can_ff; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); @@ -934,7 +971,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) opt_ff = xstrdup_or_null(config_get_ff()); if (opt_rebase < 0) - opt_rebase = config_get_rebase(); + opt_rebase = config_get_rebase(&rebase_unspecified); if (read_cache_unmerged()) die_resolve_conflict("pull"); @@ -1008,33 +1045,36 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase && merge_heads.nr > 1) die(_("Cannot rebase onto multiple branches.")); + can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]); + + if (rebase_unspecified && !opt_ff && !can_ff) { + if (opt_verbosity >= 0) + show_advice_pull_non_ff(); + } + if (opt_rebase) { int ret = 0; int ran_ff = 0; + + struct object_id newbase; + struct object_id upstream; + get_rebase_newbase_and_upstream(&newbase, &upstream, &curr_head, + merge_heads.oid, &rebase_fork_point); + if ((recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) && - submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head)) + submodule_touches_in_range(the_repository, &upstream, &curr_head)) die(_("cannot rebase with locally recorded submodule modifications")); if (!autostash) { - struct commit_list *list = NULL; - struct commit *merge_head, *head; - - head = lookup_commit_reference(the_repository, - &orig_head); - commit_list_insert(head, &list); - merge_head = lookup_commit_reference(the_repository, - &merge_heads.oid[0]); - if (repo_is_descendant_of(the_repository, - merge_head, list)) { + if (can_ff) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; ran_ff = 1; ret = run_merge(); } - free_commit_list(list); } if (!ran_ff) - ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + ret = run_rebase(&newbase, &upstream); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) diff --git a/builtin/rebase.c b/builtin/rebase.c index 19c7b377aa..840dbd7eb7 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1917,7 +1917,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die_if_checked_out(buf.buf, 1); options.head_name = xstrdup(buf.buf); /* If not is it a valid ref (branch or commit)? */ - } else if (!get_oid(branch_name, &options.orig_head)) + } else if (!get_oid(branch_name, &options.orig_head) && + lookup_commit_reference(the_repository, + &options.orig_head)) options.head_name = NULL; else die(_("fatal: no such branch/commit '%s'"), diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index bb9909c52e..d49d050e6e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -54,6 +54,7 @@ static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int advertise_atomic_push = 1; static int advertise_push_options; +static int advertise_sid; static int unpack_limit = 100; static off_t max_input_size; static int report_status; @@ -248,6 +249,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "transfer.advertisesid") == 0) { + advertise_sid = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -268,6 +274,8 @@ static void show_ref(const char *path, const struct object_id *oid) strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); if (advertise_push_options) strbuf_addstr(&cap, " push-options"); + if (advertise_sid) + strbuf_addf(&cap, " session-id=%s", trace2_session_id()); strbuf_addf(&cap, " object-format=%s", the_hash_algo->name); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); packet_write_fmt(1, "%s %s%c%s\n", @@ -977,15 +985,25 @@ static int read_proc_receive_report(struct packet_reader *reader, int new_report = 0; int code = 0; int once = 0; + int response = 0; for (;;) { struct object_id old_oid, new_oid; const char *head; const char *refname; char *p; - - if (packet_reader_read(reader) != PACKET_READ_NORMAL) + enum packet_read_status status; + + status = packet_reader_read(reader); + if (status != PACKET_READ_NORMAL) { + /* Check whether proc-receive exited abnormally */ + if (status == PACKET_READ_EOF && !response) { + strbuf_addstr(errmsg, "proc-receive exited abnormally"); + return -1; + } break; + } + response++; head = reader->line; p = strchr(head, ' '); @@ -1145,31 +1163,49 @@ static int run_proc_receive_hook(struct command *commands, if (use_push_options) strbuf_addstr(&cap, " push-options"); if (cap.len) { - packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1); + code = packet_write_fmt_gently(proc.in, "version=1%c%s\n", '\0', cap.buf + 1); strbuf_release(&cap); } else { - packet_write_fmt(proc.in, "version=1\n"); + code = packet_write_fmt_gently(proc.in, "version=1\n"); } - packet_flush(proc.in); + if (!code) + code = packet_flush_gently(proc.in); - for (;;) { - int linelen; + if (!code) + for (;;) { + int linelen; + enum packet_read_status status; - if (packet_reader_read(&reader) != PACKET_READ_NORMAL) - break; + status = packet_reader_read(&reader); + if (status != PACKET_READ_NORMAL) { + /* Check whether proc-receive exited abnormally */ + if (status == PACKET_READ_EOF) + code = -1; + break; + } - if (reader.pktlen > 8 && starts_with(reader.line, "version=")) { - version = atoi(reader.line + 8); - linelen = strlen(reader.line); - if (linelen < reader.pktlen) { - const char *feature_list = reader.line + linelen + 1; - if (parse_feature_request(feature_list, "push-options")) - hook_use_push_options = 1; + if (reader.pktlen > 8 && starts_with(reader.line, "version=")) { + version = atoi(reader.line + 8); + linelen = strlen(reader.line); + if (linelen < reader.pktlen) { + const char *feature_list = reader.line + linelen + 1; + if (parse_feature_request(feature_list, "push-options")) + hook_use_push_options = 1; + } } } + + if (code) { + strbuf_addstr(&errmsg, "fail to negotiate version with proc-receive hook"); + goto cleanup; } - if (version != 1) { + switch (version) { + case 0: + /* fallthrough */ + case 1: + break; + default: strbuf_addf(&errmsg, "proc-receive version '%d' is not supported", version); code = -1; @@ -1180,20 +1216,36 @@ static int run_proc_receive_hook(struct command *commands, for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string) continue; - packet_write_fmt(proc.in, "%s %s %s", - oid_to_hex(&cmd->old_oid), - oid_to_hex(&cmd->new_oid), - cmd->ref_name); + code = packet_write_fmt_gently(proc.in, "%s %s %s", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + if (code) + break; + } + if (!code) + code = packet_flush_gently(proc.in); + if (code) { + strbuf_addstr(&errmsg, "fail to write commands to proc-receive hook"); + goto cleanup; } - packet_flush(proc.in); /* Send push options */ if (hook_use_push_options) { struct string_list_item *item; - for_each_string_list_item(item, push_options) - packet_write_fmt(proc.in, "%s", item->string); - packet_flush(proc.in); + for_each_string_list_item(item, push_options) { + code = packet_write_fmt_gently(proc.in, "%s", item->string); + if (code) + break; + } + if (!code) + code = packet_flush_gently(proc.in); + if (code) { + strbuf_addstr(&errmsg, + "fail to write push-options to proc-receive hook"); + goto cleanup; + } } /* Read result from proc-receive */ @@ -2031,6 +2083,7 @@ static struct command *read_head_info(struct packet_reader *reader, if (linelen < reader->pktlen) { const char *feature_list = reader->line + linelen + 1; const char *hash = NULL; + const char *client_sid; int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; @@ -2053,6 +2106,12 @@ static struct command *read_head_info(struct packet_reader *reader, } if (xstrncmpz(the_hash_algo->name, hash, len)) die("error: unsupported object format '%s'", hash); + client_sid = parse_feature_value(feature_list, "session-id", &len, NULL); + if (client_sid) { + char *sid = xstrndup(client_sid, len); + trace2_data_string("transfer", NULL, "client-sid", client_sid); + free(sid); + } } if (!strcmp(reader->line, "push-cert")) { diff --git a/builtin/remote.c b/builtin/remote.c index c1b211b272..d11a5589e4 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -712,7 +712,7 @@ static int mv(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); for (i = 0; i < oldremote->fetch.raw_nr; i++) { char *ptr; @@ -1491,7 +1491,8 @@ static int update(int argc, const char **argv) static int remove_all_fetch_refspecs(const char *key) { - return git_config_set_multivar_gently(key, NULL, NULL, 1); + return git_config_set_multivar_gently(key, NULL, NULL, + CONFIG_FLAGS_MULTI_REPLACE); } static void add_branches(struct remote *remote, const char **branches, @@ -1686,7 +1687,8 @@ static int set_url(int argc, const char **argv) if (!delete_mode) git_config_set_multivar(name_buf.buf, newurl, oldurl, 0); else - git_config_set_multivar(name_buf.buf, NULL, oldurl, 1); + git_config_set_multivar(name_buf.buf, NULL, oldurl, + CONFIG_FLAGS_MULTI_REPLACE); out: strbuf_release(&name_buf); return 0; diff --git a/builtin/repack.c b/builtin/repack.c index 01e7767c79..2158b48f4c 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -14,6 +14,7 @@ #include "object-store.h" #include "promisor-remote.h" #include "shallow.h" +#include "pack.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; @@ -202,6 +203,37 @@ static int write_oid(const struct object_id *oid, struct packed_git *pack, return 0; } +static struct { + const char *name; + unsigned optional:1; +} exts[] = { + {".pack"}, + {".idx"}, + {".bitmap", 1}, + {".promisor", 1}, +}; + +static unsigned populate_pack_exts(char *name) +{ + struct stat statbuf; + struct strbuf path = STRBUF_INIT; + unsigned ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(exts); i++) { + strbuf_reset(&path); + strbuf_addf(&path, "%s-%s%s", packtmp, name, exts[i].name); + + if (stat(path.buf, &statbuf)) + continue; + + ret |= (1 << i); + } + + strbuf_release(&path); + return ret; +} + static void repack_promisor_objects(const struct pack_objects_args *args, struct string_list *names) { @@ -230,11 +262,12 @@ static void repack_promisor_objects(const struct pack_objects_args *args, out = xfdopen(cmd.out, "r"); while (strbuf_getline_lf(&line, out) != EOF) { + struct string_list_item *item; char *promisor_name; - int fd; + if (line.len != the_hash_algo->hexsz) die(_("repack: Expecting full hex object ID lines only from pack-objects.")); - string_list_append(names, line.buf); + item = string_list_append(names, line.buf); /* * pack-objects creates the .pack and .idx files, but not the @@ -249,10 +282,10 @@ static void repack_promisor_objects(const struct pack_objects_args *args, */ promisor_name = mkpathdup("%s-%s.promisor", packtmp, line.buf); - fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600); - if (fd < 0) - die_errno(_("unable to create '%s'"), promisor_name); - close(fd); + write_promisor_file(promisor_name, NULL, 0); + + item->util = (void *)(uintptr_t)populate_pack_exts(item->string); + free(promisor_name); } fclose(out); @@ -265,22 +298,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args, int cmd_repack(int argc, const char **argv, const char *prefix) { - struct { - const char *name; - unsigned optional:1; - } exts[] = { - {".pack"}, - {".idx"}, - {".bitmap", 1}, - {".promisor", 1}, - }; struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; struct string_list names = STRING_LIST_INIT_DUP; struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_packs = STRING_LIST_INIT_DUP; struct strbuf line = STRBUF_INIT; - int i, ext, ret, failed; + int i, ext, ret; FILE *out; /* variables to be filled by option parsing */ @@ -429,113 +453,42 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!names.nr && !po_args.quiet) printf_ln(_("Nothing new to pack.")); + for_each_string_list_item(item, &names) { + item->util = (void *)(uintptr_t)populate_pack_exts(item->string); + } + close_object_store(the_repository->objects); /* * Ok we have prepared all new packfiles. - * First see if there are packs of the same name and if so - * if we can move them out of the way (this can happen if we - * repacked immediately after packing fully. */ - failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { char *fname, *fname_old; - fname = mkpathdup("%s/pack-%s%s", packdir, - item->string, exts[ext].name); - if (!file_exists(fname)) { - free(fname); - continue; - } - - fname_old = mkpathdup("%s/old-%s%s", packdir, - item->string, exts[ext].name); - if (file_exists(fname_old)) - if (unlink(fname_old)) - failed = 1; - - if (!failed && rename(fname, fname_old)) { - free(fname); - free(fname_old); - failed = 1; - break; - } else { - string_list_append(&rollback, fname); - free(fname_old); - } - } - if (failed) - break; - } - if (failed) { - struct string_list rollback_failure = STRING_LIST_INIT_DUP; - for_each_string_list_item(item, &rollback) { - char *fname, *fname_old; - fname = mkpathdup("%s/%s", packdir, item->string); - fname_old = mkpathdup("%s/old-%s", packdir, item->string); - if (rename(fname_old, fname)) - string_list_append(&rollback_failure, fname); - free(fname); - free(fname_old); - } - - if (rollback_failure.nr) { - int i; - fprintf(stderr, - _("WARNING: Some packs in use have been renamed by\n" - "WARNING: prefixing old- to their name, in order to\n" - "WARNING: replace them with the new version of the\n" - "WARNING: file. But the operation failed, and the\n" - "WARNING: attempt to rename them back to their\n" - "WARNING: original names also failed.\n" - "WARNING: Please rename them in %s manually:\n"), packdir); - for (i = 0; i < rollback_failure.nr; i++) - fprintf(stderr, "WARNING: old-%s -> %s\n", - rollback_failure.items[i].string, - rollback_failure.items[i].string); - } - exit(1); - } - - /* Now the ones with the same name are out of the way... */ - for_each_string_list_item(item, &names) { - for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname, *fname_old; - struct stat statbuffer; - int exists = 0; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); fname_old = mkpathdup("%s-%s%s", packtmp, item->string, exts[ext].name); - if (!stat(fname_old, &statbuffer)) { - statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); - chmod(fname_old, statbuffer.st_mode); - exists = 1; - } - if (exists || !exts[ext].optional) { + + if (((uintptr_t)item->util) & (1 << ext)) { + struct stat statbuffer; + if (!stat(fname_old, &statbuffer)) { + statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + chmod(fname_old, statbuffer.st_mode); + } + if (rename(fname_old, fname)) die_errno(_("renaming '%s' failed"), fname_old); - } - free(fname); - free(fname_old); - } - } + } else if (!exts[ext].optional) + die(_("missing required file: %s"), fname_old); + else if (unlink(fname) < 0 && errno != ENOENT) + die_errno(_("could not unlink: %s"), fname); - /* Remove the "old-" files */ - for_each_string_list_item(item, &names) { - for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname; - fname = mkpathdup("%s/old-%s%s", - packdir, - item->string, - exts[ext].name); - if (remove_path(fname)) - warning(_("failed to remove '%s'"), fname); free(fname); + free(fname_old); } } - /* End of pack replacement. */ reprepare_packed_git(the_repository); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 69ba7326cf..85bad9052e 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -583,6 +583,75 @@ static void handle_ref_opt(const char *pattern, const char *prefix) clear_ref_exclusion(&ref_excludes); } +enum format_type { + /* We would like a relative path. */ + FORMAT_RELATIVE, + /* We would like a canonical absolute path. */ + FORMAT_CANONICAL, + /* We would like the default behavior. */ + FORMAT_DEFAULT, +}; + +enum default_type { + /* Our default is a relative path. */ + DEFAULT_RELATIVE, + /* Our default is a relative path if there's a shared root. */ + DEFAULT_RELATIVE_IF_SHARED, + /* Our default is a canonical absolute path. */ + DEFAULT_CANONICAL, + /* Our default is not to modify the item. */ + DEFAULT_UNMODIFIED, +}; + +static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def) +{ + char *cwd = NULL; + /* + * We don't ever produce a relative path if prefix is NULL, so set the + * prefix to the current directory so that we can produce a relative + * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then + * we want an absolute path unless the two share a common prefix, so don't + * set it in that case, since doing so causes a relative path to always + * be produced if possible. + */ + if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED)) + prefix = cwd = xgetcwd(); + if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) { + puts(path); + } else if (format == FORMAT_RELATIVE || + (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) { + /* + * In order for relative_path to work as expected, we need to + * make sure that both paths are absolute paths. If we don't, + * we can end up with an unexpected absolute path that the user + * didn't want. + */ + struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT; + if (!is_absolute_path(path)) { + strbuf_realpath_forgiving(&realbuf, path, 1); + path = realbuf.buf; + } + if (!is_absolute_path(prefix)) { + strbuf_realpath_forgiving(&prefixbuf, prefix, 1); + prefix = prefixbuf.buf; + } + puts(relative_path(path, prefix, &buf)); + strbuf_release(&buf); + strbuf_release(&realbuf); + strbuf_release(&prefixbuf); + } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) { + struct strbuf buf = STRBUF_INIT; + puts(relative_path(path, prefix, &buf)); + strbuf_release(&buf); + } else { + struct strbuf buf = STRBUF_INIT; + strbuf_realpath_forgiving(&buf, path, 1); + puts(buf.buf); + strbuf_release(&buf); + } + free(cwd); +} + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; @@ -596,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; const int hexsz = the_hash_algo->hexsz; int seen_end_of_options = 0; + enum format_type format = FORMAT_DEFAULT; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -668,8 +738,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!argv[i + 1]) die("--git-path requires an argument"); strbuf_reset(&buf); - puts(relative_path(git_path("%s", argv[i + 1]), - prefix, &buf)); + print_path(git_path("%s", argv[i + 1]), prefix, + format, + DEFAULT_RELATIVE_IF_SHARED); i++; continue; } @@ -687,6 +758,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) show(arg); continue; } + if (opt_with_value(arg, "--path-format", &arg)) { + if (!strcmp(arg, "absolute")) { + format = FORMAT_CANONICAL; + } else if (!strcmp(arg, "relative")) { + format = FORMAT_RELATIVE; + } else { + die("unknown argument to --path-format: %s", arg); + } + continue; + } if (!strcmp(arg, "--default")) { def = argv[++i]; if (!def) @@ -807,7 +888,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--show-toplevel")) { const char *work_tree = get_git_work_tree(); if (work_tree) - puts(work_tree); + print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED); else die("this operation must be run in a work tree"); continue; @@ -815,7 +896,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--show-superproject-working-tree")) { struct strbuf superproject = STRBUF_INIT; if (get_superproject_working_tree(&superproject)) - puts(superproject.buf); + print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED); strbuf_release(&superproject); continue; } @@ -850,16 +931,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); char *cwd; int len; + enum format_type wanted = format; if (arg[2] == 'g') { /* --git-dir */ if (gitdir) { - puts(gitdir); + print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED); continue; } if (!prefix) { - puts(".git"); + print_path(".git", prefix, format, DEFAULT_UNMODIFIED); continue; } } else { /* --absolute-git-dir */ + wanted = FORMAT_CANONICAL; if (!gitdir && !prefix) gitdir = ".git"; if (gitdir) { @@ -872,14 +955,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } cwd = xgetcwd(); len = strlen(cwd); - printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); + strbuf_reset(&buf); + strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : ""); free(cwd); + print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL); continue; } if (!strcmp(arg, "--git-common-dir")) { - strbuf_reset(&buf); - puts(relative_path(get_git_common_dir(), - prefix, &buf)); + print_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { @@ -909,8 +992,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (the_index.split_index) { const struct object_id *oid = &the_index.split_index->base_oid; const char *path = git_path("sharedindex.%s", oid_to_hex(oid)); - strbuf_reset(&buf); - puts(relative_path(path, prefix, &buf)); + print_path(path, prefix, format, DEFAULT_RELATIVE); } continue; } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index c52e4ccd19..3e7ab1ca82 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -61,8 +61,7 @@ static void insert_one_record(struct shortlog *log, if (log->summary) item->util = (void *)(UTIL_TO_INT(item) + 1); else { - const char *dot3 = log->common_repo_prefix; - char *buffer, *p; + char *buffer; struct strbuf subject = STRBUF_INIT; const char *eol; @@ -82,17 +81,6 @@ static void insert_one_record(struct shortlog *log, format_subject(&subject, oneline, " "); buffer = strbuf_detach(&subject, NULL); - if (dot3) { - int dot3len = strlen(dot3); - if (dot3len > 5) { - while ((p = strstr(buffer, dot3)) != NULL) { - int taillen = strlen(p) - dot3len; - memcpy(p, "/.../", 5); - memmove(p + 5, p + dot3len, taillen + 1); - } - } - } - if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); string_list_append(item->util, buffer); @@ -342,7 +330,7 @@ void shortlog_init(struct shortlog *log) { memset(log, 0, sizeof(*log)); - read_mailmap(&log->mailmap, &log->common_repo_prefix); + read_mailmap(&log->mailmap); log->list.strdup_strings = 1; log->wrap = DEFAULT_WRAPLEN; @@ -360,19 +348,19 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) const struct option options[] = { OPT_BIT('c', "committer", &log.groups, - N_("Group by committer rather than author"), + N_("group by committer rather than author"), SHORTLOG_GROUP_COMMITTER), OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, - N_("Suppress commit descriptions, only provides commit count")), + N_("suppress commit descriptions, only provides commit count")), OPT_BOOL('e', "email", &log.email, - N_("Show the email address of each author")), + N_("show the email address of each author")), OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"), - N_("Linewrap output"), PARSE_OPT_OPTARG, + N_("linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args), OPT_CALLBACK(0, "group", &log, N_("field"), - N_("Group by field"), parse_group_option), + N_("group by field"), parse_group_option), OPT_END(), }; diff --git a/builtin/stash.c b/builtin/stash.c index 24ddb1bffa..9bc85f91cd 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -325,35 +325,6 @@ static void add_diff_to_buf(struct diff_queue_struct *q, } } -static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) -{ - struct child_process cp = CHILD_PROCESS_INIT; - const char *c_tree_hex = oid_to_hex(c_tree); - - /* - * diff-index is very similar to diff-tree above, and should be - * converted together with update_index. - */ - cp.git_cmd = 1; - strvec_pushl(&cp.args, "diff-index", "--cached", "--name-only", - "--diff-filter=A", NULL); - strvec_push(&cp.args, c_tree_hex); - return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); -} - -static int update_index(struct strbuf *out) -{ - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * Update-index is very complicated and may need to have a public - * function exposed in order to remove this forking. - */ - cp.git_cmd = 1; - strvec_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); - return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); -} - static int restore_untracked(struct object_id *u_tree) { int res; @@ -385,6 +356,121 @@ static int restore_untracked(struct object_id *u_tree) return res; } +static void unstage_changes_unless_new(struct object_id *orig_tree) +{ + /* + * When we enter this function, there has been a clean merge of + * relevant trees, and the merge logic always stages whatever merges + * cleanly. We want to unstage those changes, unless it corresponds + * to a file that didn't exist as of orig_tree. + * + * However, if any SKIP_WORKTREE path is modified relative to + * orig_tree, then we want to clear the SKIP_WORKTREE bit and write + * it to the worktree before unstaging. + */ + + struct checkout state = CHECKOUT_INIT; + struct diff_options diff_opts; + struct lock_file lock = LOCK_INIT; + int i; + + /* If any entries have skip_worktree set, we'll have to check 'em out */ + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + state.istate = &the_index; + + /* + * Step 1: get a difference between orig_tree (which corresponding + * to the index before a merge was run) and the current index + * (reflecting the changes brought in by the merge). + */ + diff_setup(&diff_opts); + diff_opts.flags.recursive = 1; + diff_opts.detect_rename = 0; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_setup_done(&diff_opts); + + do_diff_cache(orig_tree, &diff_opts); + diffcore_std(&diff_opts); + + /* Iterate over the paths that changed due to the merge... */ + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p; + struct cache_entry *ce; + int pos; + + /* Look up the path's position in the current index. */ + p = diff_queued_diff.queue[i]; + pos = index_name_pos(&the_index, p->two->path, + strlen(p->two->path)); + + /* + * Step 2: Place changes in the working tree + * + * Stash is about restoring changes *to the working tree*. + * So if the merge successfully got a new version of some + * path, but left it out of the working tree, then clear the + * SKIP_WORKTREE bit and write it to the working tree. + */ + if (pos >= 0 && ce_skip_worktree(active_cache[pos])) { + struct stat st; + + ce = active_cache[pos]; + if (!lstat(ce->name, &st)) { + /* Conflicting path present; relocate it */ + struct strbuf new_path = STRBUF_INIT; + int fd; + + strbuf_addf(&new_path, + "%s.stash.XXXXXX", ce->name); + fd = xmkstemp(new_path.buf); + close(fd); + printf(_("WARNING: Untracked file in way of " + "tracked file! Renaming\n " + " %s -> %s\n" + " to make room.\n"), + ce->name, new_path.buf); + if (rename(ce->name, new_path.buf)) + die("Failed to move %s to %s\n", + ce->name, new_path.buf); + strbuf_release(&new_path); + } + checkout_entry(ce, &state, NULL, NULL); + ce->ce_flags &= ~CE_SKIP_WORKTREE; + } + + /* + * Step 3: "unstage" changes, as long as they are still tracked + */ + if (p->one->oid_valid) { + /* + * Path existed in orig_tree; restore index entry + * from that tree in order to "unstage" the changes. + */ + int option = ADD_CACHE_OK_TO_REPLACE; + if (pos < 0) + option = ADD_CACHE_OK_TO_ADD; + + ce = make_cache_entry(&the_index, + p->one->mode, + &p->one->oid, + p->one->path, + 0, 0); + add_index_entry(&the_index, ce, option); + } + } + diff_flush(&diff_opts); + + /* + * Step 4: write the new index to disk + */ + repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); + if (write_locked_index(&the_index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + die(_("Unable to write index.")); +} + static int do_apply_stash(const char *prefix, struct stash_info *info, int index, int quiet) { @@ -419,7 +505,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, ret = apply_cached(&out); strbuf_release(&out); if (ret) - return error(_("conflicts in index." + return error(_("conflicts in index. " "Try without --index.")); discard_cache(); @@ -467,26 +553,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (reset_tree(&index_tree, 0, 0)) return -1; } else { - struct strbuf out = STRBUF_INIT; - - if (get_newly_staged(&out, &c_tree)) { - strbuf_release(&out); - return -1; - } - - if (reset_tree(&c_tree, 0, 1)) { - strbuf_release(&out); - return -1; - } - - ret = update_index(&out); - strbuf_release(&out); - if (ret) - return -1; - - /* read back the result of update_index() back from the disk */ - discard_cache(); - read_cache(); + unstage_changes_unless_new(&c_tree); } if (!quiet) { diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c30896c897..c2bd882d17 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -562,9 +562,9 @@ static int module_foreach(int argc, const char **argv, const char *prefix) struct module_list list = MODULE_LIST_INIT; struct option module_foreach_options[] = { - OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")), + OPT__QUIET(&info.quiet, N_("suppress output of entering each submodule command")), OPT_BOOL(0, "recursive", &info.recursive, - N_("Recurse into nested submodules")), + N_("recurse into nested submodules")), OPT_END() }; @@ -706,7 +706,7 @@ static int module_init(int argc, const char **argv, const char *prefix) int quiet = 0; struct option module_init_options[] = { - OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), + OPT__QUIET(&quiet, N_("suppress output for initializing a submodule")), OPT_END() }; @@ -883,8 +883,8 @@ static int module_status(int argc, const char **argv, const char *prefix) int quiet = 0; struct option module_status_options[] = { - OPT__QUIET(&quiet, N_("Suppress submodule status output")), - OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED), + OPT__QUIET(&quiet, N_("suppress submodule status output")), + OPT_BIT(0, "cached", &info.flags, N_("use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED), OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE), OPT_END() }; @@ -1482,9 +1482,9 @@ static int module_sync(int argc, const char **argv, const char *prefix) int recursive = 0; struct option module_sync_options[] = { - OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")), + OPT__QUIET(&quiet, N_("suppress output of synchronizing submodule url")), OPT_BOOL(0, "recursive", &recursive, - N_("Recurse into nested submodules")), + N_("recurse into nested submodules")), OPT_END() }; @@ -1620,9 +1620,9 @@ static int module_deinit(int argc, const char **argv, const char *prefix) int all = 0; struct option module_deinit_options[] = { - OPT__QUIET(&quiet, N_("Suppress submodule status output")), - OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes"), 0), - OPT_BOOL(0, "all", &all, N_("Unregister all submodules")), + OPT__QUIET(&quiet, N_("suppress submodule status output")), + OPT__FORCE(&force, N_("remove submodule working trees even if they contain local changes"), 0), + OPT_BOOL(0, "all", &all, N_("unregister all submodules")), OPT_END() }; @@ -2337,7 +2337,7 @@ static int update_clone(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "dissociate", &suc.dissociate, N_("use --reference only while cloning")), OPT_STRING(0, "depth", &suc.depth, "<depth>", - N_("Create a shallow clone truncated to the " + N_("create a shallow clone truncated to the " "specified number of revisions")), OPT_INTEGER('j', "jobs", &suc.max_jobs, N_("parallel jobs")), @@ -2678,7 +2678,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix) char *config_name; struct option options[] = { - OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")), + OPT__QUIET(&quiet, N_("suppress output for setting url of a submodule")), OPT_END() }; const char *const usage[] = { diff --git a/builtin/tag.c b/builtin/tag.c index ecf011776d..24d35b746d 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -485,7 +485,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (!sorting) sorting = ref_default_sorting(); - ref_sorting_icase_all(sorting, icase); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; if (cmdmode == 'l') { int ret; diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 8a2df4459c..6029a80544 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -436,6 +436,8 @@ static void update_refs_stdin(void) switch (state) { case UPDATE_REFS_OPEN: case UPDATE_REFS_STARTED: + if (state == UPDATE_REFS_STARTED && cmd->state == UPDATE_REFS_STARTED) + die("cannot restart ongoing transaction"); /* Do not downgrade a transaction to a non-transaction. */ if (cmd->state >= state) state = cmd->state; @@ -446,7 +448,18 @@ static void update_refs_stdin(void) state = cmd->state; break; case UPDATE_REFS_CLOSED: - die("transaction is closed"); + if (cmd->state != UPDATE_REFS_STARTED) + die("transaction is closed"); + + /* + * Open a new transaction if we're currently closed and + * get a "start". + */ + state = cmd->state; + transaction = ref_transaction_begin(&err); + if (!transaction) + die("%s", err.buf); + break; } diff --git a/builtin/worktree.c b/builtin/worktree.c index ce56fdaaa9..71287b2da6 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -304,9 +304,9 @@ static void check_candidate_path(const char *path, } if (locked) - die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), cmd, path); + die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path, cmd); else - die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), cmd, path); + die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), path, cmd); } static int add_worktree(const char *path, const char *refname, @@ -1052,10 +1052,10 @@ static int repair(int ac, const char **av, const char *prefix) int rc = 0; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); - repair_worktrees(report_repair, &rc); p = ac > 0 ? av : self; for (; *p; p++) repair_worktree_at_path(*p, report_repair, &rc); + repair_worktrees(report_repair, &rc); return rc; } |