diff options
Diffstat (limited to 'builtin')
62 files changed, 2390 insertions, 1323 deletions
diff --git a/builtin/am.c b/builtin/am.c index 93bec62afa..39fea24833 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2301,7 +2301,7 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar return 0; } -static int git_am_config(const char *k, const char *v, void *cb) +static int git_am_config(const char *k, const char *v, void *cb UNUSED) { int status; diff --git a/builtin/archive.c b/builtin/archive.c index 7176b041b6..f094390ee0 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -75,7 +75,7 @@ static int run_remote_archiver(int argc, const char **argv, #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ PARSE_OPT_KEEP_ARGV0 | \ - PARSE_OPT_KEEP_UNKNOWN | \ + PARSE_OPT_KEEP_UNKNOWN_OPT | \ PARSE_OPT_NO_INTERNAL_HELP ) int cmd_archive(int argc, const char **argv, const char *prefix) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 8a052c7111..28ef7ec2a4 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -329,8 +329,9 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) return 0; } -static int inc_nr(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int inc_nr(const char *refname UNUSED, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cb_data) { unsigned int *nr = (unsigned int *)cb_data; (*nr)++; @@ -518,7 +519,7 @@ finish: } static int add_bisect_ref(const char *refname, const struct object_id *oid, - int flags, void *cb) + int flags UNUSED, void *cb) { struct add_bisect_ref_data *data = cb; @@ -764,11 +765,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, const char **a strbuf_read_file(&start_head, git_path_bisect_start(), 0); strbuf_trim(&start_head); if (!no_checkout) { - struct strvec argv = STRVEC_INIT; + const char *argv[] = { "checkout", start_head.buf, + "--", NULL }; - strvec_pushl(&argv, "checkout", start_head.buf, - "--", NULL); - if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { + if (run_command_v_opt(argv, RUN_GIT_CMD)) { res = error(_("checking out '%s' failed." " Try 'git bisect start " "<valid-branch>'."), @@ -1134,8 +1134,9 @@ static int bisect_visualize(struct bisect_terms *terms, const char **argv, int a return res; } -static int get_first_good(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int get_first_good(const char *refname UNUSED, + const struct object_id *oid, + int flag UNUSED, void *cb_data) { oidcpy(cb_data, oid); return 1; @@ -1324,7 +1325,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_bisect_helper_usage, - PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN_OPT); if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); diff --git a/builtin/blame.c b/builtin/blame.c index 02e39420b6..a9fe8cf7a6 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -920,6 +920,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) break; case PARSE_OPT_HELP: case PARSE_OPT_ERROR: + case PARSE_OPT_SUBCOMMAND: exit(129); case PARSE_OPT_COMPLETE: exit(0); diff --git a/builtin/branch.c b/builtin/branch.c index 5d00d0b8d3..407517ba68 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -204,7 +204,6 @@ static void delete_branch_config(const char *branchname) static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { - struct worktree **worktrees; struct commit *head_rev = NULL; struct object_id oid; char *name = NULL; @@ -242,8 +241,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } - worktrees = get_worktrees(); - for (i = 0; i < argc; i++, strbuf_reset(&bname)) { char *target = NULL; int flags = 0; @@ -253,12 +250,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); if (kinds == FILTER_REFS_BRANCHES) { - const struct worktree *wt = - find_shared_symref(worktrees, "HEAD", name); - if (wt) { + const char *path; + if ((path = branch_checked_out(name))) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, wt->path); + bname.buf, path); ret = 1; continue; } @@ -315,7 +311,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); strbuf_release(&bname); - free_worktrees(worktrees); return ret; } @@ -543,6 +538,13 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int die(_("Invalid branch name: '%s'"), oldname); } + if ((copy || strcmp(head, oldname)) && !ref_exists(oldref.buf)) { + if (copy && !strcmp(head, oldname)) + die(_("No commit on branch '%s' yet."), oldname); + else + die(_("No branch named '%s'."), oldname); + } + /* * A command like "git branch -M currentbranch currentbranch" cannot * cause the worktree to become inconsistent with HEAD, so allow it. @@ -604,10 +606,11 @@ static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION") static int edit_branch_description(const char *branch_name) { + int exists; struct strbuf buf = STRBUF_INIT; struct strbuf name = STRBUF_INIT; - read_branch_desc(&buf, branch_name); + exists = !read_branch_desc(&buf, branch_name); if (!buf.len || buf.buf[buf.len-1] != '\n') strbuf_addch(&buf, '\n'); strbuf_commented_addf(&buf, @@ -624,7 +627,8 @@ static int edit_branch_description(const char *branch_name) strbuf_stripspace(&buf, 1); strbuf_addf(&name, "branch.%s.description", branch_name); - git_config_set(name.buf, buf.len ? buf.buf : NULL); + if (buf.len || exists) + git_config_set(name.buf, buf.len ? buf.buf : NULL); strbuf_release(&name); strbuf_release(&buf); @@ -796,31 +800,33 @@ int cmd_branch(int argc, const char **argv, const char *prefix) } else if (edit_description) { const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + int ret = 1; /* assume failure */ if (!argc) { if (filter.detached) die(_("Cannot give description to detached HEAD")); branch_name = head; - } else if (argc == 1) - branch_name = argv[0]; - else + } else if (argc == 1) { + strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + branch_name = buf.buf; + } else { die(_("cannot edit description of more than one branch")); + } strbuf_addf(&branch_ref, "refs/heads/%s", branch_name); - if (!ref_exists(branch_ref.buf)) { - strbuf_release(&branch_ref); - - if (!argc) - return error(_("No commit on branch '%s' yet."), - branch_name); - else - return error(_("No branch named '%s'."), - branch_name); - } + if (!ref_exists(branch_ref.buf)) + ret = error((!argc || !strcmp(head, branch_name)) + ? _("No commit on branch '%s' yet.") + : _("No branch named '%s'."), + branch_name); + else if (!edit_branch_description(branch_name)) + ret = 0; /* happy */ + strbuf_release(&branch_ref); + strbuf_release(&buf); - if (edit_branch_description(branch_name)) - return 1; + return ret; } else if (copy) { if (!argc) die(_("branch name required")); @@ -840,9 +846,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) else die(_("too many arguments for a rename operation")); } else if (new_upstream) { - struct branch *branch = branch_get(argv[0]); + struct branch *branch; + struct strbuf buf = STRBUF_INIT; - if (argc > 1) + if (!argc) + branch = branch_get(NULL); + else if (argc == 1) { + strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + branch = branch_get(buf.buf); + } else die(_("too many arguments to set new upstream")); if (!branch) { @@ -853,17 +865,26 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("no such branch '%s'"), argv[0]); } - if (!ref_exists(branch->refname)) + if (!ref_exists(branch->refname)) { + if (!argc || !strcmp(head, branch->name)) + die(_("No commit on branch '%s' yet."), branch->name); die(_("branch '%s' does not exist"), branch->name); + } dwim_and_setup_tracking(the_repository, branch->name, new_upstream, BRANCH_TRACK_OVERRIDE, quiet); + strbuf_release(&buf); } else if (unset_upstream) { - struct branch *branch = branch_get(argv[0]); + struct branch *branch; struct strbuf buf = STRBUF_INIT; - if (argc > 1) + if (!argc) + branch = branch_get(NULL); + else if (argc == 1) { + strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + branch = branch_get(buf.buf); + } else die(_("too many arguments to unset upstream")); if (!branch) { @@ -876,6 +897,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!branch_has_merge_config(branch)) die(_("Branch '%s' has no upstream information"), branch->name); + strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.remote", branch->name); git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_reset(&buf); diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 9de32bc96e..530895be55 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -5,6 +5,7 @@ #include "compat/compiler.h" #include "hook.h" #include "hook-list.h" +#include "diagnose.h" static void get_system_info(struct strbuf *sys_info) @@ -59,7 +60,7 @@ static void get_populated_hooks(struct strbuf *hook_info, int nongit) } static const char * const bugreport_usage[] = { - N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"), + N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>] [--diagnose[=<mode>]"), NULL }; @@ -98,16 +99,21 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) int report = -1; time_t now = time(NULL); struct tm tm; + enum diagnose_mode diagnose = DIAGNOSE_NONE; char *option_output = NULL; char *option_suffix = "%Y-%m-%d-%H%M"; const char *user_relative_path = NULL; char *prefixed_filename; + size_t output_path_len; const struct option bugreport_options[] = { + OPT_CALLBACK_F(0, "diagnose", &diagnose, N_("mode"), + N_("create an additional zip archive of detailed diagnostics (default 'stats')"), + PARSE_OPT_OPTARG, option_parse_diagnose), OPT_STRING('o', "output-directory", &option_output, N_("path"), - N_("specify a destination for the bugreport file")), + N_("specify a destination for the bugreport file(s)")), OPT_STRING('s', "suffix", &option_suffix, N_("format"), - N_("specify a strftime format suffix for the filename")), + N_("specify a strftime format suffix for the filename(s)")), OPT_END() }; @@ -119,6 +125,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) option_output ? option_output : ""); strbuf_addstr(&report_path, prefixed_filename); strbuf_complete(&report_path, '/'); + output_path_len = report_path.len; strbuf_addstr(&report_path, "git-bugreport-"); strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0); @@ -133,6 +140,20 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) report_path.buf); } + /* Prepare diagnostics, if requested */ + if (diagnose != DIAGNOSE_NONE) { + struct strbuf zip_path = STRBUF_INIT; + strbuf_add(&zip_path, report_path.buf, output_path_len); + strbuf_addstr(&zip_path, "git-diagnostics-"); + strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0); + strbuf_addstr(&zip_path, ".zip"); + + if (create_diagnostics_archive(&zip_path, diagnose)) + die_errno(_("unable to create diagnostics archive %s"), zip_path.buf); + + strbuf_release(&zip_path); + } + /* Prepare the report contents */ get_bug_template(&buffer); diff --git a/builtin/bundle.c b/builtin/bundle.c index 2adad545a2..e80efce3a4 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -195,30 +195,19 @@ cleanup: int cmd_bundle(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("create", &fn, cmd_bundle_create), + OPT_SUBCOMMAND("verify", &fn, cmd_bundle_verify), + OPT_SUBCOMMAND("list-heads", &fn, cmd_bundle_list_heads), + OPT_SUBCOMMAND("unbundle", &fn, cmd_bundle_unbundle), OPT_END() }; - int result; argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + 0); packet_trace_identity("bundle"); - if (argc < 2) - usage_with_options(builtin_bundle_usage, options); - - else if (!strcmp(argv[0], "create")) - result = cmd_bundle_create(argc, argv, prefix); - else if (!strcmp(argv[0], "verify")) - result = cmd_bundle_verify(argc, argv, prefix); - else if (!strcmp(argv[0], "list-heads")) - result = cmd_bundle_list_heads(argc, argv, prefix); - else if (!strcmp(argv[0], "unbundle")) - result = cmd_bundle_unbundle(argc, argv, prefix); - else { - error(_("Unknown subcommand: %s"), argv[0]); - usage_with_options(builtin_bundle_usage, options); - } - return result ? 1 : 0; + return !!fn(argc, argv, prefix); } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 50cf38999d..989eee0bb4 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -16,6 +16,7 @@ #include "packfile.h" #include "object-store.h" #include "promisor-remote.h" +#include "mailmap.h" enum batch_mode { BATCH_MODE_CONTENTS, @@ -31,11 +32,28 @@ struct batch_options { int all_objects; int unordered; int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */ + int nul_terminated; const char *format; }; static const char *force_path; +static struct string_list mailmap = STRING_LIST_INIT_NODUP; +static int use_mailmap; + +static char *replace_idents_using_mailmap(char *, size_t *); + +static char *replace_idents_using_mailmap(char *object_buf, size_t *size) +{ + struct strbuf sb = STRBUF_INIT; + const char *headers[] = { "author ", "committer ", "tagger ", NULL }; + + strbuf_attach(&sb, object_buf, *size, *size + 1); + apply_mailmap_to_header(&sb, headers, &mailmap); + *size = sb.len; + return strbuf_detach(&sb, NULL); +} + static int filter_object(const char *path, unsigned mode, const struct object_id *oid, char **buf, unsigned long *size) @@ -71,6 +89,7 @@ static int stream_blob(const struct object_id *oid) static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) { + int ret; struct object_id oid; enum object_type type; char *buf; @@ -106,7 +125,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (sb.len) { printf("%s\n", sb.buf); strbuf_release(&sb); - return 0; + ret = 0; + goto cleanup; } break; @@ -115,7 +135,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) die("git cat-file: could not get object info"); printf("%"PRIuMAX"\n", (uintmax_t)size); - return 0; + ret = 0; + goto cleanup; case 'e': return !has_object_file(&oid); @@ -123,8 +144,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 'w': if (filter_object(path, obj_context.mode, - &oid, &buf, &size)) - return -1; + &oid, &buf, &size)) { + ret = -1; + goto cleanup; + } break; case 'c': @@ -143,15 +166,24 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, const char *ls_args[3] = { NULL }; ls_args[0] = "ls-tree"; ls_args[1] = obj_name; - return cmd_ls_tree(2, ls_args, NULL); + ret = cmd_ls_tree(2, ls_args, NULL); + goto cleanup; } - if (type == OBJ_BLOB) - return stream_blob(&oid); + if (type == OBJ_BLOB) { + ret = stream_blob(&oid); + goto cleanup; + } buf = read_object_file(&oid, &type, &size); if (!buf) die("Cannot read object %s", obj_name); + if (use_mailmap) { + size_t s = size; + buf = replace_idents_using_mailmap(buf, &s); + size = cast_size_t_to_ulong(s); + } + /* otherwise just spit out the data */ break; @@ -172,8 +204,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } else oidcpy(&blob_oid, &oid); - if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) - return stream_blob(&blob_oid); + if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) { + ret = stream_blob(&blob_oid); + goto cleanup; + } /* * we attempted to dereference a tag to a blob * and failed; there may be new dereference @@ -183,6 +217,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } buf = read_object_with_reference(the_repository, &oid, exp_type_id, &size, NULL); + + if (use_mailmap) { + size_t s = size; + buf = replace_idents_using_mailmap(buf, &s); + size = cast_size_t_to_ulong(s); + } break; } default: @@ -193,9 +233,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, die("git cat-file %s: bad file", obj_name); write_or_die(1, buf, size); + ret = 0; +cleanup: free(buf); free(obj_context.path); - return 0; + return ret; } struct expand_data { @@ -348,11 +390,18 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d void *contents; contents = read_object_file(oid, &type, &size); + + if (use_mailmap) { + size_t s = size; + contents = replace_idents_using_mailmap(contents, &s); + size = cast_size_t_to_ulong(s); + } + if (!contents) die("object %s disappeared", oid_to_hex(oid)); if (type != data->type) die("object %s changed type!?", oid_to_hex(oid)); - if (data->info.sizep && size != data->size) + if (data->info.sizep && size != data->size && !use_mailmap) die("object %s changed size!?", oid_to_hex(oid)); batch_write(opt, contents, size); @@ -602,12 +651,20 @@ static void batch_objects_command(struct batch_options *opt, struct queued_cmd *queued_cmd = NULL; size_t alloc = 0, nr = 0; - while (!strbuf_getline(&input, stdin)) { - int i; + while (1) { + int i, ret; const struct parse_cmd *cmd = NULL; const char *p = NULL, *cmd_end; struct queued_cmd call = {0}; + if (opt->nul_terminated) + ret = strbuf_getline_nul(&input, stdin); + else + ret = strbuf_getline(&input, stdin); + + if (ret) + break; + if (!input.len) die(_("empty command in input")); if (isspace(*input.buf)) @@ -655,6 +712,7 @@ static void batch_objects_command(struct batch_options *opt, free_cmds(queued_cmd, &nr); } + free_cmds(queued_cmd, &nr); free(queued_cmd); strbuf_release(&input); } @@ -750,7 +808,16 @@ static int batch_objects(struct batch_options *opt) goto cleanup; } - while (strbuf_getline(&input, stdin) != EOF) { + while (1) { + int ret; + if (opt->nul_terminated) + ret = strbuf_getline_nul(&input, stdin); + else + ret = strbuf_getline(&input, stdin); + + if (ret == EOF) + break; + if (data.split_on_whitespace) { /* * Split at first whitespace, tying off the beginning @@ -843,6 +910,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), + OPT_BOOL(0, "use-mailmap", &use_mailmap, N_("use mail map file")), + OPT_ALIAS(0, "mailmap", "use-mailmap"), /* Batch mode */ OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), OPT_CALLBACK_F(0, "batch", &batch, N_("format"), @@ -853,6 +922,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) N_("like --batch, but don't emit <contents>"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), + OPT_BOOL('z', NULL, &batch.nul_terminated, N_("stdin is NUL-terminated")), OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"), N_("read commands from stdin"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, @@ -885,6 +955,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) opt_cw = (opt == 'c' || opt == 'w'); opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); + if (use_mailmap) + read_mailmap(&mailmap); + /* --batch-all-objects? */ if (opt == 'b') batch.all_objects = 1; @@ -908,6 +981,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) else if (batch.all_objects) usage_msg_optf(_("'%s' requires a batch mode"), usage, options, "--batch-all-objects"); + else if (batch.nul_terminated) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "-z"); /* Batch defaults */ if (batch.buffer_output < 0) diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index bc67d3f0a8..fd0e5f8683 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -57,6 +57,8 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) int normalize = 0; int flags = 0; const char *refname; + char *to_free = NULL; + int ret = 1; if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); @@ -81,11 +83,14 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) refname = argv[i]; if (normalize) - refname = collapse_slashes(refname); + refname = to_free = collapse_slashes(refname); if (check_refname_format(refname, flags)) - return 1; + goto cleanup; if (normalize) printf("%s\n", refname); - return 0; + ret = 0; +cleanup: + free(to_free); + return ret; } diff --git a/builtin/checkout.c b/builtin/checkout.c index cdd96cd9c6..2a132392fb 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -125,7 +125,7 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm } static int update_some(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, void *context UNUSED) { int len; struct cache_entry *ce; @@ -711,6 +711,26 @@ static void setup_branch_path(struct branch_info *branch) branch->path = strbuf_detach(&buf, NULL); } +static void init_topts(struct unpack_trees_options *topts, int merge, + int show_progress, int overwrite_ignore, + struct commit *old_commit) +{ + memset(topts, 0, sizeof(*topts)); + topts->head_idx = -1; + topts->src_index = &the_index; + topts->dst_index = &the_index; + + setup_unpack_trees_porcelain(topts, "checkout"); + + topts->initial_checkout = is_cache_unborn(); + topts->update = 1; + topts->merge = 1; + topts->quiet = merge && old_commit; + topts->verbose_update = show_progress; + topts->fn = twoway_merge; + topts->preserve_ignored = !overwrite_ignore; +} + static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, @@ -741,13 +761,6 @@ static int merge_working_tree(const struct checkout_opts *opts, struct unpack_trees_options topts; const struct object_id *old_commit_oid; - memset(&topts, 0, sizeof(topts)); - topts.head_idx = -1; - topts.src_index = &the_index; - topts.dst_index = &the_index; - - setup_unpack_trees_porcelain(&topts, "checkout"); - refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { @@ -756,17 +769,12 @@ static int merge_working_tree(const struct checkout_opts *opts, } /* 2-way merge to the new branch */ - topts.initial_checkout = is_cache_unborn(); - topts.update = 1; - topts.merge = 1; - topts.quiet = opts->merge && old_branch_info->commit; - topts.verbose_update = opts->show_progress; - topts.fn = twoway_merge; + init_topts(&topts, opts->merge, opts->show_progress, + opts->overwrite_ignore, old_branch_info->commit); init_checkout_metadata(&topts.meta, new_branch_info->refname, new_branch_info->commit ? &new_branch_info->commit->object.oid : &new_branch_info->oid, NULL); - topts.preserve_ignored = !opts->overwrite_ignore; old_commit_oid = old_branch_info->commit ? &old_branch_info->commit->object.oid : @@ -982,7 +990,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, static int add_pending_uninteresting_ref(const char *refname, const struct object_id *oid, - int flags, void *cb_data) + int flags UNUSED, void *cb_data) { add_pending_oid(cb_data, refname, oid, UNINTERESTING); return 0; diff --git a/builtin/clone.c b/builtin/clone.c index 9e0b2b45ca..547d6464b3 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -34,6 +34,7 @@ #include "list-objects-filter-options.h" #include "hook.h" #include "bundle.h" +#include "bundle-uri.h" /* * Overall FIXMEs: @@ -72,11 +73,12 @@ static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; static int option_dissociate; static int max_jobs = -1; static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP; -static struct list_objects_filter_options filter_options; +static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; static int option_filter_submodules = -1; /* unspecified */ static int config_filter_submodules = -1; /* unspecified */ static struct string_list server_options = STRING_LIST_INIT_NODUP; static int option_remote_submodules; +static const char *bundle_uri; static int recurse_submodules_cb(const struct option *opt, const char *arg, int unset) @@ -160,6 +162,8 @@ static struct option builtin_clone_options[] = { N_("any cloned submodules will use their remote-tracking branch")), OPT_BOOL(0, "sparse", &option_sparse_checkout, N_("initialize sparse-checkout file to include only files at root")), + OPT_STRING(0, "bundle-uri", &bundle_uri, + N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")), OPT_END() }; @@ -316,13 +320,11 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, int src_len, dest_len; struct dir_iterator *iter; int iter_status; - unsigned int flags; struct strbuf realpath = STRBUF_INIT; mkdir_if_missing(dest->buf, 0777); - flags = DIR_ITERATOR_PEDANTIC | DIR_ITERATOR_FOLLOW_SYMLINKS; - iter = dir_iterator_begin(src->buf, flags); + iter = dir_iterator_begin(src->buf, DIR_ITERATOR_PEDANTIC); if (!iter) die_errno(_("failed to start iterator over '%s'"), src->buf); @@ -338,6 +340,10 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, strbuf_setlen(dest, dest_len); strbuf_addstr(dest, iter->relative_path); + if (S_ISLNK(iter->st.st_mode)) + die(_("symlink '%s' exists, refusing to clone with --local"), + iter->relative_path); + if (S_ISDIR(iter->st.st_mode)) { mkdir_if_missing(dest->buf, 0777); continue; @@ -494,6 +500,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs, /* if --branch=tag, pull the requested tag explicitly */ get_fetch_map(remote_head, tag_refspec, &tail, 0); } + free_refs(remote_head); } else { int i; for (i = 0; i < refspec->nr; i++) @@ -924,14 +931,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_bare = 1; if (option_bare) { - if (option_origin) - die(_("options '%s' and '%s %s' cannot be used together"), - "--bare", "--origin", option_origin); if (real_git_dir) die(_("options '%s' and '%s' cannot be used together"), "--bare", "--separate-git-dir"); option_no_checkout = 1; } + if (bundle_uri && deepen) + die(_("--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-exclude")); + repo_name = argv[0]; path = get_repo_path(repo_name, &is_bundle); @@ -1231,6 +1238,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (transport->smart_options && !deepen && !filter_options.choice) transport->smart_options->check_self_contained_and_connected = 1; + /* + * Before fetching from the remote, download and install bundle + * data from the --bundle-uri option. + */ + if (bundle_uri) { + /* At this point, we need the_repository to match the cloned repo. */ + if (repo_init(the_repository, git_dir, work_tree)) + warning(_("failed to initialize the repo, skipping bundle URI")); + else if (fetch_bundle_uri(the_repository, bundle_uri)) + warning(_("failed to fetch objects from bundle URI '%s'"), + bundle_uri); + } strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); refspec_ref_prefixes(&remote->fetch, diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 51c4040ea6..51557fe786 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -58,7 +58,7 @@ static struct option *add_common_options(struct option *to) return parse_options_concat(common_opts, to); } -static int graph_verify(int argc, const char **argv) +static int graph_verify(int argc, const char **argv, const char *prefix) { struct commit_graph *graph = NULL; struct object_directory *odb = NULL; @@ -80,7 +80,7 @@ static int graph_verify(int argc, const char **argv) trace2_cmd_mode("verify"); opts.progress = isatty(2); - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_commit_graph_verify_usage, 0); if (argc) @@ -179,7 +179,7 @@ static int write_option_max_new_filters(const struct option *opt, } static int git_commit_graph_write_config(const char *var, const char *value, - void *cb) + void *cb UNUSED) { if (!strcmp(var, "commitgraph.maxnewfilters")) write_opts.max_new_filters = git_config_int(var, value); @@ -190,7 +190,7 @@ static int git_commit_graph_write_config(const char *var, const char *value, return 0; } -static int graph_write(int argc, const char **argv) +static int graph_write(int argc, const char **argv, const char *prefix) { struct string_list pack_indexes = STRING_LIST_INIT_DUP; struct strbuf buf = STRBUF_INIT; @@ -241,7 +241,7 @@ static int graph_write(int argc, const char **argv) git_config(git_commit_graph_write_config, &opts); - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_commit_graph_write_usage, 0); if (argc) @@ -307,26 +307,22 @@ cleanup: int cmd_commit_graph(int argc, const char **argv, const char *prefix) { - struct option *builtin_commit_graph_options = common_opts; + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_commit_graph_options[] = { + OPT_SUBCOMMAND("verify", &fn, graph_verify), + OPT_SUBCOMMAND("write", &fn, graph_write), + OPT_END(), + }; + struct option *options = parse_options_concat(builtin_commit_graph_options, common_opts); git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, - builtin_commit_graph_options, - builtin_commit_graph_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - if (!argc) - goto usage; read_replace_refs = 0; save_commit_buffer = 0; - if (!strcmp(argv[0], "verify")) - return graph_verify(argc, argv); - else if (argc && !strcmp(argv[0], "write")) - return graph_write(argc, argv); + argc = parse_options(argc, argv, prefix, options, + builtin_commit_graph_usage, 0); + FREE_AND_NULL(options); - error(_("unrecognized subcommand: %s"), argv[0]); -usage: - usage_with_options(builtin_commit_graph_usage, - builtin_commit_graph_options); + return fn(argc, argv, prefix); } diff --git a/builtin/commit.c b/builtin/commit.c index fcf9c85947..d9de4ef008 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -139,7 +139,7 @@ static int opt_pass_trailer(const struct option *opt, const char *arg, int unset { BUG_ON_OPT_NEG(unset); - strvec_pushl(&trailer_args, "--trailer", arg, NULL); + strvec_pushl(opt->value, "--trailer", arg, NULL); return 0; } @@ -1633,7 +1633,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer), + OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer), OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), diff --git a/builtin/config.c b/builtin/config.c index e7b88a9c08..753e5fac29 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -207,7 +207,8 @@ static void show_config_scope(struct strbuf *buf) strbuf_addch(buf, term); } -static int show_all_config(const char *key_, const char *value_, void *cb) +static int show_all_config(const char *key_, const char *value_, + void *cb UNUSED) { if (show_origin || show_scope) { struct strbuf buf = STRBUF_INIT; @@ -458,7 +459,8 @@ static const char *get_color_slot; static const char *get_colorbool_slot; static char parsed_color[COLOR_MAXLEN]; -static int git_get_color_config(const char *var, const char *value, void *cb) +static int git_get_color_config(const char *var, const char *value, + void *cb UNUSED) { if (!strcmp(var, get_color_slot)) { if (!value) @@ -490,7 +492,7 @@ static int get_colorbool_found; static int get_diff_color_found; static int get_color_ui_found; static int git_get_colorbool_config(const char *var, const char *value, - void *cb) + void *data UNUSED) { if (!strcmp(var, get_colorbool_slot)) get_colorbool_found = git_config_colorbool(var, value); diff --git a/builtin/describe.c b/builtin/describe.c index a76f1a1a7a..e17c4b4c69 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -63,7 +63,7 @@ static const char *prio_names[] = { N_("head"), N_("lightweight"), N_("annotated"), }; -static int commit_name_neq(const void *unused_cmp_data, +static int commit_name_neq(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *peeled) @@ -140,7 +140,8 @@ static void add_to_known_names(const char *path, } } -static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data) +static int get_name(const char *path, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { int is_tag = 0; struct object_id peeled; diff --git a/builtin/diagnose.c b/builtin/diagnose.c new file mode 100644 index 0000000000..576e0e8e38 --- /dev/null +++ b/builtin/diagnose.c @@ -0,0 +1,61 @@ +#include "builtin.h" +#include "parse-options.h" +#include "diagnose.h" + +static const char * const diagnose_usage[] = { + N_("git diagnose [-o|--output-directory <path>] [-s|--suffix <format>] [--mode=<mode>]"), + NULL +}; + +int cmd_diagnose(int argc, const char **argv, const char *prefix) +{ + struct strbuf zip_path = STRBUF_INIT; + time_t now = time(NULL); + struct tm tm; + enum diagnose_mode mode = DIAGNOSE_STATS; + char *option_output = NULL; + char *option_suffix = "%Y-%m-%d-%H%M"; + char *prefixed_filename; + + const struct option diagnose_options[] = { + OPT_STRING('o', "output-directory", &option_output, N_("path"), + N_("specify a destination for the diagnostics archive")), + OPT_STRING('s', "suffix", &option_suffix, N_("format"), + N_("specify a strftime format suffix for the filename")), + OPT_CALLBACK_F(0, "mode", &mode, "(stats|all)", + N_("specify the content of the diagnostic archive"), + PARSE_OPT_NONEG, option_parse_diagnose), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, diagnose_options, + diagnose_usage, 0); + + /* Prepare the path to put the result */ + prefixed_filename = prefix_filename(prefix, + option_output ? option_output : ""); + strbuf_addstr(&zip_path, prefixed_filename); + strbuf_complete(&zip_path, '/'); + + strbuf_addstr(&zip_path, "git-diagnostics-"); + strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0); + strbuf_addstr(&zip_path, ".zip"); + + switch (safe_create_leading_directories(zip_path.buf)) { + case SCLD_OK: + case SCLD_EXISTS: + break; + default: + die_errno(_("could not create leading directories for '%s'"), + zip_path.buf); + } + + /* Prepare diagnostics */ + if (create_diagnostics_archive(&zip_path, mode)) + die_errno(_("unable to create diagnostics archive %s"), + zip_path.buf); + + free(prefixed_filename); + strbuf_release(&zip_path); + return 0; +} diff --git a/builtin/difftool.c b/builtin/difftool.c index b3c509b8de..4b10ad1a36 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -125,10 +125,10 @@ struct working_tree_entry { char path[FLEX_ARRAY]; }; -static int working_tree_entry_cmp(const void *unused_cmp_data, +static int working_tree_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct working_tree_entry *a, *b; @@ -148,10 +148,10 @@ struct pair_entry { const char path[FLEX_ARRAY]; }; -static int pair_cmp(const void *unused_cmp_data, +static int pair_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct pair_entry *a, *b; @@ -184,7 +184,7 @@ struct path_entry { char path[FLEX_ARRAY]; }; -static int path_entry_cmp(const void *unused_cmp_data, +static int path_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *key) @@ -716,7 +716,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) symlinks = has_symlinks; argc = parse_options(argc, argv, prefix, builtin_difftool_options, - builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); if (tool_help) diff --git a/builtin/env--helper.c b/builtin/env--helper.c index 27349098b0..ea04c16636 100644 --- a/builtin/env--helper.c +++ b/builtin/env--helper.c @@ -50,7 +50,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, opts, env__helper_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (env_default && !*env_default) usage_with_options(env__helper_usage, opts); if (!cmdmode) diff --git a/builtin/fast-export.c b/builtin/fast-export.c index e1748fb98b..3b3314e7b2 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -119,7 +119,7 @@ struct anonymized_entry_key { size_t orig_len; }; -static int anonymized_entry_cmp(const void *unused_cmp_data, +static int anonymized_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) @@ -1221,7 +1221,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) revs.sources = &revision_sources; revs.rewrite_parents = 1; argc = parse_options(argc, argv, prefix, options, fast_export_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); argc = setup_revisions(argc, argv, &revs, NULL); if (argc > 1) usage_with_options (fast_export_usage, options); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 14113cfd82..7134683ab9 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -46,7 +46,7 @@ struct object_entry { depth : DEPTH_BITS; }; -static int object_entry_hashcmp(const void *map_data, +static int object_entry_hashcmp(const void *map_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index f045bbbe94..afe679368d 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -62,6 +62,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) packet_trace_identity("fetch-pack"); memset(&args, 0, sizeof(args)); + list_objects_filter_init(&args.filter_options); args.uploadpack = "git-upload-pack"; for (i = 1; i < argc && *argv[i] == '-'; i++) { diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae..a0fca93bb6 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -80,7 +80,7 @@ static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND; static int shown_url = 0; static struct refspec refmap = REFSPEC_INIT_FETCH; -static struct list_objects_filter_options filter_options; +static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; static int fetch_write_commit_graph = -1; @@ -301,7 +301,7 @@ struct refname_hash_entry { char refname[FLEX_ARRAY]; }; -static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data, +static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) @@ -329,7 +329,7 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map, static int add_one_refname(const char *refname, const struct object_id *oid, - int flag, void *cbdata) + int flag UNUSED, void *cbdata) { struct hashmap *refname_map = cbdata; @@ -490,7 +490,9 @@ static void filter_prefetch_refspec(struct refspec *rs) continue; if (!rs->items[i].dst || (rs->items[i].src && - !strncmp(rs->items[i].src, "refs/tags/", 10))) { + !strncmp(rs->items[i].src, + ref_namespace[NAMESPACE_TAGS].ref, + strlen(ref_namespace[NAMESPACE_TAGS].ref)))) { int j; free(rs->items[i].src); @@ -506,7 +508,7 @@ static void filter_prefetch_refspec(struct refspec *rs) } old_dst = rs->items[i].dst; - strbuf_addstr(&new_dst, "refs/prefetch/"); + strbuf_addstr(&new_dst, ref_namespace[NAMESPACE_PREFETCH].ref); /* * If old_dst starts with "refs/", then place @@ -881,11 +883,9 @@ 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, int summary_width, - struct worktree **worktrees) + struct strbuf *display, int summary_width) { struct commit *current = NULL, *updated; - const struct worktree *wt; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,16 +900,14 @@ static int update_local_ref(struct ref *ref, } if (!update_head_ok && - (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && - !wt->is_bare && !is_null_oid(&ref->old_oid)) { + !is_null_oid(&ref->old_oid) && + branch_checked_out(ref->name)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - wt->is_current ? - _("can't fetch in current branch") : - _("checked out in another worktree"), + _("can't fetch into checked-out branch"), remote, pretty_ref, summary_width); return 1; } @@ -1110,10 +1108,10 @@ 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_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, struct worktree **worktrees) + struct fetch_head *fetch_head) { int url_len, i, rc = 0; - struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; char *url; @@ -1240,8 +1238,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_reset(¬e); if (ref) { rc |= update_local_ref(ref, transaction, what, - rm, ¬e, summary_width, - worktrees); + rm, ¬e, summary_width); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1281,7 +1278,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, abort: strbuf_release(¬e); - strbuf_release(&err); free(url); return rc; } @@ -1332,8 +1328,7 @@ static int check_exist_and_connected(struct ref *ref_map) static int fetch_and_consume_refs(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int connectivity_checked = 1; int ret; @@ -1356,7 +1351,7 @@ static int fetch_and_consume_refs(struct transport *transport, trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(transport->url, transport->remote->name, connectivity_checked, transaction, ref_map, - fetch_head, worktrees); + fetch_head); trace2_region_leave("fetch", "consume_refs", the_repository); out: @@ -1434,19 +1429,16 @@ cleanup: return result; } -static void check_not_current_branch(struct ref *ref_map, - struct worktree **worktrees) +static void check_not_current_branch(struct ref *ref_map) { - const struct worktree *wt; + const char *path; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && starts_with(ref_map->peer_ref->name, "refs/heads/") && - (wt = find_shared_symref(worktrees, "HEAD", - ref_map->peer_ref->name)) && - !wt->is_bare) + (path = branch_checked_out(ref_map->peer_ref->name))) die(_("refusing to fetch into branch '%s' " "checked out at '%s'"), - ref_map->peer_ref->name, wt->path); + ref_map->peer_ref->name, path); } static int truncate_fetch_head(void) @@ -1472,8 +1464,9 @@ static void set_option(struct transport *transport, const char *name, const char } -static int add_oid(const char *refname, const struct object_id *oid, int flags, - void *cb_data) +static int add_oid(const char *refname UNUSED, + const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct oid_array *oids = cb_data; @@ -1549,8 +1542,7 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) static int backfill_tags(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int retcode, cannot_reuse; @@ -1571,7 +1563,7 @@ static int backfill_tags(struct transport *transport, transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees); + retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head); if (gsecondary) { transport_disconnect(gsecondary); @@ -1592,7 +1584,6 @@ static int do_fetch(struct transport *transport, struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; - struct worktree **worktrees = get_worktrees(); struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; @@ -1626,9 +1617,21 @@ static int do_fetch(struct transport *transport, break; } } - } else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, - &transport_ls_refs_options.ref_prefixes); + } else { + struct branch *branch = branch_get(NULL); + + if (transport->remote->fetch.nr) + refspec_ref_prefixes(&transport->remote->fetch, + &transport_ls_refs_options.ref_prefixes); + if (branch_has_merge_config(branch) && + !strcmp(branch->remote_name, transport->remote->name)) { + int i; + for (i = 0; i < branch->merge_nr; i++) { + strvec_push(&transport_ls_refs_options.ref_prefixes, + branch->merge[i]->src); + } + } + } if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; @@ -1650,7 +1653,7 @@ static int do_fetch(struct transport *transport, ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); if (!update_head_ok) - check_not_current_branch(ref_map, worktrees); + check_not_current_branch(ref_map); retcode = open_fetch_head(&fetch_head); if (retcode) @@ -1683,7 +1686,7 @@ static int do_fetch(struct transport *transport, retcode = 1; } - if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) { + if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head)) { retcode = 1; goto cleanup; } @@ -1706,7 +1709,7 @@ static int do_fetch(struct transport *transport, * the transaction and don't commit anything. */ if (backfill_tags(transport, transaction, tags_ref_map, - &fetch_head, worktrees)) + &fetch_head)) retcode = 1; } @@ -1791,7 +1794,6 @@ cleanup: close_fetch_head(&fetch_head); strbuf_release(&err); free_refs(ref_map); - free_worktrees(worktrees); return retcode; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 6c73092f10..41acbc229e 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -228,6 +228,8 @@ static void mark_unreachable_referents(const struct object_id *oid) options.walk = mark_used; fsck_walk(obj, NULL, &options); + if (obj->type == OBJ_TREE) + free_tree_buffer((struct tree *)obj); } static int mark_loose_unreachable_referents(const struct object_id *oid, @@ -437,9 +439,6 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) out: if (obj->type == OBJ_TREE) free_tree_buffer((struct tree *)obj); - if (obj->type == OBJ_COMMIT) - free_commit_buffer(the_repository->parsed_objects, - (struct commit *)obj); return err; } @@ -488,8 +487,9 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, } static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) + const char *email UNUSED, + timestamp_t timestamp, int tz UNUSED, + const char *message UNUSED, void *cb_data) { const char *refname = cb_data; @@ -502,8 +502,9 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid return 0; } -static int fsck_handle_reflog(const char *logname, const struct object_id *oid, - int flag, void *cb_data) +static int fsck_handle_reflog(const char *logname, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cb_data) { struct strbuf refname = STRBUF_INIT; @@ -514,7 +515,7 @@ static int fsck_handle_reflog(const char *logname, const struct object_id *oid, } static int fsck_handle_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { struct object *obj; @@ -851,6 +852,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) errors_found = 0; read_replace_refs = 0; + save_commit_buffer = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c index 2c109cf8b3..6f30a4f93a 100644 --- a/builtin/fsmonitor--daemon.c +++ b/builtin/fsmonitor--daemon.c @@ -3,6 +3,7 @@ #include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "fsmonitor-path-utils.h" #include "compat/fsmonitor/fsm-health.h" #include "compat/fsmonitor/fsm-listen.h" #include "fsmonitor--daemon.h" @@ -13,8 +14,8 @@ static const char * const builtin_fsmonitor__daemon_usage[] = { N_("git fsmonitor--daemon start [<options>]"), N_("git fsmonitor--daemon run [<options>]"), - N_("git fsmonitor--daemon stop"), - N_("git fsmonitor--daemon status"), + "git fsmonitor--daemon stop", + "git fsmonitor--daemon status", NULL }; @@ -1282,6 +1283,11 @@ static int fsmonitor_run_daemon(void) strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree())); state.nr_paths_watching = 1; + strbuf_init(&state.alias.alias, 0); + strbuf_init(&state.alias.points_to, 0); + if ((err = fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias))) + goto done; + /* * We create and delete cookie files somewhere inside the .git * directory to help us keep sync with the file system. If @@ -1343,7 +1349,8 @@ static int fsmonitor_run_daemon(void) * directory.) */ strbuf_init(&state.path_ipc, 0); - strbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path())); + strbuf_addstr(&state.path_ipc, + absolute_path(fsmonitor_ipc__get_path(the_repository))); /* * Confirm that we can create platform-specific resources for the @@ -1390,6 +1397,8 @@ done: strbuf_release(&state.path_gitdir_watch); strbuf_release(&state.path_cookie_prefix); strbuf_release(&state.path_ipc); + strbuf_release(&state.alias.alias); + strbuf_release(&state.alias.points_to); return err; } diff --git a/builtin/gc.c b/builtin/gc.c index 021e9256ae..243ee85d28 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -167,10 +167,9 @@ static void gc_config(void) struct maintenance_run_opts; static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) { - struct strvec pack_refs_cmd = STRVEC_INIT; - strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + const char *argv[] = { "pack-refs", "--all", "--prune", NULL }; - return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); + return run_command_v_opt(argv, RUN_GIT_CMD); } static int too_many_loose_objects(void) @@ -776,8 +775,9 @@ struct cg_auto_data { int limit; }; -static int dfs_on_ref(const char *refname, - const struct object_id *oid, int flags, +static int dfs_on_ref(const char *refname UNUSED, + const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct cg_auto_data *data = (struct cg_auto_data *)cb_data; @@ -904,12 +904,6 @@ static int fetch_remote(struct remote *remote, void *cbdata) static int maintenance_task_prefetch(struct maintenance_run_opts *opts) { - git_config_set_multivar_gently("log.excludedecoration", - "refs/prefetch/", - "refs/prefetch/", - CONFIG_FLAGS_FIXED_VALUE | - CONFIG_FLAGS_MULTI_REPLACE); - if (for_each_remote(fetch_remote, opts)) { error(_("failed to prefetch remotes")); return 1; @@ -1459,13 +1453,28 @@ static char *get_maintpath(void) return strbuf_detach(&sb, NULL); } -static int maintenance_register(void) +static char const * const builtin_maintenance_register_usage[] = { + "git maintenance register", + NULL +}; + +static int maintenance_register(int argc, const char **argv, const char *prefix) { - int rc; + struct option options[] = { + OPT_END(), + }; + int found = 0; + const char *key = "maintenance.repo"; char *config_value; - struct child_process config_set = CHILD_PROCESS_INIT; - struct child_process config_get = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + struct string_list_item *item; + const struct string_list *list; + + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_register_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_register_usage, + options); /* Disable foreground maintenance */ git_config_set("maintenance.auto", "false"); @@ -1476,46 +1485,95 @@ static int maintenance_register(void) else git_config_set("maintenance.strategy", "incremental"); - config_get.git_cmd = 1; - strvec_pushl(&config_get.args, "config", "--global", "--get", - "--fixed-value", "maintenance.repo", maintpath, NULL); - config_get.out = -1; - - if (start_command(&config_get)) { - rc = error(_("failed to run 'git config'")); - goto done; + list = git_config_get_value_multi(key); + if (list) { + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } } - /* We already have this value in our config! */ - if (!finish_command(&config_get)) { - rc = 0; - goto done; + if (!found) { + int rc; + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); + if (!user_config) + die(_("$HOME not set")); + rc = git_config_set_multivar_in_file_gently( + user_config, "maintenance.repo", maintpath, + CONFIG_REGEX_NONE, 0); + free(user_config); + free(xdg_config); + + if (rc) + die(_("unable to add '%s' value of '%s'"), + key, maintpath); } - config_set.git_cmd = 1; - strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", - maintpath, NULL); - - rc = run_command(&config_set); - -done: free(maintpath); - return rc; + return 0; } -static int maintenance_unregister(void) +static char const * const builtin_maintenance_unregister_usage[] = { + "git maintenance unregister [--force]", + NULL +}; + +static int maintenance_unregister(int argc, const char **argv, const char *prefix) { - int rc; - struct child_process config_unset = CHILD_PROCESS_INIT; + int force = 0; + struct option options[] = { + OPT__FORCE(&force, + N_("return success even if repository was not registered"), + PARSE_OPT_NOCOMPLETE), + OPT_END(), + }; + const char *key = "maintenance.repo"; char *maintpath = get_maintpath(); + int found = 0; + struct string_list_item *item; + const struct string_list *list; + + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_unregister_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_unregister_usage, + options); + + list = git_config_get_value_multi(key); + if (list) { + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } + } - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", maintpath, NULL); + if (found) { + int rc; + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); + if (!user_config) + die(_("$HOME not set")); + rc = git_config_set_multivar_in_file_gently( + user_config, key, NULL, maintpath, + CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE); + free(user_config); + free(xdg_config); + + if (rc && + (!force || rc == CONFIG_NOTHING_SET)) + die(_("unable to unset '%s' value of '%s'"), + key, maintpath); + } else if (!force) { + die(_("repository '%s' is not registered"), maintpath); + } - rc = run_command(&config_unset); free(maintpath); - return rc; + return 0; } static const char *get_frequency(enum schedule_priority schedule) @@ -2059,6 +2117,7 @@ static int crontab_update_schedule(int run_maintenance, int fd) struct child_process crontab_edit = CHILD_PROCESS_INIT; FILE *cron_list, *cron_in; struct strbuf line = STRBUF_INIT; + struct tempfile *tmpedit = NULL; get_schedule_cmd(&cmd, NULL); strvec_split(&crontab_list.args, cmd); @@ -2073,6 +2132,17 @@ static int crontab_update_schedule(int run_maintenance, int fd) /* Ignore exit code, as an empty crontab will return error. */ finish_command(&crontab_list); + tmpedit = mks_tempfile_t(".git_cron_edit_tmpXXXXXX"); + if (!tmpedit) { + result = error(_("failed to create crontab temporary file")); + goto out; + } + cron_in = fdopen_tempfile(tmpedit, "w"); + if (!cron_in) { + result = error(_("failed to open temporary file")); + goto out; + } + /* * Read from the .lock file, filtering out the old * schedule while appending the new schedule. @@ -2080,19 +2150,6 @@ static int crontab_update_schedule(int run_maintenance, int fd) cron_list = fdopen(fd, "r"); rewind(cron_list); - strvec_split(&crontab_edit.args, cmd); - crontab_edit.in = -1; - crontab_edit.git_cmd = 0; - - 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) { - result = error(_("failed to open stdin of 'crontab'")); - goto done_editing; - } - while (!strbuf_getline_lf(&line, cron_list)) { if (!in_old_region && !strcmp(line.buf, BEGIN_LINE)) in_old_region = 1; @@ -2126,14 +2183,22 @@ static int crontab_update_schedule(int run_maintenance, int fd) } fflush(cron_in); - fclose(cron_in); - close(crontab_edit.in); -done_editing: + strvec_split(&crontab_edit.args, cmd); + strvec_push(&crontab_edit.args, get_tempfile_path(tmpedit)); + crontab_edit.git_cmd = 0; + + if (start_command(&crontab_edit)) { + result = error(_("failed to run 'crontab'; your system might not support 'cron'")); + goto out; + } + if (finish_command(&crontab_edit)) result = error(_("'crontab' died")); else fclose(cron_list); +out: + delete_tempfile(&tmpedit); return result; } @@ -2490,6 +2555,7 @@ static int maintenance_start(int argc, const char **argv, const char *prefix) PARSE_OPT_NONEG, maintenance_opt_scheduler), OPT_END() }; + const char *register_args[] = { "register", NULL }; argc = parse_options(argc, argv, prefix, options, builtin_maintenance_start_usage, 0); @@ -2499,34 +2565,46 @@ static int maintenance_start(int argc, const char **argv, const char *prefix) opts.scheduler = resolve_scheduler(opts.scheduler); validate_scheduler(opts.scheduler); - if (maintenance_register()) + if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL)) warning(_("failed to add repo to global config")); return update_background_schedule(&opts, 1); } -static int maintenance_stop(void) +static const char *const builtin_maintenance_stop_usage[] = { + "git maintenance stop", + NULL +}; + +static int maintenance_stop(int argc, const char **argv, const char *prefix) { + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_stop_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_stop_usage, options); return update_background_schedule(NULL, 0); } -static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]"); +static const char * const builtin_maintenance_usage[] = { + N_("git maintenance <subcommand> [<options>]"), + NULL, +}; int cmd_maintenance(int argc, const char **argv, const char *prefix) { - if (argc < 2 || - (argc == 2 && !strcmp(argv[1], "-h"))) - usage(builtin_maintenance_usage); - - if (!strcmp(argv[1], "run")) - return maintenance_run(argc - 1, argv + 1, prefix); - if (!strcmp(argv[1], "start")) - return maintenance_start(argc - 1, argv + 1, prefix); - if (!strcmp(argv[1], "stop")) - return maintenance_stop(); - if (!strcmp(argv[1], "register")) - return maintenance_register(); - if (!strcmp(argv[1], "unregister")) - return maintenance_unregister(); - - die(_("invalid subcommand: %s"), argv[1]); + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_maintenance_options[] = { + OPT_SUBCOMMAND("run", &fn, maintenance_run), + OPT_SUBCOMMAND("start", &fn, maintenance_start), + OPT_SUBCOMMAND("stop", &fn, maintenance_stop), + OPT_SUBCOMMAND("register", &fn, maintenance_register), + OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, builtin_maintenance_options, + builtin_maintenance_usage, 0); + return fn(argc, argv, prefix); } diff --git a/builtin/grep.c b/builtin/grep.c index bcb07ea7f7..5fa927d4e2 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -458,6 +458,33 @@ static int grep_submodule(struct grep_opt *opt, * subrepo's odbs to the in-memory alternates list. */ obj_read_lock(); + + /* + * NEEDSWORK: when reading a submodule, the sparsity settings in the + * superproject are incorrectly forgotten or misused. For example: + * + * 1. "command_requires_full_index" + * When this setting is turned on for `grep`, only the superproject + * knows it. All the submodules are read with their own configs + * and get prepare_repo_settings()'d. Therefore, these submodules + * "forget" the sparse-index feature switch. As a result, the index + * of these submodules are expanded unexpectedly. + * + * 2. "core_apply_sparse_checkout" + * When running `grep` in the superproject, this setting is + * populated using the superproject's configs. However, once + * initialized, this config is globally accessible and is read by + * prepare_repo_settings() for the submodules. For instance, if a + * submodule is using a sparse-checkout, however, the superproject + * is not, the result is that the config from the superproject will + * dictate the behavior for the submodule, making it "forget" its + * sparse-checkout state. + * + * 3. "core_sparse_checkout_cone" + * ditto. + * + * Note that this list is not exhaustive. + */ repo_read_gitmodules(subrepo, 0); /* @@ -520,8 +547,6 @@ static int grep_cache(struct grep_opt *opt, if (repo_read_index(repo) < 0) die(_("index file corrupt")); - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(repo->index); for (nr = 0; nr < repo->index->cache_nr; nr++) { const struct cache_entry *ce = repo->index->cache[nr]; @@ -530,8 +555,20 @@ static int grep_cache(struct grep_opt *opt, strbuf_setlen(&name, name_base_len); strbuf_addstr(&name, ce->name); + if (S_ISSPARSEDIR(ce->ce_mode)) { + enum object_type type; + struct tree_desc tree; + void *data; + unsigned long size; + + data = read_object_file(&ce->oid, &type, &size); + init_tree_desc(&tree, data, size); - if (S_ISREG(ce->ce_mode) && + hit |= grep_tree(opt, pathspec, &tree, &name, 0, 0); + strbuf_setlen(&name, name_base_len); + strbuf_addstr(&name, ce->name); + free(data); + } else if (S_ISREG(ce->ce_mode) && match_pathspec(repo->index, pathspec, name.buf, name.len, 0, NULL, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { @@ -961,6 +998,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL_F(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)"), PARSE_OPT_NOCOMPLETE), + OPT_INTEGER('m', "max-count", &opt.max_count, + N_("maximum number of results per file")), OPT_END() }; grep_prefix = prefix; @@ -982,6 +1021,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION); + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } + if (use_index && !startup_info->have_repository) { int fallback = 0; git_config_get_bool("grep.fallbacktonoindex", &fallback); @@ -1101,6 +1145,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (recurse_submodules && untracked) die(_("--untracked not supported with --recurse-submodules")); + /* + * Optimize out the case where the amount of matches is limited to zero. + * We do this to keep results consistent with GNU grep(1). + */ + if (opt.max_count == 0) + return 1; + if (show_in_pager) { if (num_threads > 1) warning(_("invalid option combination, ignoring --threads")); diff --git a/builtin/help.c b/builtin/help.c index 222f994f86..6f2796f211 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -43,6 +43,8 @@ static enum help_action { HELP_ACTION_ALL = 1, HELP_ACTION_GUIDES, HELP_ACTION_CONFIG, + HELP_ACTION_USER_INTERFACES, + HELP_ACTION_DEVELOPER_INTERFACES, HELP_ACTION_CONFIG_FOR_COMPLETION, HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, } cmd_mode; @@ -69,6 +71,12 @@ static struct option builtin_help_options[] = { OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"), HELP_ACTION_GUIDES), + OPT_CMDMODE(0, "user-interfaces", &cmd_mode, + N_("print list of user-facing repository, command and file interfaces"), + HELP_ACTION_USER_INTERFACES), + OPT_CMDMODE(0, "developer-interfaces", &cmd_mode, + N_("print list of file formats, protocols and other developer interfaces"), + HELP_ACTION_DEVELOPER_INTERFACES), OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"), HELP_ACTION_CONFIG), OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "", @@ -81,9 +89,11 @@ static struct option builtin_help_options[] = { static const char * const builtin_help_usage[] = { "git help [-a|--all] [--[no-]verbose]] [--[no-]external-commands] [--[no-]aliases]", - N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>]"), + N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>|<doc>]"), "git help [-g|--guides]", "git help [-c|--config]", + "git help [--user-interfaces]", + "git help [--developer-interfaces]", NULL }; @@ -430,6 +440,8 @@ static const char *cmd_to_page(const char *git_cmd) return git_cmd; else if (is_git_command(git_cmd)) return xstrfmt("git-%s", git_cmd); + else if (!strcmp("scalar", git_cmd)) + return xstrdup(git_cmd); else return xstrfmt("git%s", git_cmd); } @@ -654,6 +666,14 @@ int cmd_help(int argc, const char **argv, const char *prefix) opt_mode_usage(argc, "--config-for-completion", help_format); list_config_help(SHOW_CONFIG_VARS); return 0; + case HELP_ACTION_USER_INTERFACES: + opt_mode_usage(argc, "--user-interfaces", help_format); + list_user_interfaces_help(); + return 0; + case HELP_ACTION_DEVELOPER_INTERFACES: + opt_mode_usage(argc, "--developer-interfaces", help_format); + list_developer_interfaces_help(); + return 0; case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: opt_mode_usage(argc, "--config-sections-for-completion", help_format); diff --git a/builtin/hook.c b/builtin/hook.c index 54e5c6ec93..b6530d189a 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -67,18 +67,14 @@ usage: int cmd_hook(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option builtin_hook_options[] = { + OPT_SUBCOMMAND("run", &fn, run), OPT_END(), }; argc = parse_options(argc, argv, NULL, builtin_hook_options, - builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (!argc) - goto usage; + builtin_hook_usage, 0); - if (!strcmp(argv[0], "run")) - return run(argc, argv, prefix); - -usage: - usage_with_options(builtin_hook_usage, builtin_hook_options); + return fn(argc, argv, prefix); } diff --git a/builtin/log.c b/builtin/log.c index 88a5e98875..ee19dc5d45 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -52,6 +52,7 @@ static int default_encode_email_headers = 1; static int decoration_style; static int decoration_given; static int use_mailmap_config = 1; +static unsigned int force_in_body_from; static const char *fmt_patch_subject_prefix = "PATCH"; static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; static const char *fmt_pretty; @@ -101,6 +102,20 @@ static int parse_decoration_style(const char *value) return -1; } +static int use_default_decoration_filter = 1; +static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; +static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; +static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; + +static int clear_decorations_callback(const struct option *opt, + const char *arg, int unset) +{ + string_list_clear(&decorate_refs_include, 0); + string_list_clear(&decorate_refs_exclude, 0); + use_default_decoration_filter = 0; + return 0; +} + static int decorate_callback(const struct option *opt, const char *arg, int unset) { if (unset) @@ -162,18 +177,61 @@ static void cmd_log_init_defaults(struct rev_info *rev) parse_date_format(default_date_mode, &rev->date_mode); } +static void set_default_decoration_filter(struct decoration_filter *decoration_filter) +{ + int i; + char *value = NULL; + struct string_list *include = decoration_filter->include_ref_pattern; + const struct string_list *config_exclude = + git_config_get_value_multi("log.excludeDecoration"); + + if (config_exclude) { + struct string_list_item *item; + for_each_string_list_item(item, config_exclude) + string_list_append(decoration_filter->exclude_ref_config_pattern, + item->string); + } + + /* + * By default, decorate_all is disabled. Enable it if + * log.initialDecorationSet=all. Don't ever disable it by config, + * since the command-line takes precedent. + */ + if (use_default_decoration_filter && + !git_config_get_string("log.initialdecorationset", &value) && + !strcmp("all", value)) + use_default_decoration_filter = 0; + free(value); + + if (!use_default_decoration_filter || + decoration_filter->exclude_ref_pattern->nr || + decoration_filter->include_ref_pattern->nr || + decoration_filter->exclude_ref_config_pattern->nr) + return; + + /* + * No command-line or config options were given, so + * populate with sensible defaults. + */ + for (i = 0; i < ARRAY_SIZE(ref_namespace); i++) { + if (!ref_namespace[i].decoration) + continue; + + string_list_append(include, ref_namespace[i].ref); + } +} + static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { struct userformat_want w; int quiet = 0, source = 0, mailmap; static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; - static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; - static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; - static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; - struct decoration_filter decoration_filter = {&decorate_refs_include, - &decorate_refs_exclude, - &decorate_refs_exclude_config}; + struct decoration_filter decoration_filter = { + .exclude_ref_pattern = &decorate_refs_exclude, + .include_ref_pattern = &decorate_refs_include, + .exclude_ref_config_pattern = &decorate_refs_exclude_config, + }; static struct revision_sources revision_sources; const struct option builtin_log_options[] = { @@ -181,6 +239,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("use mail map file")), OPT_ALIAS(0, "mailmap", "use-mailmap"), + OPT_CALLBACK_F(0, "clear-decorations", NULL, NULL, + N_("clear all previously-defined decoration filters"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + clear_decorations_callback), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match <pattern>")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, @@ -199,7 +261,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); if (quiet) @@ -265,16 +327,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } if (decoration_style || rev->simplify_by_decoration) { - const struct string_list *config_exclude = - repo_config_get_value_multi(the_repository, - "log.excludeDecoration"); - - if (config_exclude) { - struct string_list_item *item; - for_each_string_list_item(item, config_exclude) - string_list_append(&decorate_refs_exclude_config, - item->string); - } + set_default_decoration_filter(&decoration_filter); if (decoration_style) rev->show_decorations = 1; @@ -645,9 +698,10 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev) return 0; } -static int show_tree_object(const struct object_id *oid, - struct strbuf *base, - const char *pathname, unsigned mode, void *context) +static int show_tree_object(const struct object_id *oid UNUSED, + struct strbuf *base UNUSED, + const char *pathname, unsigned mode, + void *context) { FILE *file = context; fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); @@ -668,10 +722,10 @@ static void show_setup_revisions_tweak(struct rev_info *rev, int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; - struct object_array_entry *objects; + unsigned int i; struct setup_revision_opt opt; struct pathspec match_all; - int i, count, ret = 0; + int ret = 0; init_log_defaults(); git_config(git_log_config, NULL); @@ -698,12 +752,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (!rev.no_walk) return cmd_log_deinit(cmd_log_walk(&rev), &rev); - count = rev.pending.nr; - objects = rev.pending.objects; rev.diffopt.no_free = 1; - for (i = 0; i < count && !ret; i++) { - struct object *o = objects[i].item; - const char *name = objects[i].name; + for (i = 0; i < rev.pending.nr && !ret; i++) { + struct object *o = rev.pending.objects[i].item; + const char *name = rev.pending.objects[i].name; switch (o->type) { case OBJ_BLOB: ret = show_blob_object(&o->oid, &rev, name); @@ -726,7 +778,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (!o) ret = error(_("could not read object %s"), oid_to_hex(oid)); - objects[i].item = o; + rev.pending.objects[i].item = o; i--; break; } @@ -743,11 +795,24 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.shown_one = 1; break; case OBJ_COMMIT: - rev.pending.nr = rev.pending.alloc = 0; - rev.pending.objects = NULL; + { + struct object_array old; + struct object_array blank = OBJECT_ARRAY_INIT; + + memcpy(&old, &rev.pending, sizeof(old)); + memcpy(&rev.pending, &blank, sizeof(rev.pending)); + add_object_array(o, name, &rev.pending); ret = cmd_log_walk_no_free(&rev); + + /* + * No need for + * object_array_clear(&pending). It was + * cleared already in prepare_revision_walk() + */ + memcpy(&rev.pending, &old, sizeof(rev.pending)); break; + } default: ret = error(_("unknown type: %d"), o->type); } @@ -995,6 +1060,10 @@ static int git_format_config(const char *var, const char *value, void *cb) from = NULL; return 0; } + if (!strcmp(var, "format.forceinbodyfrom")) { + force_in_body_from = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "format.notes")) { int b = git_parse_maybe_bool(value); if (b < 0) @@ -1886,6 +1955,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("show changes against <refspec> in cover letter or single patch")), OPT_INTEGER(0, "creation-factor", &creation_factor, N_("percentage by which creation is weighted")), + OPT_BOOL(0, "force-in-body-from", &force_in_body_from, + N_("show in-body From: even if identical to the e-mail header")), OPT_END() }; @@ -1926,9 +1997,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) */ argc = parse_options(argc, argv, prefix, builtin_format_patch_options, builtin_format_patch_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); + rev.force_in_body_from = force_in_body_from; + /* Make sure "0000-$sub.patch" gives non-negative length for $sub */ if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index e791b65e7e..4cf8a23648 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -11,6 +11,7 @@ #include "quote.h" #include "dir.h" #include "builtin.h" +#include "strbuf.h" #include "tree.h" #include "cache-tree.h" #include "parse-options.h" @@ -48,6 +49,7 @@ static char *ps_matched; static const char *with_tree; static int exc_given; static int exclude_args; +static const char *format; static const char *tag_cached = ""; static const char *tag_unmerged = ""; @@ -85,6 +87,16 @@ static void write_name(const char *name) stdout, line_terminator); } +static void write_name_to_buf(struct strbuf *sb, const char *name) +{ + const char *rel = relative_path(name, prefix_len ? prefix : NULL, sb); + + if (line_terminator) + quote_c_style(rel, sb, NULL, 0); + else + strbuf_addstr(sb, rel); +} + static const char *get_tag(const struct cache_entry *ce, const char *tag) { static char alttag[4]; @@ -222,6 +234,73 @@ static void show_submodule(struct repository *superproject, repo_clear(&subrepo); } +struct show_index_data { + const char *pathname; + struct index_state *istate; + const struct cache_entry *ce; +}; + +static size_t expand_show_index(struct strbuf *sb, const char *start, + void *context) +{ + struct show_index_data *data = context; + const char *end; + const char *p; + size_t len = strbuf_expand_literal_cb(sb, start, NULL); + struct stat st; + + if (len) + return len; + if (*start != '(') + die(_("bad ls-files format: element '%s' " + "does not start with '('"), start); + + end = strchr(start + 1, ')'); + if (!end) + die(_("bad ls-files format: element '%s' " + "does not end in ')'"), start); + + len = end - start + 1; + if (skip_prefix(start, "(objectmode)", &p)) + strbuf_addf(sb, "%06o", data->ce->ce_mode); + else if (skip_prefix(start, "(objectname)", &p)) + strbuf_add_unique_abbrev(sb, &data->ce->oid, abbrev); + else if (skip_prefix(start, "(stage)", &p)) + strbuf_addf(sb, "%d", ce_stage(data->ce)); + else if (skip_prefix(start, "(eolinfo:index)", &p)) + strbuf_addstr(sb, S_ISREG(data->ce->ce_mode) ? + get_cached_convert_stats_ascii(data->istate, + data->ce->name) : ""); + else if (skip_prefix(start, "(eolinfo:worktree)", &p)) + strbuf_addstr(sb, !lstat(data->pathname, &st) && + S_ISREG(st.st_mode) ? + get_wt_convert_stats_ascii(data->pathname) : ""); + else if (skip_prefix(start, "(eolattr)", &p)) + strbuf_addstr(sb, get_convert_attr_ascii(data->istate, + data->pathname)); + else if (skip_prefix(start, "(path)", &p)) + write_name_to_buf(sb, data->pathname); + else + die(_("bad ls-files format: %%%.*s"), (int)len, start); + + return len; +} + +static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce, + const char *format, const char *fullname) { + struct show_index_data data = { + .pathname = fullname, + .istate = repo->index, + .ce = ce, + }; + struct strbuf sb = STRBUF_INIT; + + strbuf_expand(&sb, format, expand_show_index, &data); + strbuf_addch(&sb, line_terminator); + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); +} + static void show_ce(struct repository *repo, struct dir_struct *dir, const struct cache_entry *ce, const char *fullname, const char *tag) @@ -236,6 +315,12 @@ static void show_ce(struct repository *repo, struct dir_struct *dir, max_prefix_len, ps_matched, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { + if (format) { + show_ce_fmt(repo, ce, format, fullname); + print_debug(ce); + return; + } + tag = get_tag(ce, tag); if (!show_stage) { @@ -675,6 +760,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("suppress duplicate entries")), OPT_BOOL(0, "sparse", &show_sparse_dirs, N_("show sparse directories in the presence of a sparse index")), + OPT_STRING_F(0, "format", &format, N_("format"), + N_("format to use for the output"), + PARSE_OPT_NONEG), OPT_END() }; int ret = 0; @@ -699,6 +787,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) for (i = 0; i < exclude_list.nr; i++) { add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args); } + + if (format && (show_stage || show_others || show_killed || + show_resolve_undo || skipping_duplicates || show_eol || show_tag)) + usage_msg_opt(_("--format cannot be used with -s, -o, -k, -t, " + "--resolve-undo, --deduplicate, --eol"), + ls_files_usage, builtin_ls_files_options); + if (show_tag || show_valid_bit || show_fsmonitor_bit) { tag_cached = "H "; tag_unmerged = "M "; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index e279be8bb6..c3ea09281a 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -142,7 +142,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname } static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, void *context UNUSED) { size_t baselen; int recurse = 0; @@ -213,7 +213,7 @@ static void show_tree_common_default_long(struct strbuf *base, static int show_tree_default(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, - void *context) + void *context UNUSED) { int early; int recurse; @@ -230,7 +230,8 @@ static int show_tree_default(const struct object_id *oid, struct strbuf *base, } static int show_tree_long(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; @@ -259,7 +260,8 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base, } static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; @@ -279,7 +281,8 @@ static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, } static int show_tree_object(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; diff --git a/builtin/merge-file.c b/builtin/merge-file.c index e695867ee5..c923bbf2ab 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -25,10 +25,10 @@ static int label_cb(const struct option *opt, const char *arg, int unset) int cmd_merge_file(int argc, const char **argv, const char *prefix) { - const char *names[3] = { NULL, NULL, NULL }; - mmfile_t mmfs[3]; - mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{0}}; + const char *names[3] = { 0 }; + mmfile_t mmfs[3] = { 0 }; + mmbuffer_t result = { 0 }; + xmparam_t xmp = { 0 }; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; struct option options[] = { @@ -71,21 +71,24 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) for (i = 0; i < 3; i++) { char *fname; - int ret; + mmfile_t *mmf = mmfs + i; if (!names[i]) names[i] = argv[i]; fname = prefix_filename(prefix, argv[i]); - ret = read_mmfile(mmfs + i, fname); + + if (read_mmfile(mmf, fname)) + ret = -1; + else if (mmf->size > MAX_XDIFF_SIZE || + buffer_is_binary(mmf->ptr, mmf->size)) + ret = error("Cannot merge binary files: %s", + argv[i]); + free(fname); if (ret) - return -1; + goto cleanup; - if (mmfs[i].size > MAX_XDIFF_SIZE || - buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) - return error("Cannot merge binary files: %s", - argv[i]); } xmp.ancestor = names[1]; @@ -93,9 +96,6 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmp.file2 = names[2]; ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result); - for (i = 0; i < 3; i++) - free(mmfs[i].ptr); - if (ret >= 0) { const char *filename = argv[0]; char *fpath = prefix_filename(prefix, argv[0]); @@ -116,5 +116,9 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret > 127) ret = 127; +cleanup: + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + return ret; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 5dc94d6f88..ae5782917b 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -2,13 +2,18 @@ #include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "help.h" +#include "commit-reach.h" +#include "merge-ort.h" #include "object-store.h" +#include "parse-options.h" #include "repository.h" #include "blob.h" #include "exec-cmd.h" #include "merge-blobs.h" +#include "quote.h" -static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; +static int line_termination = '\n'; struct merge_list { struct merge_list *next; @@ -28,7 +33,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees(struct tree_desc t[3], const char *base); +static void trivial_merge_trees(struct tree_desc t[3], const char *base); static const char *explanation(struct merge_list *entry) { @@ -225,7 +230,7 @@ static void unresolved_directory(const struct traverse_info *info, buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2)); #undef ENTRY_OID - merge_trees(t, newbase); + trivial_merge_trees(t, newbase); free(buf0); free(buf1); @@ -342,7 +347,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees(struct tree_desc t[3], const char *base) +static void trivial_merge_trees(struct tree_desc t[3], const char *base) { struct traverse_info info; @@ -366,19 +371,18 @@ static void *get_tree_descriptor(struct repository *r, return buf; } -int cmd_merge_tree(int argc, const char **argv, const char *prefix) +static int trivial_merge(const char *base, + const char *branch1, + const char *branch2) { struct repository *r = the_repository; struct tree_desc t[3]; void *buf1, *buf2, *buf3; - if (argc != 4) - usage(merge_tree_usage); - - buf1 = get_tree_descriptor(r, t+0, argv[1]); - buf2 = get_tree_descriptor(r, t+1, argv[2]); - buf3 = get_tree_descriptor(r, t+2, argv[3]); - merge_trees(t, ""); + buf1 = get_tree_descriptor(r, t+0, base); + buf2 = get_tree_descriptor(r, t+1, branch1); + buf3 = get_tree_descriptor(r, t+2, branch2); + trivial_merge_trees(t, ""); free(buf1); free(buf2); free(buf3); @@ -386,3 +390,162 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) show_result(); return 0; } + +enum mode { + MODE_UNKNOWN, + MODE_TRIVIAL, + MODE_REAL, +}; + +struct merge_tree_options { + int mode; + int allow_unrelated_histories; + int show_messages; + int name_only; +}; + +static int real_merge(struct merge_tree_options *o, + const char *branch1, const char *branch2, + const char *prefix) +{ + struct commit *parent1, *parent2; + struct commit_list *merge_bases = NULL; + struct merge_options opt; + struct merge_result result = { 0 }; + + parent1 = get_merge_parent(branch1); + if (!parent1) + help_unknown_ref(branch1, "merge-tree", + _("not something we can merge")); + + parent2 = get_merge_parent(branch2); + if (!parent2) + help_unknown_ref(branch2, "merge-tree", + _("not something we can merge")); + + init_merge_options(&opt, the_repository); + + opt.show_rename_progress = 0; + + opt.branch1 = branch1; + opt.branch2 = branch2; + + /* + * Get the merge bases, in reverse order; see comment above + * merge_incore_recursive in merge-ort.h + */ + merge_bases = get_merge_bases(parent1, parent2); + if (!merge_bases && !o->allow_unrelated_histories) + die(_("refusing to merge unrelated histories")); + merge_bases = reverse_commit_list(merge_bases); + + merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result); + if (result.clean < 0) + die(_("failure to merge")); + + if (o->show_messages == -1) + o->show_messages = !result.clean; + + printf("%s%c", oid_to_hex(&result.tree->object.oid), line_termination); + if (!result.clean) { + struct string_list conflicted_files = STRING_LIST_INIT_NODUP; + const char *last = NULL; + int i; + + merge_get_conflicted_files(&result, &conflicted_files); + for (i = 0; i < conflicted_files.nr; i++) { + const char *name = conflicted_files.items[i].string; + struct stage_info *c = conflicted_files.items[i].util; + if (!o->name_only) + printf("%06o %s %d\t", + c->mode, oid_to_hex(&c->oid), c->stage); + else if (last && !strcmp(last, name)) + continue; + write_name_quoted_relative( + name, prefix, stdout, line_termination); + last = name; + } + string_list_clear(&conflicted_files, 1); + } + if (o->show_messages) { + putchar(line_termination); + merge_display_update_messages(&opt, line_termination == '\0', + &result); + } + merge_finalize(&opt, &result); + return !result.clean; /* result.clean < 0 handled above */ +} + +int cmd_merge_tree(int argc, const char **argv, const char *prefix) +{ + struct merge_tree_options o = { .show_messages = -1 }; + int expected_remaining_argc; + int original_argc; + + const char * const merge_tree_usage[] = { + N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"), + N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"), + NULL + }; + struct option mt_options[] = { + OPT_CMDMODE(0, "write-tree", &o.mode, + N_("do a real merge instead of a trivial merge"), + MODE_REAL), + OPT_CMDMODE(0, "trivial-merge", &o.mode, + N_("do a trivial merge only"), MODE_TRIVIAL), + OPT_BOOL(0, "messages", &o.show_messages, + N_("also show informational/conflict messages")), + OPT_SET_INT('z', NULL, &line_termination, + N_("separate paths with the NUL character"), '\0'), + OPT_BOOL_F(0, "name-only", + &o.name_only, + N_("list filenames without modes/oids/stages"), + PARSE_OPT_NONEG), + OPT_BOOL_F(0, "allow-unrelated-histories", + &o.allow_unrelated_histories, + N_("allow merging unrelated histories"), + PARSE_OPT_NONEG), + OPT_END() + }; + + /* Parse arguments */ + original_argc = argc - 1; /* ignoring argv[0] */ + argc = parse_options(argc, argv, prefix, mt_options, + merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION); + switch (o.mode) { + default: + BUG("unexpected command mode %d", o.mode); + case MODE_UNKNOWN: + switch (argc) { + default: + usage_with_options(merge_tree_usage, mt_options); + case 2: + o.mode = MODE_REAL; + break; + case 3: + o.mode = MODE_TRIVIAL; + break; + } + expected_remaining_argc = argc; + break; + case MODE_REAL: + expected_remaining_argc = 2; + break; + case MODE_TRIVIAL: + expected_remaining_argc = 3; + /* Removal of `--trivial-merge` is expected */ + original_argc--; + break; + } + if (o.mode == MODE_TRIVIAL && argc < original_argc) + die(_("--trivial-merge is incompatible with all other options")); + + if (argc != expected_remaining_argc) + usage_with_options(merge_tree_usage, mt_options); + + /* Do the relevant type of merge */ + if (o.mode == MODE_REAL) + return real_merge(&o, argv[0], argv[1], prefix); + else + return trivial_merge(argv[0], argv[1], argv[2]); +} diff --git a/builtin/merge.c b/builtin/merge.c index fcd87a2a09..5900b81729 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -313,8 +313,16 @@ static int save_state(struct object_id *stash) int len; struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buffer = STRBUF_INIT; + struct lock_file lock_file = LOCK_INIT; + int fd; int rc = -1; + fd = repo_hold_locked_index(the_repository, &lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + repo_update_index_if_able(the_repository, &lock_file); + rollback_lock_file(&lock_file); + strvec_pushl(&cp.args, "stash", "create", NULL); cp.out = -1; cp.git_cmd = 1; @@ -375,24 +383,26 @@ static void reset_hard(const struct object_id *oid, int verbose) static void restore_state(const struct object_id *head, const struct object_id *stash) { - struct strbuf sb = STRBUF_INIT; - const char *args[] = { "stash", "apply", NULL, NULL }; - - if (is_null_oid(stash)) - return; + struct strvec args = STRVEC_INIT; reset_hard(head, 1); - args[2] = oid_to_hex(stash); + if (is_null_oid(stash)) + goto refresh_cache; + + strvec_pushl(&args, "stash", "apply", "--index", "--quiet", NULL); + strvec_push(&args, oid_to_hex(stash)); /* * It is OK to ignore error here, for example when there was * nothing to restore. */ - run_command_v_opt(args, RUN_GIT_CMD); + run_command_v_opt(args.v, RUN_GIT_CMD); + strvec_clear(&args); - strbuf_release(&sb); - refresh_cache(REFRESH_QUIET); +refresh_cache: + if (discard_cache() < 0 || read_cache() < 0) + die(_("could not read index")); } /* This is called when no merge was necessary. */ @@ -503,7 +513,6 @@ static void merge_name(const char *remote, struct strbuf *msg) { struct commit *remote_head; struct object_id branch_head; - struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; struct merge_remote_desc *desc; const char *ptr; @@ -591,7 +600,6 @@ static void merge_name(const char *remote, struct strbuf *msg) oid_to_hex(&remote_head->object.oid), remote); cleanup: free(found_ref); - strbuf_release(&buf); strbuf_release(&bname); } @@ -759,8 +767,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, else clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); - if (clean < 0) - exit(128); + if (clean < 0) { + rollback_lock_file(&lock); + return 2; + } if (write_locked_index(&the_index, &lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("unable to write %s"), get_index_file()); @@ -1604,6 +1614,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ refresh_cache(REFRESH_QUIET); if (allow_trivial && fast_forward != FF_ONLY) { + /* + * Must first ensure that index matches HEAD before + * attempting a trivial merge. + */ + struct tree *head_tree = get_commit_tree(head_commit); + struct strbuf sb = STRBUF_INIT; + + if (repo_index_has_changes(the_repository, head_tree, + &sb)) { + error(_("Your local changes to the following files would be overwritten by merge:\n %s"), + sb.buf); + strbuf_release(&sb); + return 2; + } + /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); @@ -1660,15 +1685,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * tree in the index -- this means that the index must be in * sync with the head commit. The strategies are responsible * to ensure this. + * + * Stash away the local changes so that we can try more than one + * and/or recover from merge strategies bailing while leaving the + * index and working tree polluted. */ - if (use_strategies_nr == 1 || - /* - * Stash away the local changes so that we can try more than one. - */ - save_state(&stash)) + if (save_state(&stash)) oidclr(&stash); - for (i = 0; !merge_was_ok && i < use_strategies_nr; i++) { + for (i = 0; i < use_strategies_nr; i++) { int ret, cnt; if (i) { printf(_("Rewinding the tree to pristine...\n")); @@ -1683,7 +1708,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ wt_strategy = use_strategies[i]->name; - ret = try_merge_strategy(use_strategies[i]->name, + ret = try_merge_strategy(wt_strategy, common, remoteheads, head_commit); /* @@ -1693,16 +1718,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ if (ret < 2) { if (!ret) { - if (option_commit) { - /* Automerge succeeded. */ - automerge_was_ok = 1; - break; - } + /* + * This strategy worked; no point in trying + * another. + */ merge_was_ok = 1; + best_strategy = wt_strategy; + break; } cnt = (use_strategies_nr > 1) ? evaluate_result() : 0; if (best_cnt <= 0 || cnt <= best_cnt) { - best_strategy = use_strategies[i]->name; + best_strategy = wt_strategy; best_cnt = cnt; } } @@ -1712,7 +1738,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If we have a resulting tree, that means the strategy module * auto resolved the merge cleanly. */ - if (automerge_was_ok) { + if (merge_was_ok && option_commit) { + automerge_was_ok = 1; ret = finish_automerge(head_commit, head_subsumed, common, remoteheads, &result_tree, wt_strategy); diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 5edbb7fe86..9a18a82b05 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -56,11 +56,12 @@ static struct opts_multi_pack_index { static int parse_object_dir(const struct option *opt, const char *arg, int unset) { - free(opts.object_dir); + char **value = opt->value; + free(*value); if (unset) - opts.object_dir = xstrdup(get_object_directory()); + *value = xstrdup(get_object_directory()); else - opts.object_dir = real_pathdup(arg, 1); + *value = real_pathdup(arg, 1); return 0; } @@ -78,7 +79,7 @@ static struct option *add_common_options(struct option *prev) } static int git_multi_pack_index_write_config(const char *var, const char *value, - void *cb) + void *cb UNUSED) { if (!strcmp(var, "pack.writebitmaphashcache")) { if (git_config_bool(var, value)) @@ -87,6 +88,13 @@ static int git_multi_pack_index_write_config(const char *var, const char *value, opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE; } + if (!strcmp(var, "pack.writebitmaplookuptable")) { + if (git_config_bool(var, value)) + opts.flags |= MIDX_WRITE_BITMAP_LOOKUP_TABLE; + else + opts.flags &= ~MIDX_WRITE_BITMAP_LOOKUP_TABLE; + } + /* * We should never make a fall-back call to 'git_default_config', since * this was already called in 'cmd_multi_pack_index()'. @@ -104,7 +112,8 @@ static void read_packs_from_stdin(struct string_list *to) strbuf_release(&buf); } -static int cmd_multi_pack_index_write(int argc, const char **argv) +static int cmd_multi_pack_index_write(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_write_options[] = { @@ -132,9 +141,9 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_write_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_write_usage, options); @@ -160,7 +169,8 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) opts.refs_snapshot, opts.flags); } -static int cmd_multi_pack_index_verify(int argc, const char **argv) +static int cmd_multi_pack_index_verify(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_verify_options[] = { @@ -174,9 +184,9 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_verify_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_verify_usage, options); @@ -186,7 +196,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) return verify_midx_file(the_repository, opts.object_dir, opts.flags); } -static int cmd_multi_pack_index_expire(int argc, const char **argv) +static int cmd_multi_pack_index_expire(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_expire_options[] = { @@ -200,9 +211,9 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_expire_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_expire_usage, options); @@ -212,7 +223,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv) return expire_midx_packs(the_repository, opts.object_dir, opts.flags); } -static int cmd_multi_pack_index_repack(int argc, const char **argv) +static int cmd_multi_pack_index_repack(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_repack_options[] = { @@ -229,10 +241,10 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_repack_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_repack_usage, options); @@ -247,7 +259,15 @@ int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { int res; - struct option *builtin_multi_pack_index_options = common_opts; + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_multi_pack_index_options[] = { + OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack), + OPT_SUBCOMMAND("write", &fn, cmd_multi_pack_index_write), + OPT_SUBCOMMAND("verify", &fn, cmd_multi_pack_index_verify), + OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire), + OPT_END(), + }; + struct option *options = parse_options_concat(builtin_multi_pack_index_options, common_opts); git_config(git_default_config, NULL); @@ -256,31 +276,12 @@ int cmd_multi_pack_index(int argc, const char **argv, the_repository->objects->odb) opts.object_dir = xstrdup(the_repository->objects->odb->path); - argc = parse_options(argc, argv, prefix, - builtin_multi_pack_index_options, - builtin_multi_pack_index_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - - if (!argc) - goto usage; - - if (!strcmp(argv[0], "repack")) - res = cmd_multi_pack_index_repack(argc, argv); - else if (!strcmp(argv[0], "write")) - res = cmd_multi_pack_index_write(argc, argv); - else if (!strcmp(argv[0], "verify")) - res = cmd_multi_pack_index_verify(argc, argv); - else if (!strcmp(argv[0], "expire")) - res = cmd_multi_pack_index_expire(argc, argv); - else { - error(_("unrecognized subcommand: %s"), argv[0]); - goto usage; - } + argc = parse_options(argc, argv, prefix, options, + builtin_multi_pack_index_usage, 0); + FREE_AND_NULL(options); + + res = fn(argc, argv, prefix); free(opts.object_dir); return res; - -usage: - usage_with_options(builtin_multi_pack_index_usage, - builtin_multi_pack_index_options); } diff --git a/builtin/mv.c b/builtin/mv.c index 83a465ba83..3413ad1c9b 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -13,12 +13,20 @@ #include "string-list.h" #include "parse-options.h" #include "submodule.h" +#include "entry.h" static const char * const builtin_mv_usage[] = { N_("git mv [<options>] <source>... <destination>"), NULL }; +enum update_mode { + WORKING_DIRECTORY = (1 << 1), + INDEX = (1 << 2), + SPARSE = (1 << 3), + SKIP_WORKTREE_DIR = (1 << 4), +}; + #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 @@ -63,7 +71,7 @@ static const char **internal_prefix_pathspec(const char *prefix, static const char *add_slash(const char *path) { size_t len = strlen(path); - if (path[len - 1] != '/') { + if (len && path[len - 1] != '/') { char *with_slash = xmalloc(st_add(len, 2)); memcpy(with_slash, path, len); with_slash[len++] = '/'; @@ -115,6 +123,39 @@ static int index_range_of_same_dir(const char *src, int length, return last - first; } +/* + * Given the path of a directory that does not exist on-disk, check whether the + * directory contains any entries in the index with the SKIP_WORKTREE flag + * enabled. + * Return 1 if such index entries exist. + * Return 0 otherwise. + */ +static int empty_dir_has_sparse_contents(const char *name) +{ + int ret = 0; + const char *with_slash = add_slash(name); + int length = strlen(with_slash); + + int pos = cache_name_pos(with_slash, length); + const struct cache_entry *ce; + + if (pos < 0) { + pos = -pos - 1; + if (pos >= the_index.cache_nr) + goto free_return; + ce = active_cache[pos]; + if (strncmp(with_slash, ce->name, length)) + goto free_return; + if (ce_skip_worktree(ce)) + ret = 1; + } + +free_return: + if (with_slash != name) + free((char *)with_slash); + return ret; +} + int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; @@ -129,12 +170,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; + const char *dst_w_slash; + const char **src_dir = NULL; + int src_dir_nr = 0, src_dir_alloc = 0; + struct strbuf a_src_dir = STRBUF_INIT; + enum update_mode *modes, dst_mode = 0; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; struct lock_file lock_file = LOCK_INIT; struct cache_entry *ce; struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; + struct string_list dirty_paths = STRING_LIST_INIT_NODUP; git_config(git_default_config, NULL); @@ -148,7 +194,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) die(_("index file corrupt")); source = internal_prefix_pathspec(prefix, argv, argc, 0); - modes = xcalloc(argc, sizeof(enum update_mode)); + CALLOC_ARRAY(modes, argc); + /* * Keep trailing slash, needed to let * "git mv file no-such-dir/" error out, except in the case @@ -158,6 +205,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); + dst_w_slash = add_slash(dest_path[0]); submodule_gitfile = xcalloc(argc, sizeof(char *)); if (dest_path[0][0] == '\0') @@ -165,18 +213,37 @@ int cmd_mv(int argc, const char **argv, const char *prefix) destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { - dest_path[0] = add_slash(dest_path[0]); - destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); } else { - if (argc != 1) + if (!path_in_sparse_checkout(dst_w_slash, &the_index) && + empty_dir_has_sparse_contents(dst_w_slash)) { + destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); + dst_mode = SKIP_WORKTREE_DIR; + } else if (argc != 1) { die(_("destination '%s' is not a directory"), dest_path[0]); - destination = dest_path; + } else { + destination = dest_path; + /* + * <destination> is a file outside of sparse-checkout + * cone. Insist on cone mode here for backward + * compatibility. We don't want dst_mode to be assigned + * for a file when the repo is using no-cone mode (which + * is deprecated at this point) sparse-checkout. As + * SPARSE here is only considering cone-mode situation. + */ + if (!path_in_cone_mode_sparse_checkout(destination[0], &the_index)) + dst_mode = SPARSE; + } + } + if (dst_w_slash != dest_path[0]) { + free((char *)dst_w_slash); + dst_w_slash = NULL; } /* Checking */ for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; - int length, src_is_dir; + int length; const char *bad = NULL; int skip_sparse = 0; @@ -185,54 +252,107 @@ int cmd_mv(int argc, const char **argv, const char *prefix) length = strlen(src); if (lstat(src, &st) < 0) { - /* only error if existence is expected. */ - if (modes[i] != SPARSE) + int pos; + const struct cache_entry *ce; + + pos = cache_name_pos(src, length); + if (pos < 0) { + const char *src_w_slash = add_slash(src); + if (!path_in_sparse_checkout(src_w_slash, &the_index) && + empty_dir_has_sparse_contents(src)) { + modes[i] |= SKIP_WORKTREE_DIR; + goto dir_check; + } + /* only error if existence is expected. */ + if (!(modes[i] & SPARSE)) + bad = _("bad source"); + goto act_on_entry; + } + ce = active_cache[pos]; + if (!ce_skip_worktree(ce)) { bad = _("bad source"); - } else if (!strncmp(src, dst, length) && - (dst[length] == 0 || dst[length] == '/')) { + goto act_on_entry; + } + if (!ignore_sparse) { + string_list_append(&only_match_skip_worktree, src); + goto act_on_entry; + } + /* Check if dst exists in index */ + if (cache_name_pos(dst, strlen(dst)) < 0) { + modes[i] |= SPARSE; + goto act_on_entry; + } + if (!force) { + bad = _("destination exists"); + goto act_on_entry; + } + modes[i] |= SPARSE; + goto act_on_entry; + } + if (!strncmp(src, dst, length) && + (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); - } else if ((src_is_dir = S_ISDIR(st.st_mode)) - && lstat(dst, &st) == 0) + goto act_on_entry; + } + if (S_ISDIR(st.st_mode) + && lstat(dst, &st) == 0) { bad = _("cannot move directory over file"); - else if (src_is_dir) { + goto act_on_entry; + } + +dir_check: + if (S_ISDIR(st.st_mode)) { + int j, dst_len, n; int first = cache_name_pos(src, length), last; - if (first >= 0) + if (first >= 0) { prepare_move_submodule(src, first, submodule_gitfile + i); - else if (index_range_of_same_dir(src, length, - &first, &last) < 1) + goto act_on_entry; + } else if (index_range_of_same_dir(src, length, + &first, &last) < 1) { bad = _("source directory is empty"); - else { /* last - first >= 1 */ - int j, dst_len, n; - - modes[i] = WORKING_DIRECTORY; - n = argc + last - first; - REALLOC_ARRAY(source, n); - REALLOC_ARRAY(destination, n); - REALLOC_ARRAY(modes, n); - REALLOC_ARRAY(submodule_gitfile, n); - - dst = add_slash(dst); - dst_len = strlen(dst); - - for (j = 0; j < last - first; j++) { - const struct cache_entry *ce = active_cache[first + j]; - const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, path + length + 1); - modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; - submodule_gitfile[argc + j] = NULL; - } - argc += last - first; + goto act_on_entry; } - } else if (!(ce = cache_file_exists(src, length, 0))) { + + /* last - first >= 1 */ + modes[i] |= WORKING_DIRECTORY; + + ALLOC_GROW(src_dir, src_dir_nr + 1, src_dir_alloc); + src_dir[src_dir_nr++] = src; + + n = argc + last - first; + REALLOC_ARRAY(source, n); + REALLOC_ARRAY(destination, n); + REALLOC_ARRAY(modes, n); + REALLOC_ARRAY(submodule_gitfile, n); + + dst = add_slash(dst); + dst_len = strlen(dst); + + for (j = 0; j < last - first; j++) { + const struct cache_entry *ce = active_cache[first + j]; + const char *path = ce->name; + source[argc + j] = path; + destination[argc + j] = + prefix_path(dst, dst_len, path + length + 1); + memset(modes + argc + j, 0, sizeof(enum update_mode)); + modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; + submodule_gitfile[argc + j] = NULL; + } + argc += last - first; + goto act_on_entry; + } + if (!(ce = cache_file_exists(src, length, 0))) { bad = _("not under version control"); - } else if (ce_stage(ce)) { + goto act_on_entry; + } + if (ce_stage(ce)) { bad = _("conflicted"); - } else if (lstat(dst, &st) == 0 && - (!ignore_case || strcasecmp(src, dst))) { + goto act_on_entry; + } + if (lstat(dst, &st) == 0 && + (!ignore_case || strcasecmp(src, dst))) { bad = _("destination exists"); if (force) { /* @@ -246,34 +366,52 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } else bad = _("Cannot overwrite"); } - } else if (string_list_has_string(&src_for_dst, dst)) + goto act_on_entry; + } + if (string_list_has_string(&src_for_dst, dst)) { bad = _("multiple sources for the same target"); - else if (is_dir_sep(dst[strlen(dst) - 1])) + goto act_on_entry; + } + if (is_dir_sep(dst[strlen(dst) - 1])) { bad = _("destination directory does not exist"); - else { - /* - * We check if the paths are in the sparse-checkout - * definition as a very final check, since that - * allows us to point the user to the --sparse - * option as a way to have a successful run. - */ - if (!ignore_sparse && - !path_in_sparse_checkout(src, &the_index)) { - string_list_append(&only_match_skip_worktree, src); - skip_sparse = 1; - } - if (!ignore_sparse && - !path_in_sparse_checkout(dst, &the_index)) { - string_list_append(&only_match_skip_worktree, dst); - skip_sparse = 1; + goto act_on_entry; + } + + if (ignore_sparse && + (dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && + index_entry_exists(&the_index, dst, strlen(dst))) { + bad = _("destination exists in the index"); + if (force) { + if (verbose) + warning(_("overwriting '%s'"), dst); + bad = NULL; + } else { + goto act_on_entry; } + } + /* + * We check if the paths are in the sparse-checkout + * definition as a very final check, since that + * allows us to point the user to the --sparse + * option as a way to have a successful run. + */ + if (!ignore_sparse && + !path_in_sparse_checkout(src, &the_index)) { + string_list_append(&only_match_skip_worktree, src); + skip_sparse = 1; + } + if (!ignore_sparse && + !path_in_sparse_checkout(dst, &the_index)) { + string_list_append(&only_match_skip_worktree, dst); + skip_sparse = 1; + } - if (skip_sparse) - goto remove_entry; + if (skip_sparse) + goto remove_entry; - string_list_insert(&src_for_dst, dst); - } + string_list_insert(&src_for_dst, dst); +act_on_entry: if (!bad) continue; if (!ignore_errors) @@ -282,14 +420,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) remove_entry: if (--argc > 0) { int n = argc - i; - memmove(source + i, source + i + 1, - n * sizeof(char *)); - memmove(destination + i, destination + i + 1, - n * sizeof(char *)); - memmove(modes + i, modes + i + 1, - n * sizeof(enum update_mode)); - memmove(submodule_gitfile + i, submodule_gitfile + i + 1, - n * sizeof(char *)); + MOVE_ARRAY(source + i, source + i + 1, n); + MOVE_ARRAY(destination + i, destination + i + 1, n); + MOVE_ARRAY(modes + i, modes + i + 1, n); + MOVE_ARRAY(submodule_gitfile + i, + submodule_gitfile + i + 1, n); i--; } } @@ -304,11 +439,19 @@ remove_entry: const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; int pos; + int sparse_and_dirty = 0; + struct checkout state = CHECKOUT_INIT; + state.istate = &the_index; + + if (force) + state.force = 1; if (show_only || verbose) printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { + if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) && + !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && + rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); @@ -322,14 +465,86 @@ remove_entry: 1); } - if (mode == WORKING_DIRECTORY) + if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR)) continue; pos = cache_name_pos(src, strlen(src)); assert(pos >= 0); + if (!(mode & SPARSE) && !lstat(src, &st)) + sparse_and_dirty = ce_modified(active_cache[pos], &st, 0); rename_cache_entry_at(pos, dst); + + if (ignore_sparse && + core_apply_sparse_checkout && + core_sparse_checkout_cone) { + /* + * NEEDSWORK: we are *not* paying attention to + * "out-to-out" move (<source> is out-of-cone and + * <destination> is out-of-cone) at this point. It + * should be added in a future patch. + */ + if ((mode & SPARSE) && + path_in_sparse_checkout(dst, &the_index)) { + /* from out-of-cone to in-cone */ + int dst_pos = cache_name_pos(dst, strlen(dst)); + struct cache_entry *dst_ce = active_cache[dst_pos]; + + dst_ce->ce_flags &= ~CE_SKIP_WORKTREE; + + if (checkout_entry(dst_ce, &state, NULL, NULL)) + die(_("cannot checkout %s"), dst_ce->name); + } else if ((dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && + !(mode & SPARSE) && + !path_in_sparse_checkout(dst, &the_index)) { + /* from in-cone to out-of-cone */ + int dst_pos = cache_name_pos(dst, strlen(dst)); + struct cache_entry *dst_ce = active_cache[dst_pos]; + + /* + * if src is clean, it will suffice to remove it + */ + if (!sparse_and_dirty) { + dst_ce->ce_flags |= CE_SKIP_WORKTREE; + unlink_or_warn(src); + } else { + /* + * if src is dirty, move it to the + * destination and create leading + * dirs if necessary + */ + char *dst_dup = xstrdup(dst); + string_list_append(&dirty_paths, dst); + safe_create_leading_directories(dst_dup); + FREE_AND_NULL(dst_dup); + rename(src, dst); + } + } + } } + /* + * cleanup the empty src_dirs + */ + for (i = 0; i < src_dir_nr; i++) { + int dummy; + strbuf_addstr(&a_src_dir, src_dir[i]); + /* + * if entries under a_src_dir are all moved away, + * recursively remove a_src_dir to cleanup + */ + if (index_range_of_same_dir(a_src_dir.buf, a_src_dir.len, + &dummy, &dummy) < 1) { + remove_dir_recursively(&a_src_dir, 0); + } + strbuf_reset(&a_src_dir); + } + + strbuf_release(&a_src_dir); + free(src_dir); + + if (dirty_paths.nr) + advise_on_moving_dirty_path(&dirty_paths); + if (gitmodules_modified) stage_updated_gitmodules(&the_index); @@ -338,6 +553,7 @@ remove_entry: die(_("Unable to write new index file")); string_list_clear(&src_for_dst, 0); + string_list_clear(&dirty_paths, 0); UNLEAK(source); UNLEAK(dest_path); free(submodule_gitfile); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 580b1eb170..15535e914a 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -344,7 +344,8 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_) return a->taggerdate != b->taggerdate; } -static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) +static int name_ref(const char *path, const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct object *o = parse_object(the_repository, oid); struct name_ref_data *data = cb_data; diff --git a/builtin/notes.c b/builtin/notes.c index a3d0d15a22..be51f69225 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -994,17 +994,34 @@ static int get_ref(int argc, const char **argv, const char *prefix) int cmd_notes(int argc, const char **argv, const char *prefix) { - int result; const char *override_notes_ref = NULL; + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"), N_("use notes from <notes-ref>")), + OPT_SUBCOMMAND("list", &fn, list), + OPT_SUBCOMMAND("add", &fn, add), + OPT_SUBCOMMAND("copy", &fn, copy), + OPT_SUBCOMMAND("append", &fn, append_edit), + OPT_SUBCOMMAND("edit", &fn, append_edit), + OPT_SUBCOMMAND("show", &fn, show), + OPT_SUBCOMMAND("merge", &fn, merge), + OPT_SUBCOMMAND("remove", &fn, remove_cmd), + OPT_SUBCOMMAND("prune", &fn, prune), + OPT_SUBCOMMAND("get-ref", &fn, get_ref), OPT_END() }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_notes_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + PARSE_OPT_SUBCOMMAND_OPTIONAL); + if (!fn) { + if (argc) { + error(_("unknown subcommand: `%s'"), argv[0]); + usage_with_options(git_notes_usage, options); + } + fn = list; + } if (override_notes_ref) { struct strbuf sb = STRBUF_INIT; @@ -1014,28 +1031,5 @@ int cmd_notes(int argc, const char **argv, const char *prefix) strbuf_release(&sb); } - if (argc < 1 || !strcmp(argv[0], "list")) - result = list(argc, argv, prefix); - else if (!strcmp(argv[0], "add")) - result = add(argc, argv, prefix); - else if (!strcmp(argv[0], "copy")) - result = copy(argc, argv, prefix); - else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit")) - result = append_edit(argc, argv, prefix); - else if (!strcmp(argv[0], "show")) - result = show(argc, argv, prefix); - else if (!strcmp(argv[0], "merge")) - result = merge(argc, argv, prefix); - else if (!strcmp(argv[0], "remove")) - result = remove_cmd(argc, argv, prefix); - else if (!strcmp(argv[0], "prune")) - result = prune(argc, argv, prefix); - else if (!strcmp(argv[0], "get-ref")) - result = get_ref(argc, argv, prefix); - else { - result = error(_("unknown subcommand: %s"), argv[0]); - usage_with_options(git_notes_usage, options); - } - - return result ? 1 : 0; + return !!fn(argc, argv, prefix); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 39e28cfcaf..3658c05caf 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -759,8 +759,8 @@ static enum write_one_status write_one(struct hashfile *f, return WRITE_ONE_WRITTEN; } -static int mark_tagged(const char *path, const struct object_id *oid, int flag, - void *cb_data) +static int mark_tagged(const char *path UNUSED, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { struct object_id peeled; struct object_entry *entry = packlist_find(&to_pack, oid); @@ -3035,7 +3035,8 @@ static void add_tag_chain(const struct object_id *oid) } } -static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data) +static int add_ref_tag(const char *tag UNUSED, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { struct object_id peeled; @@ -3148,6 +3149,14 @@ static int git_pack_config(const char *k, const char *v, void *cb) else write_bitmap_options &= ~BITMAP_OPT_HASH_CACHE; } + + if (!strcmp(k, "pack.writebitmaplookuptable")) { + if (git_config_bool(k, v)) + write_bitmap_options |= BITMAP_OPT_LOOKUP_TABLE; + else + write_bitmap_options &= ~BITMAP_OPT_LOOKUP_TABLE; + } + if (!strcmp(k, "pack.usebitmaps")) { use_bitmap_index_default = git_config_bool(k, v); return 0; @@ -3950,8 +3959,9 @@ static void record_recent_commit(struct commit *commit, void *data) } static int mark_bitmap_preferred_tip(const char *refname, - const struct object_id *oid, int flags, - void *_data) + const struct object_id *oid, + int flags UNUSED, + void *data UNUSED) { struct object_id peeled; struct object *object; diff --git a/builtin/pull.c b/builtin/pull.c index 01155ba67b..403a24d7ca 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -990,6 +990,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) int rebase_unspecified = 0; int can_ff; int divergent; + int ret; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); @@ -1100,7 +1101,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (is_null_oid(&orig_head)) { if (merge_heads.nr > 1) die(_("Cannot merge multiple branches into empty head.")); - return pull_into_void(merge_heads.oid, &curr_head); + ret = pull_into_void(merge_heads.oid, &curr_head); + goto cleanup; } if (merge_heads.nr > 1) { if (opt_rebase) @@ -1125,8 +1127,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) } if (opt_rebase) { - int ret = 0; - struct object_id newbase; struct object_id upstream; get_rebase_newbase_and_upstream(&newbase, &upstream, &curr_head, @@ -1149,12 +1149,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix) recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) ret = rebase_submodules(); - return ret; + goto cleanup; } else { - int ret = run_merge(); + ret = run_merge(); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) ret = update_submodules(); - return ret; + goto cleanup; } + +cleanup: + oid_array_clear(&merge_heads); + return ret; } diff --git a/builtin/push.c b/builtin/push.c index df0d68e599..f0329c62a2 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -169,8 +169,8 @@ static NORETURN void die_push_simple(struct branch *branch, if (git_branch_track != BRANCH_TRACK_SIMPLE) advice_automergesimple_maybe = _("\n" "To avoid automatically configuring " - "upstream branches when their name\n" - "doesn't match the local branch, see option " + "an upstream branch when its name\n" + "won't match the local branch, see option " "'simple' of branch.autoSetupMerge\n" "in 'git help config'.\n"); die(_("The upstream branch of your current branch does not match\n" diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 50318849d6..e2a74efb42 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -38,8 +38,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) OPT_END() }; struct option *options; - int res = 0; + int i, dash_dash = -1, res = 0; struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT; + struct object_id oid; + const char *three_dots = NULL; git_config(git_diff_ui_config, NULL); @@ -47,7 +49,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) options = parse_options_concat(range_diff_options, diffopt.parseopts); argc = parse_options(argc, argv, prefix, options, - builtin_range_diff_usage, 0); + builtin_range_diff_usage, PARSE_OPT_KEEP_DASHDASH); diff_setup_done(&diffopt); @@ -55,40 +57,91 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) if (!simple_color) diffopt.use_color = 1; - if (argc == 2) { - if (!is_range_diff_range(argv[0])) - die(_("not a commit range: '%s'"), argv[0]); - strbuf_addstr(&range1, argv[0]); + for (i = 0; i < argc; i++) + if (!strcmp(argv[i], "--")) { + dash_dash = i; + break; + } + + if (dash_dash == 3 || + (dash_dash < 0 && argc > 2 && + !get_oid_committish(argv[0], &oid) && + !get_oid_committish(argv[1], &oid) && + !get_oid_committish(argv[2], &oid))) { + if (dash_dash < 0) + ; /* already validated arguments */ + else if (get_oid_committish(argv[0], &oid)) + usage_msg_optf(_("not a revision: '%s'"), + builtin_range_diff_usage, options, + argv[0]); + else if (get_oid_committish(argv[1], &oid)) + usage_msg_optf(_("not a revision: '%s'"), + builtin_range_diff_usage, options, + argv[1]); + else if (get_oid_committish(argv[2], &oid)) + usage_msg_optf(_("not a revision: '%s'"), + builtin_range_diff_usage, options, + argv[2]); - if (!is_range_diff_range(argv[1])) - die(_("not a commit range: '%s'"), argv[1]); - strbuf_addstr(&range2, argv[1]); - } else if (argc == 3) { strbuf_addf(&range1, "%s..%s", argv[0], argv[1]); strbuf_addf(&range2, "%s..%s", argv[0], argv[2]); - } else if (argc == 1) { - const char *b = strstr(argv[0], "..."), *a = argv[0]; + + strvec_pushv(&other_arg, argv + + (dash_dash < 0 ? 3 : dash_dash)); + } else if (dash_dash == 2 || + (dash_dash < 0 && argc > 1 && + is_range_diff_range(argv[0]) && + is_range_diff_range(argv[1]))) { + if (dash_dash < 0) + ; /* already validated arguments */ + else if (!is_range_diff_range(argv[0])) + usage_msg_optf(_("not a commit range: '%s'"), + builtin_range_diff_usage, options, + argv[0]); + else if (!is_range_diff_range(argv[1])) + usage_msg_optf(_("not a commit range: '%s'"), + builtin_range_diff_usage, options, + argv[1]); + + strbuf_addstr(&range1, argv[0]); + strbuf_addstr(&range2, argv[1]); + + strvec_pushv(&other_arg, argv + + (dash_dash < 0 ? 2 : dash_dash)); + } else if (dash_dash == 1 || + (dash_dash < 0 && argc > 0 && + (three_dots = strstr(argv[0], "...")))) { + const char *a, *b; int a_len; - if (!b) { - error(_("single arg format must be symmetric range")); - usage_with_options(builtin_range_diff_usage, options); - } + if (dash_dash < 0) + ; /* already validated arguments */ + else if (!(three_dots = strstr(argv[0], "..."))) + usage_msg_optf(_("not a symmetric range: '%s'"), + builtin_range_diff_usage, options, + argv[0]); - a_len = (int)(b - a); - if (!a_len) { + if (three_dots == argv[0]) { a = "HEAD"; a_len = strlen(a); + } else { + a = argv[0]; + a_len = (int)(three_dots - a); } - b += 3; - if (!*b) + + if (three_dots[3]) + b = three_dots + 3; + else b = "HEAD"; + strbuf_addf(&range1, "%s..%.*s", b, a_len, a); strbuf_addf(&range2, "%.*s..%s", a_len, a, b); - } else { - error(_("need two commit ranges")); - usage_with_options(builtin_range_diff_usage, options); - } + + strvec_pushv(&other_arg, argv + + (dash_dash < 0 ? 1 : dash_dash)); + } else + usage_msg_opt(_("need two commit ranges"), + builtin_range_diff_usage, options); FREE_AND_NULL(options); range_diff_opts.dual_color = simple_color < 1; diff --git a/builtin/rebase.c b/builtin/rebase.c index 70aa7c842f..56e4214b44 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -102,6 +102,7 @@ struct rebase_options { int reschedule_failed_exec; int reapply_cherry_picks; int fork_point; + int update_refs; }; #define REBASE_OPTIONS_INIT { \ @@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) ret = complete_action(the_repository, &replay, flags, shortrevisions, opts->onto_name, opts->onto, &opts->orig_head, &commands, opts->autosquash, + opts->update_refs, &todo_list); } @@ -800,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.updaterefs")) { + opts->update_refs = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "rebase.reschedulefailedexec")) { opts->reschedule_failed_exec = git_config_bool(var, value); return 0; @@ -1124,6 +1131,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), + OPT_BOOL(0, "update-refs", &options.update_refs, + N_("update branches that point to commits " + "that are being rebased")), { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 31b48e728b..44bcea3a5b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -291,7 +291,7 @@ static void show_ref(const char *path, const struct object_id *oid) } static int show_ref_cb(const char *path_full, const struct object_id *oid, - int flag, void *data) + int flag UNUSED, void *data) { struct oidset *seen = data; const char *path = strip_namespace(path_full); @@ -465,7 +465,7 @@ static void rp_error(const char *err, ...) va_end(params); } -static int copy_to_sideband(int in, int out, void *arg) +static int copy_to_sideband(int in, int out UNUSED, void *arg UNUSED) { char data[128]; int keepalive_active = 0; diff --git a/builtin/reflog.c b/builtin/reflog.c index 4dd297dce8..270681dcdf 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -56,7 +56,8 @@ struct worktree_reflogs { struct string_list reflogs; }; -static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data) +static int collect_reflog(const char *ref, const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct worktree_reflogs *cb = cb_data; struct worktree *worktree = cb->worktree; @@ -66,7 +67,8 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus * Avoid collecting the same shared ref multiple times because * they are available via all worktrees. */ - if (!worktree->is_current && ref_type(ref) == REF_TYPE_NORMAL) + if (!worktree->is_current && + parse_worktree_ref(ref, NULL, NULL, NULL) == REF_WORKTREE_SHARED) return 0; strbuf_worktree_ref(worktree, &newref, ref); @@ -193,6 +195,8 @@ static int expire_unreachable_callback(const struct option *opt, { struct cmd_reflog_expire_cb *cmd = opt->value; + BUG_ON_OPT_NEG(unset); + if (parse_expiry_date(arg, &cmd->expire_unreachable)) die(_("invalid timestamp '%s' given to '--%s'"), arg, opt->long_name); @@ -207,6 +211,8 @@ static int expire_total_callback(const struct option *opt, { struct cmd_reflog_expire_cb *cmd = opt->value; + BUG_ON_OPT_NEG(unset); + if (parse_expiry_date(arg, &cmd->expire_total)) die(_("invalid timestamp '%s' given to '--%s'"), arg, opt->long_name); @@ -223,7 +229,7 @@ static int cmd_reflog_show(int argc, const char **argv, const char *prefix) parse_options(argc, argv, prefix, options, reflog_show_usage, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); return cmd_log_reflog(argc, argv, prefix); } @@ -404,40 +410,21 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) int cmd_reflog(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("show", &fn, cmd_reflog_show), + OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), + OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), + OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), OPT_END() }; argc = parse_options(argc, argv, prefix, options, reflog_usage, + PARSE_OPT_SUBCOMMAND_OPTIONAL | PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN | - PARSE_OPT_NO_INTERNAL_HELP); - - /* - * With "git reflog" we default to showing it. !argc is - * impossible with PARSE_OPT_KEEP_ARGV0. - */ - if (argc == 1) - goto log_reflog; - - if (!strcmp(argv[1], "-h")) - usage_with_options(reflog_usage, options); - else if (*argv[1] == '-') - goto log_reflog; - - if (!strcmp(argv[1], "show")) - return cmd_reflog_show(argc - 1, argv + 1, prefix); - else if (!strcmp(argv[1], "expire")) - return cmd_reflog_expire(argc - 1, argv + 1, prefix); - else if (!strcmp(argv[1], "delete")) - return cmd_reflog_delete(argc - 1, argv + 1, prefix); - else if (!strcmp(argv[1], "exists")) - return cmd_reflog_exists(argc - 1, argv + 1, prefix); - - /* - * Fall-through for e.g. "git reflog -1", "git reflog master", - * as well as the plain "git reflog" above goto above. - */ -log_reflog: - return cmd_log_reflog(argc, argv, prefix); + PARSE_OPT_KEEP_UNKNOWN_OPT); + if (fn) + return fn(argc - 1, argv + 1, prefix); + else + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote.c b/builtin/remote.c index a3a0c27d7a..910f7b9316 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -150,7 +150,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not) return 0; } -static int add(int argc, const char **argv) +static int add(int argc, const char **argv, const char *prefix) { int fetch = 0, fetch_tags = TAGS_DEFAULT; unsigned mirror = MIRROR_NONE; @@ -177,8 +177,8 @@ static int add(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage, - 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_add_usage, 0); if (argc != 2) usage_with_options(builtin_remote_add_usage, options); @@ -264,7 +264,8 @@ static const char *abbrev_ref(const char *name, const char *prefix) } #define abbrev_branch(name) abbrev_ref((name), "refs/heads/") -static int config_read_branches(const char *key, const char *value, void *cb) +static int config_read_branches(const char *key, const char *value, + void *data UNUSED) { const char *orig_key = key; char *name; @@ -344,12 +345,13 @@ static void read_branches(void) struct ref_states { struct remote *remote; - struct string_list new_refs, stale, tracked, heads, push; + struct string_list new_refs, skipped, stale, tracked, heads, push; int queried; }; #define REF_STATES_INIT { \ .new_refs = STRING_LIST_INIT_DUP, \ + .skipped = STRING_LIST_INIT_DUP, \ .stale = STRING_LIST_INIT_DUP, \ .tracked = STRING_LIST_INIT_DUP, \ .heads = STRING_LIST_INIT_DUP, \ @@ -368,7 +370,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat states->remote->fetch.raw[i]); for (ref = fetch_map; ref; ref = ref->next) { - if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) + if (omit_name_by_refspec(ref->name, &states->remote->fetch)) + string_list_append(&states->skipped, abbrev_branch(ref->name)); + else if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new_refs, abbrev_branch(ref->name)); else string_list_append(&states->tracked, abbrev_branch(ref->name)); @@ -383,6 +387,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat free_refs(fetch_map); string_list_sort(&states->new_refs); + string_list_sort(&states->skipped); string_list_sort(&states->tracked); string_list_sort(&states->stale); @@ -534,7 +539,8 @@ struct branches_for_remote { }; static int add_branch_for_removal(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct branches_for_remote *branches = cb_data; struct refspec_item refspec; @@ -576,7 +582,8 @@ struct rename_info { }; static int read_remote_branches(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct rename_info *rename = cb_data; struct strbuf buf = STRBUF_INIT; @@ -676,7 +683,7 @@ static void handle_push_default(const char* old_name, const char* new_name) } -static int mv(int argc, const char **argv) +static int mv(int argc, const char **argv, const char *prefix) { int show_progress = isatty(2); struct option options[] = { @@ -691,7 +698,7 @@ static int mv(int argc, const char **argv) int i, refs_renamed_nr = 0, refspec_updated = 0; struct progress *progress = NULL; - argc = parse_options(argc, argv, NULL, options, + argc = parse_options(argc, argv, prefix, options, builtin_remote_rename_usage, 0); if (argc != 2) @@ -726,29 +733,31 @@ static int mv(int argc, const char **argv) return error(_("Could not rename config section '%s' to '%s'"), buf.buf, buf2.buf); - strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); - 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; - - strbuf_reset(&buf2); - strbuf_addstr(&buf2, oldremote->fetch.raw[i]); - ptr = strstr(buf2.buf, old_remote_context.buf); - if (ptr) { - refspec_updated = 1; - strbuf_splice(&buf2, - ptr-buf2.buf + strlen(":refs/remotes/"), - strlen(rename.old_name), rename.new_name, - strlen(rename.new_name)); - } else - warning(_("Not updating non-default fetch refspec\n" - "\t%s\n" - "\tPlease update the configuration manually if necessary."), - buf2.buf); - - git_config_set_multivar(buf.buf, buf2.buf, "^$", 0); + if (oldremote->fetch.raw_nr) { + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); + 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; + + strbuf_reset(&buf2); + strbuf_addstr(&buf2, oldremote->fetch.raw[i]); + ptr = strstr(buf2.buf, old_remote_context.buf); + if (ptr) { + refspec_updated = 1; + strbuf_splice(&buf2, + ptr-buf2.buf + strlen(":refs/remotes/"), + strlen(rename.old_name), rename.new_name, + strlen(rename.new_name)); + } else + warning(_("Not updating non-default fetch refspec\n" + "\t%s\n" + "\tPlease update the configuration manually if necessary."), + buf2.buf); + + git_config_set_multivar(buf.buf, buf2.buf, "^$", 0); + } } read_branches(); @@ -840,7 +849,7 @@ static int mv(int argc, const char **argv) return 0; } -static int rm(int argc, const char **argv) +static int rm(int argc, const char **argv, const char *prefix) { struct option options[] = { OPT_END() @@ -858,12 +867,14 @@ static int rm(int argc, const char **argv) cb_data.skipped = &skipped; cb_data.keep = &known_remotes; - if (argc != 2) + argc = parse_options(argc, argv, prefix, options, + builtin_remote_rm_usage, 0); + if (argc != 1) usage_with_options(builtin_remote_rm_usage, options); - remote = remote_get(argv[1]); + remote = remote_get(argv[0]); if (!remote_is_configured(remote, 1)) { - error(_("No such remote: '%s'"), argv[1]); + error(_("No such remote: '%s'"), argv[0]); exit(2); } @@ -941,6 +952,7 @@ static void clear_push_info(void *util, const char *string) static void free_remote_ref_states(struct ref_states *states) { string_list_clear(&states->new_refs, 0); + string_list_clear(&states->skipped, 0); string_list_clear(&states->stale, 1); string_list_clear(&states->tracked, 0); string_list_clear(&states->heads, 0); @@ -948,7 +960,8 @@ static void free_remote_ref_states(struct ref_states *states) } static int append_ref_to_tracked_list(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags, void *cb_data) { struct ref_states *states = cb_data; struct refspec_item refspec; @@ -1035,6 +1048,8 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) arg = states->remote->name; } else if (string_list_has_string(&states->tracked, name)) arg = _(" tracked"); + else if (string_list_has_string(&states->skipped, name)) + arg = _(" skipped"); else if (string_list_has_string(&states->stale, name)) arg = _(" stale (use 'git remote prune' to remove)"); else @@ -1247,7 +1262,7 @@ static int show_all(void) return result; } -static int show(int argc, const char **argv) +static int show(int argc, const char **argv, const char *prefix) { int no_query = 0, result = 0, query_flag = 0; struct option options[] = { @@ -1256,7 +1271,8 @@ static int show(int argc, const char **argv) }; struct show_info info = SHOW_INFO_INIT; - argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage, + argc = parse_options(argc, argv, prefix, options, + builtin_remote_show_usage, 0); if (argc < 1) @@ -1307,6 +1323,7 @@ static int show(int argc, const char **argv) /* remote branch info */ info.width = 0; for_each_string_list(&info.states.new_refs, add_remote_to_show_info, &info); + for_each_string_list(&info.states.skipped, add_remote_to_show_info, &info); for_each_string_list(&info.states.tracked, add_remote_to_show_info, &info); for_each_string_list(&info.states.stale, add_remote_to_show_info, &info); if (info.list.nr) @@ -1349,7 +1366,7 @@ static int show(int argc, const char **argv) return result; } -static int set_head(int argc, const char **argv) +static int set_head(int argc, const char **argv, const char *prefix) { int i, opt_a = 0, opt_d = 0, result = 0; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; @@ -1362,8 +1379,8 @@ static int set_head(int argc, const char **argv) N_("delete refs/remotes/<name>/HEAD")), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage, - 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_sethead_usage, 0); if (argc) strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]); @@ -1454,7 +1471,7 @@ static int prune_remote(const char *remote, int dry_run) return result; } -static int prune(int argc, const char **argv) +static int prune(int argc, const char **argv, const char *prefix) { int dry_run = 0, result = 0; struct option options[] = { @@ -1462,8 +1479,8 @@ static int prune(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage, - 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_prune_usage, 0); if (argc < 1) usage_with_options(builtin_remote_prune_usage, options); @@ -1474,7 +1491,7 @@ static int prune(int argc, const char **argv) return result; } -static int get_remote_default(const char *key, const char *value, void *priv) +static int get_remote_default(const char *key, const char *value UNUSED, void *priv) { if (strcmp(key, "remotes.default") == 0) { int *found = priv; @@ -1483,7 +1500,7 @@ static int get_remote_default(const char *key, const char *value, void *priv) return 0; } -static int update(int argc, const char **argv) +static int update(int argc, const char **argv, const char *prefix) { int i, prune = -1; struct option options[] = { @@ -1495,7 +1512,8 @@ static int update(int argc, const char **argv) int default_defined = 0; int retval; - argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, + argc = parse_options(argc, argv, prefix, options, + builtin_remote_update_usage, PARSE_OPT_KEEP_ARGV0); strvec_push(&fetch_argv, "fetch"); @@ -1566,7 +1584,7 @@ static int set_remote_branches(const char *remotename, const char **branches, return 0; } -static int set_branches(int argc, const char **argv) +static int set_branches(int argc, const char **argv, const char *prefix) { int add_mode = 0; struct option options[] = { @@ -1574,7 +1592,7 @@ static int set_branches(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, NULL, options, + argc = parse_options(argc, argv, prefix, options, builtin_remote_setbranches_usage, 0); if (argc == 0) { error(_("no remote specified")); @@ -1585,7 +1603,7 @@ static int set_branches(int argc, const char **argv) return set_remote_branches(argv[0], argv + 1, add_mode); } -static int get_url(int argc, const char **argv) +static int get_url(int argc, const char **argv, const char *prefix) { int i, push_mode = 0, all_mode = 0; const char *remotename = NULL; @@ -1599,7 +1617,8 @@ static int get_url(int argc, const char **argv) N_("return all URLs")), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_geturl_usage, 0); if (argc != 1) usage_with_options(builtin_remote_geturl_usage, options); @@ -1638,7 +1657,7 @@ static int get_url(int argc, const char **argv) return 0; } -static int set_url(int argc, const char **argv) +static int set_url(int argc, const char **argv, const char *prefix) { int i, push_mode = 0, add_mode = 0, delete_mode = 0; int matches = 0, negative_matches = 0; @@ -1659,7 +1678,8 @@ static int set_url(int argc, const char **argv) N_("delete URLs")), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage, + argc = parse_options(argc, argv, prefix, options, + builtin_remote_seturl_usage, PARSE_OPT_KEEP_ARGV0); if (add_mode && delete_mode) @@ -1730,41 +1750,33 @@ out: int cmd_remote(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), + OPT_SUBCOMMAND("add", &fn, add), + OPT_SUBCOMMAND("rename", &fn, mv), + OPT_SUBCOMMAND_F("rm", &fn, rm, PARSE_OPT_NOCOMPLETE), + OPT_SUBCOMMAND("remove", &fn, rm), + OPT_SUBCOMMAND("set-head", &fn, set_head), + OPT_SUBCOMMAND("set-branches", &fn, set_branches), + OPT_SUBCOMMAND("get-url", &fn, get_url), + OPT_SUBCOMMAND("set-url", &fn, set_url), + OPT_SUBCOMMAND("show", &fn, show), + OPT_SUBCOMMAND("prune", &fn, prune), + OPT_SUBCOMMAND("update", &fn, update), OPT_END() }; - int result; argc = parse_options(argc, argv, prefix, options, builtin_remote_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + PARSE_OPT_SUBCOMMAND_OPTIONAL); - if (argc < 1) - result = show_all(); - else if (!strcmp(argv[0], "add")) - result = add(argc, argv); - else if (!strcmp(argv[0], "rename")) - result = mv(argc, argv); - else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) - result = rm(argc, argv); - else if (!strcmp(argv[0], "set-head")) - result = set_head(argc, argv); - else if (!strcmp(argv[0], "set-branches")) - result = set_branches(argc, argv); - else if (!strcmp(argv[0], "get-url")) - result = get_url(argc, argv); - else if (!strcmp(argv[0], "set-url")) - result = set_url(argc, argv); - else if (!strcmp(argv[0], "show")) - result = show(argc, argv); - else if (!strcmp(argv[0], "prune")) - result = prune(argc, argv); - else if (!strcmp(argv[0], "update")) - result = update(argc, argv); - else { - error(_("Unknown subcommand: %s"), argv[0]); - usage_with_options(builtin_remote_usage, options); + if (fn) { + return !!fn(argc, argv, prefix); + } else { + if (argc) { + error(_("unknown subcommand: `%s'"), argv[0]); + usage_with_options(builtin_remote_usage, options); + } + return !!show_all(); } - - return result ? 1 : 0; } diff --git a/builtin/repack.c b/builtin/repack.c index 4a7ae4cf48..a5bacc7797 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -514,9 +514,9 @@ struct midx_snapshot_ref_data { int preferred; }; -static int midx_snapshot_ref_one(const char *refname, +static int midx_snapshot_ref_one(const char *refname UNUSED, const struct object_id *oid, - int flag, void *_data) + int flag UNUSED, void *_data) { struct midx_snapshot_ref_data *data = _data; struct object_id peeled; @@ -727,7 +727,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) 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_nonkept_packs = STRING_LIST_INIT_DUP; struct string_list existing_kept_packs = STRING_LIST_INIT_DUP; struct pack_geometry *geometry = NULL; @@ -1117,7 +1116,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } string_list_clear(&names, 0); - string_list_clear(&rollback, 0); string_list_clear(&existing_nonkept_packs, 0); string_list_clear(&existing_kept_packs, 0); clear_pack_geometry(geometry); diff --git a/builtin/replace.c b/builtin/replace.c index 583702a098..a29e911d30 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -106,6 +106,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) size_t base_len; int had_error = 0; struct object_id oid; + const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; strbuf_addstr(&ref, git_replace_ref_base); base_len = ref.len; @@ -147,6 +148,8 @@ static int check_ref_valid(struct object_id *object, struct strbuf *ref, int force) { + const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; + strbuf_reset(ref); strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object)); if (check_refname_format(ref->buf, 0)) diff --git a/builtin/reset.c b/builtin/reset.c index 344fff8f3a..fdce6f8c85 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -174,88 +174,6 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } -static int pathspec_needs_expanded_index(const struct pathspec *pathspec) -{ - unsigned int i, pos; - int res = 0; - char *skip_worktree_seen = NULL; - - /* - * When using a magic pathspec, assume for the sake of simplicity that - * the index needs to be expanded to match all matchable files. - */ - if (pathspec->magic) - return 1; - - for (i = 0; i < pathspec->nr; i++) { - struct pathspec_item item = pathspec->items[i]; - - /* - * If the pathspec item has a wildcard, the index should be expanded - * if the pathspec has the possibility of matching a subset of entries inside - * of a sparse directory (but not the entire directory). - * - * If the pathspec item is a literal path, the index only needs to be expanded - * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't - * expand for in-cone files) and b) it doesn't match any sparse directories - * (since we can reset whole sparse directories without expanding them). - */ - if (item.nowildcard_len < item.len) { - /* - * Special case: if the pattern is a path inside the cone - * followed by only wildcards, the pattern cannot match - * partial sparse directories, so we know we don't need to - * expand the index. - * - * Examples: - * - in-cone/foo***: doesn't need expanded index - * - not-in-cone/bar*: may need expanded index - * - **.c: may need expanded index - */ - if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && - path_in_cone_mode_sparse_checkout(item.original, &the_index)) - continue; - - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - - if (!S_ISSPARSEDIR(ce->ce_mode)) - continue; - - /* - * If the pre-wildcard length is longer than the sparse - * directory name and the sparse directory is the first - * component of the pathspec, need to expand the index. - */ - if (item.nowildcard_len > ce_namelen(ce) && - !strncmp(item.original, ce->name, ce_namelen(ce))) { - res = 1; - break; - } - - /* - * If the pre-wildcard length is shorter than the sparse - * directory and the pathspec does not match the whole - * directory, need to expand the index. - */ - if (!strncmp(item.original, ce->name, item.nowildcard_len) && - wildmatch(item.original, ce->name, 0)) { - res = 1; - break; - } - } - } else if (!path_in_cone_mode_sparse_checkout(item.original, &the_index) && - !matches_skip_worktree(pathspec, i, &skip_worktree_seen)) - res = 1; - - if (res > 0) - break; - } - - free(skip_worktree_seen); - return res; -} - static int read_from_tree(const struct pathspec *pathspec, struct object_id *tree_oid, int intent_to_add) @@ -273,7 +191,7 @@ static int read_from_tree(const struct pathspec *pathspec, opt.change = diff_change; opt.add_remove = diff_addremove; - if (pathspec->nr && the_index.sparse_index && pathspec_needs_expanded_index(pathspec)) + if (pathspec->nr && pathspec_needs_expanded_index(&the_index, pathspec)) ensure_full_index(&the_index); if (do_diff_cache(tree_oid, &opt)) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 30fd8e83ea..fba6f5d51f 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -46,6 +46,7 @@ static const char rev_list_usage[] = " --parents\n" " --children\n" " --objects | --objects-edge\n" +" --disk-usage[=human]\n" " --unpacked\n" " --header | --pretty\n" " --[no-]object-names\n" @@ -81,6 +82,7 @@ static int arg_show_object_names = 1; static int show_disk_usage; static off_t total_disk_usage; +static int human_readable; static off_t get_object_disk_usage(struct object *obj) { @@ -368,6 +370,17 @@ static int show_object_fast( return 1; } +static void print_disk_usage(off_t size) +{ + struct strbuf sb = STRBUF_INIT; + if (human_readable) + strbuf_humanise_bytes(&sb, size); + else + strbuf_addf(&sb, "%"PRIuMAX, (uintmax_t)size); + puts(sb.buf); + strbuf_release(&sb); +} + static inline int parse_missing_action_value(const char *value) { if (!strcmp(value, "error")) { @@ -473,6 +486,7 @@ static int try_bitmap_disk_usage(struct rev_info *revs, int filter_provided_objects) { struct bitmap_index *bitmap_git; + off_t size_from_bitmap; if (!show_disk_usage) return -1; @@ -481,8 +495,8 @@ static int try_bitmap_disk_usage(struct rev_info *revs, if (!bitmap_git) return -1; - printf("%"PRIuMAX"\n", - (uintmax_t)get_disk_usage_from_bitmap(bitmap_git, revs)); + size_from_bitmap = get_disk_usage_from_bitmap(bitmap_git, revs); + print_disk_usage(size_from_bitmap); return 0; } @@ -624,7 +638,21 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } - if (!strcmp(arg, "--disk-usage")) { + if (skip_prefix(arg, "--disk-usage", &arg)) { + if (*arg == '=') { + if (!strcmp(++arg, "human")) { + human_readable = 1; + } else + die(_("invalid value for '%s': '%s', the only allowed format is '%s'"), + "--disk-usage=<format>", arg, "human"); + } else if (*arg) { + /* + * Arguably should goto a label to continue chain of ifs? + * Doesn't matter unless we try to add --disk-usage-foo + * afterwards. + */ + usage(rev_list_usage); + } show_disk_usage = 1; info.flags |= REV_LIST_QUIET; continue; @@ -753,7 +781,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } if (show_disk_usage) - printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage); + print_disk_usage(total_disk_usage); cleanup: release_revisions(&revs); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index b259d8990a..8f61050bde 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -195,7 +195,8 @@ static int show_default(void) return 0; } -static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) +static int show_reference(const char *refname, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { if (ref_excluded(ref_excludes, refname)) return 0; @@ -203,7 +204,8 @@ static int show_reference(const char *refname, const struct object_id *oid, int return 0; } -static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) +static int anti_reference(const char *refname, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { show_rev(REVERSED, oid, refname); return 0; @@ -479,6 +481,9 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) if (!s) s = help; + if (s == sb.buf) + die(_("missing opt-spec before option flags")); + if (s - sb.buf == 1) /* short option only */ o->short_name = *sb.buf; else if (sb.buf[1] != ',') /* long option only */ diff --git a/builtin/revert.c b/builtin/revert.c index f84c253f4c..ee2a0807f0 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -141,7 +141,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) argc = parse_options(argc, argv, NULL, options, usage_str, PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -246,6 +246,9 @@ int cmd_revert(int argc, const char **argv, const char *prefix) res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); + if (opts.revs) + release_revisions(opts.revs); + free(opts.revs); return res; } diff --git a/builtin/rm.c b/builtin/rm.c index 84a935a16e..b6ba859fe4 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -287,6 +287,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) setup_work_tree(); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache() < 0) @@ -296,8 +298,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen = xcalloc(pathspec.nr, 1); - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); + if (pathspec_needs_expanded_index(&the_index, &pathspec)) + ensure_full_index(&the_index); + for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 086dfee45a..7a1e1fe7c0 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -381,6 +381,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) break; case PARSE_OPT_HELP: case PARSE_OPT_ERROR: + case PARSE_OPT_SUBCOMMAND: exit(129); case PARSE_OPT_COMPLETE: exit(0); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 64c649c6a2..d3f5715e3e 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -404,7 +404,7 @@ static int append_ref(const char *refname, const struct object_id *oid, } static int append_head_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { struct object_id tmp; int ofs = 11; @@ -419,7 +419,7 @@ static int append_head_ref(const char *refname, const struct object_id *oid, } static int append_remote_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { struct object_id tmp; int ofs = 13; @@ -434,7 +434,7 @@ static int append_remote_ref(const char *refname, const struct object_id *oid, } static int append_tag_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { if (!starts_with(refname, "refs/tags/")) return 0; diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 5fa207a044..4856906108 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -47,7 +47,7 @@ static void show_one(const char *refname, const struct object_id *oid) } static int show_ref(const char *refname, const struct object_id *oid, - int flag, void *cbdata) + int flag UNUSED, void *cbdata UNUSED) { if (show_head && !strcmp(refname, "HEAD")) goto match; @@ -77,8 +77,9 @@ match: return 0; } -static int add_existing(const char *refname, const struct object_id *oid, - int flag, void *cbdata) +static int add_existing(const char *refname, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; string_list_insert(list, refname); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index f91e29b56a..287716db68 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -48,7 +48,7 @@ static char const * const builtin_sparse_checkout_list_usage[] = { NULL }; -static int sparse_checkout_list(int argc, const char **argv) +static int sparse_checkout_list(int argc, const char **argv, const char *prefix) { static struct option builtin_sparse_checkout_list_options[] = { OPT_END(), @@ -60,7 +60,7 @@ static int sparse_checkout_list(int argc, const char **argv) if (!core_apply_sparse_checkout) die(_("this worktree is not sparse")); - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_list_options, builtin_sparse_checkout_list_usage, 0); @@ -431,7 +431,7 @@ static struct sparse_checkout_init_opts { int sparse_index; } init_opts; -static int sparse_checkout_init(int argc, const char **argv) +static int sparse_checkout_init(int argc, const char **argv, const char *prefix) { struct pattern_list pl; char *sparse_filename; @@ -452,7 +452,7 @@ static int sparse_checkout_init(int argc, const char **argv) init_opts.cone_mode = -1; init_opts.sparse_index = -1; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_init_options, builtin_sparse_checkout_init_usage, 0); @@ -767,7 +767,7 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_add_options, builtin_sparse_checkout_add_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); sanitize_paths(argc, argv, prefix, add_opts.skip_checks); @@ -813,7 +813,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_set_options, builtin_sparse_checkout_set_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index)) return 1; @@ -843,7 +843,8 @@ static struct sparse_checkout_reapply_opts { int sparse_index; } reapply_opts; -static int sparse_checkout_reapply(int argc, const char **argv) +static int sparse_checkout_reapply(int argc, const char **argv, + const char *prefix) { static struct option builtin_sparse_checkout_reapply_options[] = { OPT_BOOL(0, "cone", &reapply_opts.cone_mode, @@ -859,7 +860,7 @@ static int sparse_checkout_reapply(int argc, const char **argv) reapply_opts.cone_mode = -1; reapply_opts.sparse_index = -1; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_reapply_options, builtin_sparse_checkout_reapply_usage, 0); @@ -876,7 +877,8 @@ static char const * const builtin_sparse_checkout_disable_usage[] = { NULL }; -static int sparse_checkout_disable(int argc, const char **argv) +static int sparse_checkout_disable(int argc, const char **argv, + const char *prefix) { static struct option builtin_sparse_checkout_disable_options[] = { OPT_END(), @@ -895,7 +897,7 @@ static int sparse_checkout_disable(int argc, const char **argv) * forcibly return to a dense checkout regardless of initial state. */ - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_disable_options, builtin_sparse_checkout_disable_usage, 0); @@ -922,39 +924,25 @@ static int sparse_checkout_disable(int argc, const char **argv) int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) { - static struct option builtin_sparse_checkout_options[] = { + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_sparse_checkout_options[] = { + OPT_SUBCOMMAND("list", &fn, sparse_checkout_list), + OPT_SUBCOMMAND("init", &fn, sparse_checkout_init), + OPT_SUBCOMMAND("set", &fn, sparse_checkout_set), + OPT_SUBCOMMAND("add", &fn, sparse_checkout_add), + OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply), + OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable), OPT_END(), }; - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(builtin_sparse_checkout_usage, - builtin_sparse_checkout_options); - argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_options, - builtin_sparse_checkout_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + builtin_sparse_checkout_usage, 0); git_config(git_default_config, NULL); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; - if (argc > 0) { - if (!strcmp(argv[0], "list")) - return sparse_checkout_list(argc, argv); - if (!strcmp(argv[0], "init")) - return sparse_checkout_init(argc, argv); - if (!strcmp(argv[0], "set")) - return sparse_checkout_set(argc, argv, prefix); - if (!strcmp(argv[0], "add")) - return sparse_checkout_add(argc, argv, prefix); - if (!strcmp(argv[0], "reapply")) - return sparse_checkout_reapply(argc, argv); - if (!strcmp(argv[0], "disable")) - return sparse_checkout_disable(argc, argv); - } - - usage_with_options(builtin_sparse_checkout_usage, - builtin_sparse_checkout_options); + return fn(argc, argv, prefix); } diff --git a/builtin/stash.c b/builtin/stash.c index 30fa101460..2274aae255 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -638,9 +638,12 @@ cleanup: return ret; } -static int reject_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) +static int reject_reflog_ent(struct object_id *ooid UNUSED, + struct object_id *noid UNUSED, + const char *email UNUSED, + timestamp_t timestamp UNUSED, + int tz UNUSED, const char *message UNUSED, + void *cb_data UNUSED) { return 1; } @@ -782,7 +785,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_list_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (!ref_exists(ref_stash)) return 0; @@ -873,7 +876,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); argc = parse_options(argc, argv, prefix, options, git_stash_show_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); strvec_push(&revision_args, argv[0]); @@ -979,7 +982,7 @@ static int store_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_store_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (argc != 1) { if (!quiet) @@ -1739,6 +1742,11 @@ static int push_stash(int argc, const char **argv, const char *prefix, include_untracked, only_staged); } +static int push_stash_unassumed(int argc, const char **argv, const char *prefix) +{ + return push_stash(argc, argv, prefix, 0); +} + static int save_stash(int argc, const char **argv, const char *prefix) { int keep_index = -1; @@ -1787,15 +1795,28 @@ int cmd_stash(int argc, const char **argv, const char *prefix) pid_t pid = getpid(); const char *index_file; struct strvec args = STRVEC_INIT; - + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("apply", &fn, apply_stash), + OPT_SUBCOMMAND("clear", &fn, clear_stash), + OPT_SUBCOMMAND("drop", &fn, drop_stash), + OPT_SUBCOMMAND("pop", &fn, pop_stash), + OPT_SUBCOMMAND("branch", &fn, branch_stash), + OPT_SUBCOMMAND("list", &fn, list_stash), + OPT_SUBCOMMAND("show", &fn, show_stash), + OPT_SUBCOMMAND("store", &fn, store_stash), + OPT_SUBCOMMAND("create", &fn, create_stash), + OPT_SUBCOMMAND("push", &fn, push_stash_unassumed), + OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE), OPT_END() }; git_config(git_stash_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_usage, - PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + PARSE_OPT_SUBCOMMAND_OPTIONAL | + PARSE_OPT_KEEP_UNKNOWN_OPT | + PARSE_OPT_KEEP_DASHDASH); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -1804,33 +1825,10 @@ int cmd_stash(int argc, const char **argv, const char *prefix) strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); - if (!argc) - return !!push_stash(0, NULL, prefix, 0); - else if (!strcmp(argv[0], "apply")) - return !!apply_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "clear")) - return !!clear_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "drop")) - return !!drop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "pop")) - return !!pop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "branch")) - return !!branch_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "list")) - return !!list_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "show")) - return !!show_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "store")) - return !!store_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "create")) - return !!create_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "push")) - return !!push_stash(argc, argv, prefix, 0); - else if (!strcmp(argv[0], "save")) - return !!save_stash(argc, argv, prefix); - else if (*argv[0] != '-') - usage_msg_optf(_("unknown subcommand: %s"), - git_stash_usage, options, argv[0]); + if (fn) + return !!fn(argc, argv, prefix); + else if (!argc) + return !!push_stash_unassumed(0, NULL, prefix); /* Assume 'stash push' */ strvec_push(&args, "push"); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c597df7528..0b4acb442b 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -31,45 +31,61 @@ typedef void (*each_submodule_fn)(const struct cache_entry *list_item, void *cb_data); -static char *repo_get_default_remote(struct repository *repo) +static int repo_get_default_remote(struct repository *repo, char **default_remote) { - char *dest = NULL, *ret; + char *dest = NULL; struct strbuf sb = STRBUF_INIT; struct ref_store *store = get_main_ref_store(repo); const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL, NULL); if (!refname) - die(_("No such ref: %s"), "HEAD"); + return die_message(_("No such ref: %s"), "HEAD"); /* detached HEAD */ - if (!strcmp(refname, "HEAD")) - return xstrdup("origin"); + if (!strcmp(refname, "HEAD")) { + *default_remote = xstrdup("origin"); + return 0; + } if (!skip_prefix(refname, "refs/heads/", &refname)) - die(_("Expecting a full ref name, got %s"), refname); + return die_message(_("Expecting a full ref name, got %s"), + refname); strbuf_addf(&sb, "branch.%s.remote", refname); if (repo_config_get_string(repo, sb.buf, &dest)) - ret = xstrdup("origin"); + *default_remote = xstrdup("origin"); else - ret = dest; + *default_remote = dest; strbuf_release(&sb); - return ret; + return 0; } -static char *get_default_remote_submodule(const char *module_path) +static int get_default_remote_submodule(const char *module_path, char **default_remote) { struct repository subrepo; + int ret; - repo_submodule_init(&subrepo, the_repository, module_path, null_oid()); - return repo_get_default_remote(&subrepo); + if (repo_submodule_init(&subrepo, the_repository, module_path, + null_oid()) < 0) + return die_message(_("could not get a repository handle for submodule '%s'"), + module_path); + ret = repo_get_default_remote(&subrepo, default_remote); + repo_clear(&subrepo); + + return ret; } static char *get_default_remote(void) { - return repo_get_default_remote(the_repository); + char *default_remote; + int code = repo_get_default_remote(the_repository, &default_remote); + + if (code) + exit(code); + + return default_remote; } static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet) @@ -96,32 +112,11 @@ static char *resolve_relative_url(const char *rel_url, const char *up_path, int return resolved_url; } -static int resolve_relative_url_test(int argc, const char **argv, const char *prefix) +/* the result should be freed by the caller. */ +static char *get_submodule_displaypath(const char *path, const char *prefix) { - char *remoteurl, *res; - const char *up_path, *url; - - if (argc != 4) - die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>"); - - up_path = argv[1]; - remoteurl = xstrdup(argv[2]); - url = argv[3]; - - if (!strcmp(up_path, "(null)")) - up_path = NULL; - - res = relative_url(remoteurl, url, up_path); - puts(res); - free(res); - free(remoteurl); - return 0; -} + const char *super_prefix = get_super_prefix(); -static char *do_get_submodule_displaypath(const char *path, - const char *prefix, - const char *super_prefix) -{ if (prefix && super_prefix) { BUG("cannot have prefix '%s' and superprefix '%s'", prefix, super_prefix); @@ -137,13 +132,6 @@ static char *do_get_submodule_displaypath(const char *path, } } -/* the result should be freed by the caller. */ -static char *get_submodule_displaypath(const char *path, const char *prefix) -{ - const char *super_prefix = get_super_prefix(); - return do_get_submodule_displaypath(path, prefix, super_prefix); -} - static char *compute_rev_name(const char *sub_path, const char* object_id) { struct strbuf sb = STRBUF_INIT; @@ -188,6 +176,11 @@ struct module_list { }; #define MODULE_LIST_INIT { 0 } +static void module_list_release(struct module_list *ml) +{ + free(ml->entries); +} + static int module_list_compute(int argc, const char **argv, const char *prefix, struct pathspec *pathspec, @@ -195,6 +188,7 @@ static int module_list_compute(int argc, const char **argv, { int i, result = 0; char *ps_matched = NULL; + parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL, prefix, argv); @@ -249,7 +243,7 @@ static void module_list_active(struct module_list *list) active_modules.entries[active_modules.nr++] = ce; } - free(list->entries); + module_list_release(list); *list = active_modules; } @@ -272,49 +266,11 @@ static char *get_up_path(const char *path) return strbuf_detach(&sb, NULL); } -static int module_list(int argc, const char **argv, const char *prefix) -{ - int i; - struct pathspec pathspec; - struct module_list list = MODULE_LIST_INIT; - - struct option module_list_options[] = { - OPT_STRING(0, "prefix", &prefix, - N_("path"), - N_("alternative anchor for relative paths")), - OPT_END() - }; - - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper list [--prefix=<path>] [<path>...]"), - NULL - }; - - argc = parse_options(argc, argv, prefix, module_list_options, - git_submodule_helper_usage, 0); - - if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; - - for (i = 0; i < list.nr; i++) { - const struct cache_entry *ce = list.entries[i]; - - if (ce_stage(ce)) - printf("%06o %s U\t", ce->ce_mode, - oid_to_hex(null_oid())); - else - printf("%06o %s %d\t", ce->ce_mode, - oid_to_hex(&ce->oid), ce_stage(ce)); - - fprintf(stdout, "%s\n", ce->name); - } - return 0; -} - static void for_each_listed_submodule(const struct module_list *list, each_submodule_fn fn, void *cb_data) { int i; + for (i = 0; i < list->nr; i++) fn(list->entries[i], cb_data); } @@ -334,7 +290,6 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, struct foreach_cb *info = cb_data; const char *path = list_item->name; const struct object_id *ce_oid = &list_item->oid; - const struct submodule *sub; struct child_process cp = CHILD_PROCESS_INIT; char *displaypath; @@ -433,26 +388,25 @@ cleanup: static int module_foreach(int argc, const char **argv, const char *prefix) { struct foreach_cb info = FOREACH_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; 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_BOOL(0, "recursive", &info.recursive, N_("recurse into nested submodules")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper foreach [--quiet] [--recursive] [--] <command>"), + N_("git submodule foreach [--quiet] [--recursive] [--] <command>"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_foreach_options, git_submodule_helper_usage, 0); if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.argc = argc; info.argv = argv; @@ -460,7 +414,11 @@ static int module_foreach(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } static int starts_with_dot_slash(const char *const path) @@ -477,22 +435,19 @@ static int starts_with_dot_dot_slash(const char *const path) struct init_cb { const char *prefix; - const char *superprefix; unsigned int flags; }; #define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, - const char *superprefix, unsigned int flags) + unsigned int flags) { const struct submodule *sub; struct strbuf sb = STRBUF_INIT; - char *upd = NULL, *url = NULL, *displaypath; + const char *upd; + char *url = NULL, *displaypath; - /* try superprefix from the environment, if it is not passed explicitly */ - if (!superprefix) - superprefix = get_super_prefix(); - displaypath = do_get_submodule_displaypath(path, prefix, superprefix); + displaypath = get_submodule_displaypath(path, prefix); sub = submodule_from_path(the_repository, null_oid(), path); @@ -529,6 +484,7 @@ static void init_submodule(const char *path, const char *prefix, if (starts_with_dot_dot_slash(url) || starts_with_dot_slash(url)) { char *oldurl = url; + url = resolve_relative_url(oldurl, NULL, 0); free(oldurl); } @@ -545,14 +501,15 @@ static void init_submodule(const char *path, const char *prefix, /* Copy "update" setting when it is not set yet */ strbuf_addf(&sb, "submodule.%s.update", sub->name); - if (git_config_get_string(sb.buf, &upd) && + if (git_config_get_string_tmp(sb.buf, &upd) && sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { if (sub->update_strategy.type == SM_UPDATE_COMMAND) { fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"), sub->name); - upd = xstrdup("none"); - } else - upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy)); + upd = "none"; + } else { + upd = submodule_update_type_to_string(sub->update_strategy.type); + } if (git_config_set_gently(sb.buf, upd)) die(_("Failed to register update mode for submodule path '%s'"), displaypath); @@ -560,37 +517,36 @@ static void init_submodule(const char *path, const char *prefix, strbuf_release(&sb); free(displaypath); free(url); - free(upd); } static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct init_cb *info = cb_data; - init_submodule(list_item->name, info->prefix, info->superprefix, info->flags); + + init_submodule(list_item->name, info->prefix, info->flags); } static int module_init(int argc, const char **argv, const char *prefix) { struct init_cb info = INIT_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; - struct option module_init_options[] = { OPT__QUIET(&quiet, N_("suppress output for initializing a submodule")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper init [<options>] [<path>]"), + N_("git submodule init [<options>] [<path>]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_init_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; /* * If there are no path args and submodule.active is set then, @@ -605,7 +561,11 @@ static int module_init(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, init_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct status_cb { @@ -623,20 +583,23 @@ static void print_status(unsigned int flags, char state, const char *path, printf("%c%s %s", state, oid_to_hex(oid), displaypath); if (state == ' ' || state == '+') { - const char *name = compute_rev_name(path, oid_to_hex(oid)); + char *name = compute_rev_name(path, oid_to_hex(oid)); if (name) printf(" (%s)", name); + free(name); } printf("\n"); } -static int handle_submodule_head_ref(const char *refname, - const struct object_id *oid, int flags, +static int handle_submodule_head_ref(const char *refname UNUSED, + const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct object_id *output = cb_data; + if (oid) oidcpy(output, oid); @@ -743,6 +706,7 @@ static void status_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct status_cb *info = cb_data; + status_submodule(list_item->name, &list_item->oid, list_item->ce_flags, info->prefix, info->flags); } @@ -750,27 +714,26 @@ static void status_submodule_cb(const struct cache_entry *list_item, static int module_status(int argc, const char **argv, const char *prefix) { struct status_cb info = STATUS_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; 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_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule status [--quiet] [--cached] [--recursive] [<path>...]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_status_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.prefix = prefix; if (quiet) @@ -778,25 +741,11 @@ static int module_status(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, status_submodule_cb, &info); - return 0; -} - -static int module_name(int argc, const char **argv, const char *prefix) -{ - const struct submodule *sub; - - if (argc != 2) - usage(_("git submodule--helper name <path>")); - - sub = submodule_from_path(the_repository, null_oid(), argv[1]); - - if (!sub) - die(_("no submodule mapping found in .gitmodules for path '%s'"), - argv[1]); - - printf("%s\n", sub->name); - - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct module_cb { @@ -805,16 +754,34 @@ struct module_cb { struct object_id oid_src; struct object_id oid_dst; char status; - const char *sm_path; + char *sm_path; }; #define MODULE_CB_INIT { 0 } +static void module_cb_release(struct module_cb *mcb) +{ + free(mcb->sm_path); +} + struct module_cb_list { struct module_cb **entries; int alloc, nr; }; #define MODULE_CB_LIST_INIT { 0 } +static void module_cb_list_release(struct module_cb_list *mcbl) +{ + int i; + + for (i = 0; i < mcbl->nr; i++) { + struct module_cb *mcb = mcbl->entries[i]; + + module_cb_release(mcb); + free(mcb); + } + free(mcbl->entries); +} + struct summary_cb { int argc; const char **argv; @@ -851,7 +818,7 @@ static char *verify_submodule_committish(const char *sm_path, return strbuf_detach(&result, NULL); } -static void print_submodule_summary(struct summary_cb *info, char *errmsg, +static void print_submodule_summary(struct summary_cb *info, const char *errmsg, int total_commits, const char *displaypath, const char *src_abbrev, const char *dst_abbrev, struct module_cb *p) @@ -909,12 +876,13 @@ static void generate_submodule_summary(struct summary_cb *info, { char *displaypath, *src_abbrev = NULL, *dst_abbrev; int missing_src = 0, missing_dst = 0; - char *errmsg = NULL; + struct strbuf errmsg = STRBUF_INIT; int total_commits = -1; if (!info->cached && oideq(&p->oid_dst, null_oid())) { if (S_ISGITLINK(p->mod_dst)) { struct ref_store *refs = get_submodule_ref_store(p->sm_path); + if (refs) refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst); } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) { @@ -1009,28 +977,27 @@ static void generate_submodule_summary(struct summary_cb *info, * submodule, i.e., deleted or changed to blob */ if (S_ISGITLINK(p->mod_dst)) { - struct strbuf errmsg_str = STRBUF_INIT; if (missing_src && missing_dst) { - strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commits %s and %s\n", + strbuf_addf(&errmsg, " Warn: %s doesn't contain commits %s and %s\n", displaypath, oid_to_hex(&p->oid_src), oid_to_hex(&p->oid_dst)); } else { - strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commit %s\n", + strbuf_addf(&errmsg, " Warn: %s doesn't contain commit %s\n", displaypath, missing_src ? oid_to_hex(&p->oid_src) : oid_to_hex(&p->oid_dst)); } - errmsg = strbuf_detach(&errmsg_str, NULL); } } - print_submodule_summary(info, errmsg, total_commits, - displaypath, src_abbrev, + print_submodule_summary(info, errmsg.len ? errmsg.buf : NULL, + total_commits, displaypath, src_abbrev, dst_abbrev, p); free(displaypath); free(src_abbrev); free(dst_abbrev); + strbuf_release(&errmsg); } static void prepare_submodule_summary(struct summary_cb *info, @@ -1114,6 +1081,9 @@ static int compute_summary_module_list(struct object_id *head_oid, { struct strvec diff_args = STRVEC_INIT; struct rev_info rev; + struct setup_revision_opt opt = { + .free_removed_argv_elements = 1, + }; struct module_cb_list list = MODULE_CB_LIST_INIT; int ret = 0; @@ -1131,7 +1101,7 @@ static int compute_summary_module_list(struct object_id *head_oid, init_revisions(&rev, info->prefix); rev.abbrev = 0; precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); - setup_revisions(diff_args.nr, diff_args.v, &rev, NULL); + setup_revisions(diff_args.nr, diff_args.v, &rev, &opt); rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = submodule_summary_callback; rev.diffopt.format_callback_data = &list; @@ -1158,6 +1128,7 @@ static int compute_summary_module_list(struct object_id *head_oid, cleanup: strvec_clear(&diff_args); release_revisions(&rev); + module_cb_list_release(&list); return ret; } @@ -1171,7 +1142,6 @@ static int module_summary(int argc, const char **argv, const char *prefix) enum diff_cmd diff_cmd = DIFF_INDEX; struct object_id head_oid; int ret; - struct option module_summary_options[] = { OPT_BOOL(0, "cached", &cached, N_("use the commit stored in the index instead of the submodule HEAD")), @@ -1183,9 +1153,8 @@ static int module_summary(int argc, const char **argv, const char *prefix) N_("limit the summary size")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper summary [<options>] [<commit>] [--] [<path>]"), + N_("git submodule summary [<options>] [<commit>] [--] [<path>]"), NULL }; @@ -1245,6 +1214,7 @@ static void sync_submodule(const char *path, const char *prefix, char *sub_origin_url, *super_config_url, *displaypath, *default_remote; struct strbuf sb = STRBUF_INIT; char *sub_config_path = NULL; + int code; if (!is_submodule_active(the_repository, path)) return; @@ -1255,6 +1225,7 @@ static void sync_submodule(const char *path, const char *prefix, if (starts_with_dot_dot_slash(sub->url) || starts_with_dot_slash(sub->url)) { char *up_path = get_up_path(path); + sub_origin_url = resolve_relative_url(sub->url, up_path, 1); super_config_url = resolve_relative_url(sub->url, NULL, 1); free(up_path); @@ -1283,10 +1254,9 @@ static void sync_submodule(const char *path, const char *prefix, goto cleanup; strbuf_reset(&sb); - default_remote = get_default_remote_submodule(path); - if (!default_remote) - die(_("failed to get the default remote for submodule '%s'"), - path); + code = get_default_remote_submodule(path, &default_remote); + if (code) + exit(code); remote_key = xstrfmt("remote.%s.url", default_remote); free(default_remote); @@ -1330,34 +1300,34 @@ cleanup: static void sync_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct sync_cb *info = cb_data; + sync_submodule(list_item->name, info->prefix, info->flags); } static int module_sync(int argc, const char **argv, const char *prefix) { struct sync_cb info = SYNC_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; int recursive = 0; - struct option module_sync_options[] = { OPT__QUIET(&quiet, N_("suppress output of synchronizing submodule url")), OPT_BOOL(0, "recursive", &recursive, N_("recurse into nested submodules")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper sync [--quiet] [--recursive] [<path>]"), + N_("git submodule sync [--quiet] [--recursive] [<path>]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_sync_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.prefix = prefix; if (quiet) @@ -1367,7 +1337,11 @@ static int module_sync(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, sync_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct deinit_cb { @@ -1411,6 +1385,7 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_FORCE)) { struct child_process cp_rm = CHILD_PROCESS_INIT; + cp_rm.git_cmd = 1; strvec_pushl(&cp_rm.args, "rm", "-qn", path, NULL); @@ -1447,6 +1422,7 @@ static void deinit_submodule(const char *path, const char *prefix, /* remove the .git/config entries (unless the user already did it) */ if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) { char *sub_key = xstrfmt("submodule.%s", sub->name); + /* * remove the whole section so we have a clean state when * the user later decides to init this submodule again @@ -1474,23 +1450,22 @@ static void deinit_submodule_cb(const struct cache_entry *list_item, static int module_deinit(int argc, const char **argv, const char *prefix) { struct deinit_cb info = DEINIT_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; int force = 0; 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_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule deinit [--quiet] [-f | --force] [--all | [--] [<path>...]]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_deinit_options, git_submodule_helper_usage, 0); @@ -1505,7 +1480,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix) die(_("Use '--all' if you really want to deinitialize all submodules")); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.prefix = prefix; if (quiet) @@ -1515,7 +1490,11 @@ static int module_deinit(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, deinit_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct module_clone_data { @@ -1525,7 +1504,6 @@ struct module_clone_data { const char *url; const char *depth; struct list_objects_filter_options *filter_options; - struct string_list reference; unsigned int quiet: 1; unsigned int progress: 1; unsigned int dissociate: 1; @@ -1533,7 +1511,6 @@ struct module_clone_data { int single_branch; }; #define MODULE_CLONE_DATA_INIT { \ - .reference = STRING_LIST_INIT_NODUP, \ .single_branch = -1, \ } @@ -1574,7 +1551,9 @@ static int add_possible_reference_from_superproject( struct strbuf err = STRBUF_INIT; strbuf_add(&sb, odb->path, len); - repo_init(&alternate, sb.buf, NULL); + if (repo_init(&alternate, sb.buf, NULL) < 0) + die(_("could not get a repository handle for gitdir '%s'"), + sb.buf); /* * We need to end the new path with '/' to mark it as a dir, @@ -1589,7 +1568,9 @@ static int add_possible_reference_from_superproject( sm_alternate = compute_alternate_path(sb.buf, &err); if (sm_alternate) { - string_list_append(sas->reference, xstrdup(sb.buf)); + char *p = strbuf_detach(&sb, NULL); + + string_list_append(sas->reference, p)->util = p; free(sm_alternate); } else { switch (sas->error_mode) { @@ -1648,23 +1629,31 @@ static void prepare_possible_alternates(const char *sm_name, free(error_strategy); } -static int clone_submodule(struct module_clone_data *clone_data) +static char *clone_submodule_sm_gitdir(const char *name) { - char *p, *sm_gitdir; - char *sm_alternate = NULL, *error_strategy = NULL; struct strbuf sb = STRBUF_INIT; - struct child_process cp = CHILD_PROCESS_INIT; + char *sm_gitdir; - submodule_name_to_gitdir(&sb, the_repository, clone_data->name); + submodule_name_to_gitdir(&sb, the_repository, name); sm_gitdir = absolute_pathdup(sb.buf); - strbuf_reset(&sb); + strbuf_release(&sb); - if (!is_absolute_path(clone_data->path)) { - strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path); - clone_data->path = strbuf_detach(&sb, NULL); - } else { - clone_data->path = xstrdup(clone_data->path); - } + return sm_gitdir; +} + +static int clone_submodule(const struct module_clone_data *clone_data, + struct string_list *reference) +{ + char *p; + char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name); + char *sm_alternate = NULL, *error_strategy = NULL; + struct child_process cp = CHILD_PROCESS_INIT; + const char *clone_data_path = clone_data->path; + char *to_free = NULL; + + if (!is_absolute_path(clone_data->path)) + clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(), + clone_data->path); if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) die(_("refusing to create/use '%s' in another submodule's " @@ -1674,7 +1663,7 @@ static int clone_submodule(struct module_clone_data *clone_data) if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); - prepare_possible_alternates(clone_data->name, &clone_data->reference); + prepare_possible_alternates(clone_data->name, reference); strvec_push(&cp.args, "clone"); strvec_push(&cp.args, "--no-checkout"); @@ -1684,9 +1673,10 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, "--progress"); if (clone_data->depth && *(clone_data->depth)) strvec_pushl(&cp.args, "--depth", clone_data->depth, NULL); - if (clone_data->reference.nr) { + if (reference->nr) { struct string_list_item *item; - for_each_string_list_item(item, &clone_data->reference) + + for_each_string_list_item(item, reference) strvec_pushl(&cp.args, "--reference", item->string, NULL); } @@ -1705,7 +1695,7 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, "--"); strvec_push(&cp.args, clone_data->url); - strvec_push(&cp.args, clone_data->path); + strvec_push(&cp.args, clone_data_path); cp.git_cmd = 1; prepare_submodule_repo_env(&cp.env); @@ -1713,23 +1703,25 @@ static int clone_submodule(struct module_clone_data *clone_data) if(run_command(&cp)) die(_("clone of '%s' into submodule path '%s' failed"), - clone_data->url, clone_data->path); + clone_data->url, clone_data_path); } else { - if (clone_data->require_init && !access(clone_data->path, X_OK) && - !is_empty_dir(clone_data->path)) - die(_("directory not empty: '%s'"), clone_data->path); - if (safe_create_leading_directories_const(clone_data->path) < 0) - die(_("could not create directory '%s'"), clone_data->path); - strbuf_addf(&sb, "%s/index", sm_gitdir); - unlink_or_warn(sb.buf); - strbuf_reset(&sb); + char *path; + + if (clone_data->require_init && !access(clone_data_path, X_OK) && + !is_empty_dir(clone_data_path)) + die(_("directory not empty: '%s'"), clone_data_path); + if (safe_create_leading_directories_const(clone_data_path) < 0) + die(_("could not create directory '%s'"), clone_data_path); + path = xstrfmt("%s/index", sm_gitdir); + unlink_or_warn(path); + free(path); } - connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0); + connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0); - p = git_pathdup_submodule(clone_data->path, "config"); + p = git_pathdup_submodule(clone_data_path, "config"); if (!p) - die(_("could not get submodule directory for '%s'"), clone_data->path); + die(_("could not get submodule directory for '%s'"), clone_data_path); /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ git_config_get_string("submodule.alternateLocation", &sm_alternate); @@ -1744,9 +1736,9 @@ static int clone_submodule(struct module_clone_data *clone_data) free(sm_alternate); free(error_strategy); - strbuf_release(&sb); free(sm_gitdir); free(p); + free(to_free); return 0; } @@ -1754,7 +1746,9 @@ static int module_clone(int argc, const char **argv, const char *prefix) { int dissociate = 0, quiet = 0, progress = 0, require_init = 0; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; - struct list_objects_filter_options filter_options; + struct string_list reference = STRING_LIST_INIT_NODUP; + struct list_objects_filter_options filter_options = + LIST_OBJECTS_FILTER_INIT; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &clone_data.prefix, @@ -1769,7 +1763,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_STRING(0, "url", &clone_data.url, N_("string"), N_("url where to clone the submodule from")), - OPT_STRING_LIST(0, "reference", &clone_data.reference, + OPT_STRING_LIST(0, "reference", &reference, N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &dissociate, @@ -1787,7 +1781,6 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=<path>] [--quiet] " "[--reference <repository>] [--name <name>] [--depth <depth>] " @@ -1796,7 +1789,6 @@ static int module_clone(int argc, const char **argv, const char *prefix) NULL }; - memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); @@ -1810,31 +1802,33 @@ static int module_clone(int argc, const char **argv, const char *prefix) usage_with_options(git_submodule_helper_usage, module_clone_options); - clone_submodule(&clone_data); + clone_submodule(&clone_data, &reference); list_objects_filter_release(&filter_options); + string_list_clear(&reference, 1); return 0; } -static void determine_submodule_update_strategy(struct repository *r, - int just_cloned, - const char *path, - const char *update, - struct submodule_update_strategy *out) +static int determine_submodule_update_strategy(struct repository *r, + int just_cloned, + const char *path, + enum submodule_update_type update, + struct submodule_update_strategy *out) { const struct submodule *sub = submodule_from_path(r, null_oid(), path); char *key; const char *val; + int ret; key = xstrfmt("submodule.%s.update", sub->name); if (update) { - if (parse_submodule_update_strategy(update, out) < 0) - die(_("Invalid update mode '%s' for submodule path '%s'"), - update, path); + out->type = update; } else if (!repo_config_get_string_tmp(r, key, &val)) { - if (parse_submodule_update_strategy(val, out) < 0) - die(_("Invalid update mode '%s' configured for submodule path '%s'"), - val, path); + if (parse_submodule_update_strategy(val, out) < 0) { + ret = die_message(_("Invalid update mode '%s' configured for submodule path '%s'"), + val, path); + goto cleanup; + } } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { if (sub->update_strategy.type == SM_UPDATE_COMMAND) BUG("how did we read update = !command from .gitmodules?"); @@ -1849,7 +1843,10 @@ static void determine_submodule_update_strategy(struct repository *r, out->type == SM_UPDATE_NONE)) out->type = SM_UPDATE_CHECKOUT; + ret = 0; +cleanup: free(key); + return ret; } struct update_clone_data { @@ -1863,7 +1860,7 @@ struct submodule_update_clone { int current; /* configuration parameters which are passed on to the children */ - struct update_data *update_data; + const struct update_data *update_data; /* to be consumed by update_submodule() */ struct update_clone_data *update_clone; @@ -1878,11 +1875,16 @@ struct submodule_update_clone { }; #define SUBMODULE_UPDATE_CLONE_INIT { 0 } +static void submodule_update_clone_release(struct submodule_update_clone *suc) +{ + free(suc->update_clone); + free(suc->failed_clones); +} + struct update_data { const char *prefix; - const char *recursive_prefix; - const char *displaypath; - const char *update_default; + char *displaypath; + enum submodule_update_type update_default; struct object_id suboid; struct string_list references; struct submodule_update_strategy update_strategy; @@ -1917,6 +1919,12 @@ struct update_data { .max_jobs = 1, \ } +static void update_data_release(struct update_data *ud) +{ + free(ud->displaypath); + module_list_release(&ud->list); +} + static void next_submodule_warn_missing(struct submodule_update_clone *suc, struct strbuf *out, const char *displaypath) { @@ -1949,30 +1957,20 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, const char *update_string; enum submodule_update_type update_type; char *key; - struct strbuf displaypath_sb = STRBUF_INIT; + const struct update_data *ud = suc->update_data; + char *displaypath = get_submodule_displaypath(ce->name, ud->prefix); struct strbuf sb = STRBUF_INIT; - const char *displaypath = NULL; int needs_cloning = 0; int need_free_url = 0; if (ce_stage(ce)) { - if (suc->update_data->recursive_prefix) - strbuf_addf(&sb, "%s/%s", suc->update_data->recursive_prefix, ce->name); - else - strbuf_addstr(&sb, ce->name); - strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); + strbuf_addf(out, _("Skipping unmerged submodule %s"), displaypath); strbuf_addch(out, '\n'); goto cleanup; } sub = submodule_from_path(the_repository, null_oid(), ce->name); - if (suc->update_data->recursive_prefix) - displaypath = relative_path(suc->update_data->recursive_prefix, - ce->name, &displaypath_sb); - else - displaypath = ce->name; - if (!sub) { next_submodule_warn_missing(suc, out, displaypath); goto cleanup; @@ -2051,6 +2049,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strvec_pushl(&child->args, "--url", url, NULL); if (suc->update_data->references.nr) { struct string_list_item *item; + for_each_string_list_item(item, &suc->update_data->references) strvec_pushl(&child->args, "--reference", item->string, NULL); } @@ -2062,7 +2061,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, "--no-single-branch"); cleanup: - strbuf_release(&displaypath_sb); + free(displaypath); strbuf_release(&sb); if (need_free_url) free((void*)url); @@ -2083,6 +2082,7 @@ static int update_clone_get_next_task(struct child_process *child, ce = suc->update_data->list.entries[suc->current]; if (prepare_to_clone_next_submodule(ce, child, suc, err)) { int *p = xmalloc(sizeof(*p)); + *p = suc->current; *idx_task_cb = p; suc->current++; @@ -2098,6 +2098,7 @@ static int update_clone_get_next_task(struct child_process *child, index = suc->current - suc->update_data->list.nr; if (index < suc->failed_clones_nr) { int *p; + ce = suc->failed_clones[index]; if (!prepare_to_clone_next_submodule(ce, child, suc, err)) { suc->current ++; @@ -2121,6 +2122,7 @@ static int update_clone_start_failure(struct strbuf *err, void *idx_task_cb) { struct submodule_update_clone *suc = suc_cb; + suc->quickstop = 1; return 1; } @@ -2132,9 +2134,9 @@ static int update_clone_task_finished(int result, { const struct cache_entry *ce; struct submodule_update_clone *suc = suc_cb; - int *idxP = idx_task_cb; int idx = *idxP; + free(idxP); if (!result) @@ -2167,19 +2169,20 @@ static int git_update_clone_config(const char *var, const char *value, void *cb) { int *max_jobs = cb; + if (!strcmp(var, "submodule.fetchjobs")) *max_jobs = parse_submodule_fetchjobs(var, value); return 0; } -static int is_tip_reachable(const char *path, struct object_id *oid) +static int is_tip_reachable(const char *path, const struct object_id *oid) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf rev = STRBUF_INIT; char *hex = oid_to_hex(oid); cp.git_cmd = 1; - cp.dir = xstrdup(path); + cp.dir = path; cp.no_stderr = 1; strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL); @@ -2191,13 +2194,14 @@ static int is_tip_reachable(const char *path, struct object_id *oid) return 1; } -static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid) +static int fetch_in_submodule(const char *module_path, int depth, int quiet, + const struct object_id *oid) { struct child_process cp = CHILD_PROCESS_INIT; prepare_submodule_repo_env(&cp.env); cp.git_cmd = 1; - cp.dir = xstrdup(module_path); + cp.dir = module_path; strvec_push(&cp.args, "fetch"); if (quiet) @@ -2207,6 +2211,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str if (oid) { char *hex = oid_to_hex(oid); char *remote = get_default_remote(); + strvec_pushl(&cp.args, remote, hex, NULL); free(remote); } @@ -2214,11 +2219,11 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str return run_command(&cp); } -static int run_update_command(struct update_data *ud, int subforce) +static int run_update_command(const struct update_data *ud, int subforce) { struct child_process cp = CHILD_PROCESS_INIT; char *oid = oid_to_hex(&ud->oid); - int must_die_on_failure = 0; + int ret; switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: @@ -2232,55 +2237,50 @@ static int run_update_command(struct update_data *ud, int subforce) strvec_push(&cp.args, "rebase"); if (ud->quiet) strvec_push(&cp.args, "--quiet"); - must_die_on_failure = 1; break; case SM_UPDATE_MERGE: cp.git_cmd = 1; strvec_push(&cp.args, "merge"); if (ud->quiet) strvec_push(&cp.args, "--quiet"); - must_die_on_failure = 1; break; case SM_UPDATE_COMMAND: cp.use_shell = 1; strvec_push(&cp.args, ud->update_strategy.command); - must_die_on_failure = 1; break; default: - BUG("unexpected update strategy type: %s", - submodule_strategy_to_string(&ud->update_strategy)); + BUG("unexpected update strategy type: %d", + ud->update_strategy.type); } strvec_push(&cp.args, oid); - cp.dir = xstrdup(ud->sm_path); + cp.dir = ud->sm_path; prepare_submodule_repo_env(&cp.env); - if (run_command(&cp)) { + if ((ret = run_command(&cp))) { switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: die_message(_("Unable to checkout '%s' in submodule path '%s'"), oid, ud->displaypath); + /* No "ret" assignment, use "git checkout"'s */ break; case SM_UPDATE_REBASE: - die_message(_("Unable to rebase '%s' in submodule path '%s'"), - oid, ud->displaypath); + ret = die_message(_("Unable to rebase '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_MERGE: - die_message(_("Unable to merge '%s' in submodule path '%s'"), - oid, ud->displaypath); + ret = die_message(_("Unable to merge '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_COMMAND: - die_message(_("Execution of '%s %s' failed in submodule path '%s'"), - ud->update_strategy.command, oid, ud->displaypath); + ret = die_message(_("Execution of '%s %s' failed in submodule path '%s'"), + ud->update_strategy.command, oid, ud->displaypath); break; default: - BUG("unexpected update strategy type: %s", - submodule_strategy_to_string(&ud->update_strategy)); + BUG("unexpected update strategy type: %d", + ud->update_strategy.type); } - if (must_die_on_failure) - exit(128); - /* the command failed, but update must continue */ - return 1; + return ret; } if (ud->quiet) @@ -2304,14 +2304,14 @@ static int run_update_command(struct update_data *ud, int subforce) ud->displaypath, ud->update_strategy.command, oid); break; default: - BUG("unexpected update strategy type: %s", - submodule_strategy_to_string(&ud->update_strategy)); + BUG("unexpected update strategy type: %d", + ud->update_strategy.type); } return 0; } -static int run_update_procedure(struct update_data *ud) +static int run_update_procedure(const struct update_data *ud) { int subforce = is_null_oid(&ud->suboid) || ud->force; @@ -2333,59 +2333,67 @@ static int run_update_procedure(struct update_data *ud) */ if (!is_tip_reachable(ud->sm_path, &ud->oid) && fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, &ud->oid)) - die(_("Fetched in submodule path '%s', but it did not " - "contain %s. Direct fetching of that commit failed."), - ud->displaypath, oid_to_hex(&ud->oid)); + return die_message(_("Fetched in submodule path '%s', but it did not " + "contain %s. Direct fetching of that commit failed."), + ud->displaypath, oid_to_hex(&ud->oid)); } return run_update_command(ud, subforce); } -static const char *remote_submodule_branch(const char *path) +static int remote_submodule_branch(const char *path, const char **branch) { const struct submodule *sub; - const char *branch = NULL; char *key; + *branch = NULL; sub = submodule_from_path(the_repository, null_oid(), path); if (!sub) - return NULL; + return die_message(_("could not initialize submodule at path '%s'"), + path); key = xstrfmt("submodule.%s.branch", sub->name); - if (repo_config_get_string_tmp(the_repository, key, &branch)) - branch = sub->branch; + if (repo_config_get_string_tmp(the_repository, key, branch)) + *branch = sub->branch; free(key); - if (!branch) - return "HEAD"; + if (!*branch) { + *branch = "HEAD"; + return 0; + } - if (!strcmp(branch, ".")) { + if (!strcmp(*branch, ".")) { const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!refname) - die(_("No such ref: %s"), "HEAD"); + return die_message(_("No such ref: %s"), "HEAD"); /* detached HEAD */ if (!strcmp(refname, "HEAD")) - die(_("Submodule (%s) branch configured to inherit " - "branch from superproject, but the superproject " - "is not on any branch"), sub->name); + return die_message(_("Submodule (%s) branch configured to inherit " + "branch from superproject, but the superproject " + "is not on any branch"), sub->name); if (!skip_prefix(refname, "refs/heads/", &refname)) - die(_("Expecting a full ref name, got %s"), refname); - return refname; + return die_message(_("Expecting a full ref name, got %s"), + refname); + + *branch = refname; + return 0; } - return branch; + /* Our "branch" is coming from repo_config_get_string_tmp() */ + return 0; } -static void ensure_core_worktree(const char *path) +static int ensure_core_worktree(const char *path) { const char *cw; struct repository subrepo; if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) - die(_("could not get a repository handle for submodule '%s'"), path); + return die_message(_("could not get a repository handle for submodule '%s'"), + path); if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) { char *cfg_file, *abs_path; @@ -2403,15 +2411,39 @@ static void ensure_core_worktree(const char *path) free(abs_path); strbuf_release(&sb); } + + repo_clear(&subrepo); + return 0; +} + +static const char *submodule_update_type_to_label(enum submodule_update_type type) +{ + switch (type) { + case SM_UPDATE_CHECKOUT: + return "checkout"; + case SM_UPDATE_MERGE: + return "merge"; + case SM_UPDATE_REBASE: + return "rebase"; + case SM_UPDATE_UNSPECIFIED: + case SM_UPDATE_NONE: + case SM_UPDATE_COMMAND: + break; + } + BUG("unreachable with type %d", type); } -static void update_data_to_args(struct update_data *update_data, struct strvec *args) +static void update_data_to_args(const struct update_data *update_data, + struct strvec *args) { + enum submodule_update_type update_type = update_data->update_default; + + if (update_data->displaypath) { + strvec_push(args, "--super-prefix"); + strvec_pushf(args, "%s/", update_data->displaypath); + } strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL); strvec_pushf(args, "--jobs=%d", update_data->max_jobs); - if (update_data->recursive_prefix) - strvec_pushl(args, "--recursive-prefix", - update_data->recursive_prefix, NULL); if (update_data->quiet) strvec_push(args, "--quiet"); if (update_data->force) @@ -2430,10 +2462,13 @@ static void update_data_to_args(struct update_data *update_data, struct strvec * strvec_push(args, "--require-init"); if (update_data->depth) strvec_pushf(args, "--depth=%d", update_data->depth); - if (update_data->update_default) - strvec_pushl(args, "--update", update_data->update_default, NULL); + if (update_type != SM_UPDATE_UNSPECIFIED) + strvec_pushf(args, "--%s", + submodule_update_type_to_label(update_type)); + if (update_data->references.nr) { struct string_list_item *item; + for_each_string_list_item(item, &update_data->references) strvec_pushl(args, "--reference", item->string, NULL); } @@ -2453,66 +2488,62 @@ static void update_data_to_args(struct update_data *update_data, struct strvec * static int update_submodule(struct update_data *update_data) { - char *prefixed_path; - - ensure_core_worktree(update_data->sm_path); - - if (update_data->recursive_prefix) - prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix, - update_data->sm_path); - else - prefixed_path = xstrdup(update_data->sm_path); - - update_data->displaypath = get_submodule_displaypath(prefixed_path, - update_data->prefix); - free(prefixed_path); + int ret; - determine_submodule_update_strategy(the_repository, update_data->just_cloned, - update_data->sm_path, update_data->update_default, - &update_data->update_strategy); + ret = determine_submodule_update_strategy(the_repository, + update_data->just_cloned, + update_data->sm_path, + update_data->update_default, + &update_data->update_strategy); + if (ret) + return ret; if (update_data->just_cloned) oidcpy(&update_data->suboid, null_oid()); else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid)) - die(_("Unable to find current revision in submodule path '%s'"), - update_data->displaypath); + return die_message(_("Unable to find current revision in submodule path '%s'"), + update_data->displaypath); if (update_data->remote) { - char *remote_name = get_default_remote_submodule(update_data->sm_path); - const char *branch = remote_submodule_branch(update_data->sm_path); - char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch); + char *remote_name; + const char *branch; + char *remote_ref; + int code; + + code = get_default_remote_submodule(update_data->sm_path, &remote_name); + if (code) + return code; + code = remote_submodule_branch(update_data->sm_path, &branch); + if (code) + return code; + remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch); + + free(remote_name); if (!update_data->nofetch) { if (fetch_in_submodule(update_data->sm_path, update_data->depth, 0, NULL)) - die(_("Unable to fetch in submodule path '%s'"), - update_data->sm_path); + return die_message(_("Unable to fetch in submodule path '%s'"), + update_data->sm_path); } if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid)) - die(_("Unable to find %s revision in submodule path '%s'"), - remote_ref, update_data->sm_path); + return die_message(_("Unable to find %s revision in submodule path '%s'"), + remote_ref, update_data->sm_path); free(remote_ref); } - if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) - if (run_update_procedure(update_data)) - return 1; + if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) { + ret = run_update_procedure(update_data); + if (ret) + return ret; + } if (update_data->recursive) { struct child_process cp = CHILD_PROCESS_INIT; struct update_data next = *update_data; - int res; - if (update_data->recursive_prefix) - prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix, - update_data->sm_path); - else - prefixed_path = xstrfmt("%s/", update_data->sm_path); - - next.recursive_prefix = get_submodule_displaypath(prefixed_path, - update_data->prefix); next.prefix = NULL; oidcpy(&next.oid, null_oid()); oidcpy(&next.suboid, null_oid()); @@ -2522,16 +2553,11 @@ static int update_submodule(struct update_data *update_data) prepare_submodule_repo_env(&cp.env); update_data_to_args(&next, &cp.args); - /* die() if child process die()'d */ - res = run_command(&cp); - if (!res) - return 0; - die_message(_("Failed to recurse into submodule path '%s'"), - update_data->displaypath); - if (res == 128) - exit(res); - else if (res) - return 1; + ret = run_command(&cp); + if (ret) + die_message(_("Failed to recurse into submodule path '%s'"), + update_data->displaypath); + return ret; } return 0; @@ -2539,7 +2565,7 @@ static int update_submodule(struct update_data *update_data) static int update_submodules(struct update_data *update_data) { - int i, res = 0; + int i, ret = 0; struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; suc.update_data = update_data; @@ -2557,33 +2583,48 @@ static int update_submodules(struct update_data *update_data) * - the listener can avoid doing any work if fetching failed. */ if (suc.quickstop) { - res = 1; + ret = 1; goto cleanup; } for (i = 0; i < suc.update_clone_nr; i++) { struct update_clone_data ucd = suc.update_clone[i]; + int code; oidcpy(&update_data->oid, &ucd.oid); update_data->just_cloned = ucd.just_cloned; update_data->sm_path = ucd.sub->path; - if (update_submodule(update_data)) - res = 1; + code = ensure_core_worktree(update_data->sm_path); + if (code) + goto fail; + + update_data->displaypath = get_submodule_displaypath( + update_data->sm_path, update_data->prefix); + code = update_submodule(update_data); + FREE_AND_NULL(update_data->displaypath); +fail: + if (!code) + continue; + ret = code; + if (ret == 128) + goto cleanup; } cleanup: + submodule_update_clone_release(&suc); string_list_clear(&update_data->references, 0); - return res; + return ret; } static int module_update(int argc, const char **argv, const char *prefix) { - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; + struct pathspec pathspec2 = { 0 }; struct update_data opt = UPDATE_DATA_INIT; - struct list_objects_filter_options filter_options; + struct list_objects_filter_options filter_options = + LIST_OBJECTS_FILTER_INIT; int ret; - struct option module_update_options[] = { OPT__FORCE(&opt.force, N_("force checkout updates"), 0), OPT_BOOL(0, "init", &opt.init, @@ -2597,13 +2638,15 @@ static int module_update(int argc, const char **argv, const char *prefix) OPT_STRING(0, "prefix", &opt.prefix, N_("path"), N_("path into the working tree")), - OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, - N_("path"), - N_("path into the working tree, across nested " - "submodule boundaries")), - OPT_STRING(0, "update", &opt.update_default, - N_("string"), - N_("rebase, merge, checkout or none")), + OPT_SET_INT(0, "checkout", &opt.update_default, + N_("use the 'checkout' update strategy (default)"), + SM_UPDATE_CHECKOUT), + OPT_SET_INT('m', "merge", &opt.update_default, + N_("use the 'merge' update strategy"), + SM_UPDATE_MERGE), + OPT_SET_INT('r', "rebase", &opt.update_default, + N_("use the 'rebase' update strategy"), + SM_UPDATE_REBASE), OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &opt.dissociate, @@ -2619,13 +2662,12 @@ static int module_update(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "progress", &opt.progress, N_("force cloning progress")), OPT_BOOL(0, "require-init", &opt.require_init, - N_("disallow cloning into non-empty directory")), + N_("disallow cloning into non-empty directory, implies --init")), OPT_BOOL(0, "single-branch", &opt.single_branch, N_("clone only one branch, HEAD or --branch")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule [--quiet] update" " [--init [--filter=<filter-spec>]] [--remote]" @@ -2639,10 +2681,12 @@ static int module_update(int argc, const char **argv, const char *prefix) update_clone_config_from_gitmodules(&opt.max_jobs); git_config(git_update_clone_config, &opt.max_jobs); - memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_update_options, git_submodule_helper_usage, 0); + if (opt.require_init) + opt.init = 1; + if (filter_options.choice && !opt.init) { usage_with_options(git_submodule_helper_usage, module_update_options); @@ -2651,13 +2695,11 @@ static int module_update(int argc, const char **argv, const char *prefix) opt.filter_options = &filter_options; if (opt.update_default) - if (parse_submodule_update_strategy(opt.update_default, - &opt.update_strategy) < 0) - die(_("bad value for update parameter")); + opt.update_strategy.type = opt.update_default; if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0) { - list_objects_filter_release(&filter_options); - return 1; + ret = 1; + goto cleanup; } if (pathspec.nr) @@ -2668,8 +2710,11 @@ static int module_update(int argc, const char **argv, const char *prefix) struct init_cb info = INIT_CB_INIT; if (module_list_compute(argc, argv, opt.prefix, - &pathspec, &list) < 0) - return 1; + &pathspec2, &list) < 0) { + module_list_release(&list); + ret = 1; + goto cleanup; + } /* * If there are no path args and submodule.active is set then, @@ -2679,15 +2724,19 @@ static int module_update(int argc, const char **argv, const char *prefix) module_list_active(&list); info.prefix = opt.prefix; - info.superprefix = opt.recursive_prefix; if (opt.quiet) info.flags |= OPT_QUIET; for_each_listed_submodule(&list, init_submodule_cb, &info); + module_list_release(&list); } ret = update_submodules(&opt); +cleanup: + update_data_release(&opt); list_objects_filter_release(&filter_options); + clear_pathspec(&pathspec); + clear_pathspec(&pathspec2); return ret; } @@ -2771,10 +2820,9 @@ static int push_check(int argc, const char **argv, const char *prefix) static int absorb_git_dirs(int argc, const char **argv, const char *prefix) { int i; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES; - struct option embed_gitdir_options[] = { OPT_STRING(0, "prefix", &prefix, N_("path"), @@ -2783,53 +2831,26 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) ABSORB_GITDIR_RECURSE_SUBMODULES), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper absorb-git-dirs [<options>] [<path>...]"), + N_("git submodule absorbgitdirs [<options>] [<path>...]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, embed_gitdir_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; for (i = 0; i < list.nr; i++) absorb_git_dir_into_superproject(list.entries[i]->name, flags); - return 0; -} - -static int is_active(int argc, const char **argv, const char *prefix) -{ - if (argc != 2) - die("submodule--helper is-active takes exactly 1 argument"); - - return !is_submodule_active(the_repository, argv[1]); -} - -/* - * Exit non-zero if any of the submodule names given on the command line is - * invalid. If no names are given, filter stdin to print only valid names - * (which is primarily intended for testing). - */ -static int check_name(int argc, const char **argv, const char *prefix) -{ - if (argc > 1) { - while (*++argv) { - if (check_submodule_name(*argv) < 0) - return 1; - } - } else { - struct strbuf buf = STRBUF_INIT; - while (strbuf_getline(&buf, stdin) != EOF) { - if (!check_submodule_name(buf.buf)) - printf("%s\n", buf.buf); - } - strbuf_release(&buf); - } - return 0; + ret = 0; +cleanup: + clear_pathspec(&pathspec); + module_list_release(&list); + return ret; } static int module_config(int argc, const char **argv, const char *prefix) @@ -2838,7 +2859,6 @@ static int module_config(int argc, const char **argv, const char *prefix) CHECK_WRITEABLE = 1, DO_UNSET = 2 } command = 0; - struct option module_config_options[] = { OPT_CMDMODE(0, "check-writeable", &command, N_("check if it is safe to write to the .gitmodules file"), @@ -2884,13 +2904,12 @@ static int module_set_url(int argc, const char **argv, const char *prefix) const char *newurl; const char *path; char *config_name; - struct option options[] = { OPT__QUIET(&quiet, N_("suppress output for setting url of a submodule")), OPT_END() }; const char *const usage[] = { - N_("git submodule--helper set-url [--quiet] <path> <newurl>"), + N_("git submodule set-url [--quiet] <path> <newurl>"), NULL }; @@ -2915,13 +2934,13 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) const char *opt_branch = NULL; const char *path; char *config_name; - - /* - * We accept the `quiet` option for uniformity across subcommands, - * though there is nothing to make less verbose in this subcommand. - */ struct option options[] = { + /* + * We accept the `quiet` option for uniformity across subcommands, + * though there is nothing to make less verbose in this subcommand. + */ OPT_NOOP_NOARG('q', "quiet"), + OPT_BOOL('d', "default", &opt_default, N_("set the default tracking branch to master")), OPT_STRING('b', "branch", &opt_branch, N_("branch"), @@ -2929,8 +2948,8 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) OPT_END() }; const char *const usage[] = { - N_("git submodule--helper set-branch [-q|--quiet] (-d|--default) <path>"), - N_("git submodule--helper set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), + N_("git submodule set-branch [-q|--quiet] (-d|--default) <path>"), + N_("git submodule set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), NULL }; @@ -2956,7 +2975,6 @@ static int module_create_branch(int argc, const char **argv, const char *prefix) { enum branch_track track; int quiet = 0, force = 0, reflog = 0, dry_run = 0; - struct option options[] = { OPT__QUIET(&quiet, N_("print only error messages")), OPT__FORCE(&force, N_("force creation"), 0), @@ -3019,8 +3037,10 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path) if (!capture_command(&cp_remote, &sb_remote_out, 0)) { char *next_line; char *line = sb_remote_out.buf; + while ((next_line = strchr(line, '\n')) != NULL) { size_t len = next_line - line; + if (strip_suffix_mem(line, &len, " (fetch)")) strbuf_addf(msg, " %.*s\n", (int)len, line); line = next_line + 1; @@ -3034,6 +3054,8 @@ static int add_submodule(const struct add_data *add_data) { char *submod_gitdir_path; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; + struct string_list reference = STRING_LIST_INIT_NODUP; + int ret = -1; /* perhaps the path already exists and is already a git repo, else clone it */ if (is_directory(add_data->sm_path)) { @@ -3050,6 +3072,7 @@ static int add_submodule(const struct add_data *add_data) free(submod_gitdir_path); } else { struct child_process cp = CHILD_PROCESS_INIT; + submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name); if (is_directory(submod_gitdir_path)) { @@ -3088,15 +3111,17 @@ static int add_submodule(const struct add_data *add_data) clone_data.url = add_data->realrepo; clone_data.quiet = add_data->quiet; clone_data.progress = add_data->progress; - if (add_data->reference_path) - string_list_append(&clone_data.reference, - xstrdup(add_data->reference_path)); + if (add_data->reference_path) { + char *p = xstrdup(add_data->reference_path); + + string_list_append(&reference, p)->util = p; + } clone_data.dissociate = add_data->dissociate; if (add_data->depth >= 0) clone_data.depth = xstrfmt("%d", add_data->depth); - if (clone_submodule(&clone_data)) - return -1; + if (clone_submodule(&clone_data, &reference)) + goto cleanup; prepare_submodule_repo_env(&cp.env); cp.git_cmd = 1; @@ -3115,7 +3140,10 @@ static int add_submodule(const struct add_data *add_data) if (run_command(&cp)) die(_("unable to checkout submodule '%s'"), add_data->sm_path); } - return 0; + ret = 0; +cleanup: + string_list_clear(&reference, 1); + return ret; } static int config_submodule_in_gitmodules(const char *name, const char *var, const char *value) @@ -3136,7 +3164,7 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con static void configure_added_submodule(struct add_data *add_data) { char *key; - char *val = NULL; + const char *val; struct child_process add_submod = CHILD_PROCESS_INIT; struct child_process add_gitmodules = CHILD_PROCESS_INIT; @@ -3181,7 +3209,7 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get_string("submodule.active", &val) && val) { + if (!git_config_get_string_tmp("submodule.active", &val)) { /* * If the submodule being added isn't already covered by the * current configured pathspec, set the submodule's active flag @@ -3255,7 +3283,6 @@ static int module_add(int argc, const char **argv, const char *prefix) int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; char *to_free = NULL; - struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), N_("branch of repository to add as submodule")), @@ -3272,11 +3299,12 @@ static int module_add(int argc, const char **argv, const char *prefix) OPT_INTEGER(0, "depth", &add_data.depth, N_("depth for shallow clones")), OPT_END() }; - const char *const usage[] = { - N_("git submodule--helper add [<options>] [--] <repository> [<path>]"), + N_("git submodule add [<options>] [--] <repository> [<path>]"), NULL }; + struct strbuf sb = STRBUF_INIT; + int ret = 1; argc = parse_options(argc, argv, prefix, options, usage, 0); @@ -3296,8 +3324,12 @@ static int module_add(int argc, const char **argv, const char *prefix) else add_data.sm_path = xstrdup(argv[1]); - if (prefix && *prefix && !is_absolute_path(add_data.sm_path)) - add_data.sm_path = xstrfmt("%s%s", prefix, add_data.sm_path); + if (prefix && *prefix && !is_absolute_path(add_data.sm_path)) { + char *sm_path = add_data.sm_path; + + add_data.sm_path = xstrfmt("%s%s", prefix, sm_path); + free(sm_path); + } if (starts_with_dot_dot_slash(add_data.repo) || starts_with_dot_slash(add_data.repo)) { @@ -3326,20 +3358,17 @@ static int module_add(int argc, const char **argv, const char *prefix) die_on_repo_without_commits(add_data.sm_path); if (!force) { - int exit_code = -1; - struct strbuf sb = STRBUF_INIT; struct child_process cp = CHILD_PROCESS_INIT; + cp.git_cmd = 1; cp.no_stdout = 1; strvec_pushl(&cp.args, "add", "--dry-run", "--ignore-missing", "--no-warn-embedded-repo", add_data.sm_path, NULL); - if ((exit_code = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) { + if ((ret = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) { strbuf_complete_line(&sb); fputs(sb.buf, stderr); - free(add_data.sm_path); - return exit_code; + goto cleanup; } - strbuf_release(&sb); } if(!add_data.sm_name) @@ -3354,15 +3383,17 @@ static int module_add(int argc, const char **argv, const char *prefix) add_data.progress = !!progress; add_data.dissociate = !!dissociate; - if (add_submodule(&add_data)) { - free(add_data.sm_path); - return 1; - } + if (add_submodule(&add_data)) + goto cleanup; configure_added_submodule(&add_data); + + ret = 0; +cleanup: free(add_data.sm_path); free(to_free); + strbuf_release(&sb); - return 0; + return ret; } #define SUPPORT_SUPER_PREFIX (1<<0) @@ -3374,22 +3405,17 @@ struct cmd_struct { }; static struct cmd_struct commands[] = { - {"list", module_list, 0}, - {"name", module_name, 0}, - {"clone", module_clone, 0}, - {"add", module_add, SUPPORT_SUPER_PREFIX}, - {"update", module_update, 0}, - {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"clone", module_clone, SUPPORT_SUPER_PREFIX}, + {"add", module_add, 0}, + {"update", module_update, SUPPORT_SUPER_PREFIX}, {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, - {"init", module_init, SUPPORT_SUPER_PREFIX}, + {"init", module_init, 0}, {"status", module_status, SUPPORT_SUPER_PREFIX}, {"sync", module_sync, SUPPORT_SUPER_PREFIX}, {"deinit", module_deinit, 0}, - {"summary", module_summary, SUPPORT_SUPER_PREFIX}, + {"summary", module_summary, 0}, {"push-check", push_check, 0}, - {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, - {"is-active", is_active, 0}, - {"check-name", check_name, 0}, + {"absorbgitdirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"config", module_config, 0}, {"set-url", module_set_url, 0}, {"set-branch", module_set_branch, 0}, diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index e547a08d6c..590ed17dd3 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -6,14 +6,17 @@ static const char * const git_symbolic_ref_usage[] = { N_("git symbolic-ref [<options>] <name> [<ref>]"), - N_("git symbolic-ref -d [-q] <name>"), + N_("git symbolic-ref -d [-q] [--no-recurse] <name>"), NULL }; -static int check_symref(const char *HEAD, int quiet, int shorten, int print) +static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, int print) { - int flag; - const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag); + int resolve_flags, flag; + const char *refname; + + resolve_flags = (recurse ? 0 : RESOLVE_REF_NO_RECURSE); + refname = resolve_ref_unsafe(HEAD, resolve_flags, NULL, &flag); if (!refname) die("No such ref: %s", HEAD); @@ -35,13 +38,14 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print) int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { - int quiet = 0, delete = 0, shorten = 0, ret = 0; + int quiet = 0, delete = 0, shorten = 0, recurse = 1, ret = 0; const char *msg = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("suppress error message for non-symbolic (detached) refs")), OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")), OPT_BOOL(0, "short", &shorten, N_("shorten ref output")), + OPT_BOOL(0, "recurse", &recurse, N_("recursively dereference (default)")), OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_END(), }; @@ -55,7 +59,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) if (delete) { if (argc != 1) usage_with_options(git_symbolic_ref_usage, options); - ret = check_symref(argv[0], 1, 0, 0); + ret = check_symref(argv[0], 1, 0, 0, 0); if (ret) die("Cannot delete %s, not a symbolic ref", argv[0]); if (!strcmp(argv[0], "HEAD")) @@ -65,12 +69,14 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) switch (argc) { case 1: - ret = check_symref(argv[0], quiet, shorten, 1); + ret = check_symref(argv[0], quiet, shorten, recurse, 1); break; case 2: if (!strcmp(argv[0], "HEAD") && !starts_with(argv[1], "refs/")) die("Refusing to point HEAD outside of refs/"); + if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0) + die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]); ret = !!create_symref(argv[0], argv[1], msg); break; default: diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 56d05e2725..43789b8ef2 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -97,15 +97,27 @@ static void use(int bytes) display_throughput(progress, consumed_bytes); } +/* + * Decompress zstream from the standard input into a newly + * allocated buffer of specified size and return the buffer. + * The caller is responsible to free the returned buffer. + * + * But for dry_run mode, "get_data()" is only used to check the + * integrity of data, and the returned buffer is not used at all. + * Therefore, in dry_run mode, "get_data()" will release the small + * allocated buffer which is reused to hold temporary zstream output + * and return NULL instead of returning garbage data. + */ static void *get_data(unsigned long size) { git_zstream stream; - void *buf = xmallocz(size); + unsigned long bufsize = dry_run && size > 8192 ? 8192 : size; + void *buf = xmallocz(bufsize); memset(&stream, 0, sizeof(stream)); stream.next_out = buf; - stream.avail_out = size; + stream.avail_out = bufsize; stream.next_in = fill(1); stream.avail_in = len; git_inflate_init(&stream); @@ -125,8 +137,17 @@ static void *get_data(unsigned long size) } stream.next_in = fill(1); stream.avail_in = len; + if (dry_run) { + /* reuse the buffer in dry_run mode */ + stream.next_out = buf; + stream.avail_out = bufsize > size - stream.total_out ? + size - stream.total_out : + bufsize; + } } git_inflate_end(&stream); + if (dry_run) + FREE_AND_NULL(buf); return buf; } @@ -326,10 +347,70 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size, { void *buf = get_data(size); - if (!dry_run && buf) + if (buf) write_object(nr, type, buf, size); - else - free(buf); +} + +struct input_zstream_data { + git_zstream *zstream; + unsigned char buf[8192]; + int status; +}; + +static const void *feed_input_zstream(struct input_stream *in_stream, + unsigned long *readlen) +{ + struct input_zstream_data *data = in_stream->data; + git_zstream *zstream = data->zstream; + void *in = fill(1); + + if (in_stream->is_finished) { + *readlen = 0; + return NULL; + } + + zstream->next_out = data->buf; + zstream->avail_out = sizeof(data->buf); + zstream->next_in = in; + zstream->avail_in = len; + + data->status = git_inflate(zstream, 0); + + in_stream->is_finished = data->status != Z_OK; + use(len - zstream->avail_in); + *readlen = sizeof(data->buf) - zstream->avail_out; + + return data->buf; +} + +static void stream_blob(unsigned long size, unsigned nr) +{ + git_zstream zstream = { 0 }; + struct input_zstream_data data = { 0 }; + struct input_stream in_stream = { + .read = feed_input_zstream, + .data = &data, + }; + struct obj_info *info = &obj_list[nr]; + + data.zstream = &zstream; + git_inflate_init(&zstream); + + if (stream_loose_object(&in_stream, size, &info->oid)) + die(_("failed to write object in stream")); + + if (data.status != Z_STREAM_END) + die(_("inflate returned (%d)"), data.status); + git_inflate_end(&zstream); + + if (strict) { + struct blob *blob = lookup_blob(the_repository, &info->oid); + + if (!blob) + die(_("invalid blob object from stream")); + blob->object.flags |= FLAG_WRITTEN; + } + info->obj = NULL; } static int resolve_against_held(unsigned nr, const struct object_id *base, @@ -359,10 +440,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, oidread(&base_oid, fill(the_hash_algo->rawsz)); use(the_hash_algo->rawsz); delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); + if (!delta_data) return; - } if (has_object_file(&base_oid)) ; /* Ok we have this one */ else if (resolve_against_held(nr, &base_oid, @@ -398,10 +477,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, die("offset value out of bound for delta base object"); delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); + if (!delta_data) return; - } lo = 0; hi = nr; while (lo < hi) { @@ -468,9 +545,14 @@ static void unpack_one(unsigned nr) } switch (type) { + case OBJ_BLOB: + if (!dry_run && size > big_file_threshold) { + stream_blob(size, nr); + return; + } + /* fallthrough */ case OBJ_COMMIT: case OBJ_TREE: - case OBJ_BLOB: case OBJ_TAG: unpack_non_delta_entry(type, size, nr); return; diff --git a/builtin/worktree.c b/builtin/worktree.c index cd62eef240..c6710b2552 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1112,31 +1112,24 @@ static int repair(int ac, const char **av, const char *prefix) int cmd_worktree(int ac, const char **av, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("add", &fn, add), + OPT_SUBCOMMAND("prune", &fn, prune), + OPT_SUBCOMMAND("list", &fn, list), + OPT_SUBCOMMAND("lock", &fn, lock_worktree), + OPT_SUBCOMMAND("unlock", &fn, unlock_worktree), + OPT_SUBCOMMAND("move", &fn, move_worktree), + OPT_SUBCOMMAND("remove", &fn, remove_worktree), + OPT_SUBCOMMAND("repair", &fn, repair), OPT_END() }; git_config(git_worktree_config, NULL); - if (ac < 2) - usage_with_options(worktree_usage, options); if (!prefix) prefix = ""; - if (!strcmp(av[1], "add")) - return add(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "prune")) - return prune(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "list")) - return list(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "lock")) - return lock_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "unlock")) - return unlock_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "move")) - return move_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "remove")) - return remove_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "repair")) - return repair(ac - 1, av + 1, prefix); - usage_with_options(worktree_usage, options); + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + return fn(ac, av, prefix); } |