diff options
Diffstat (limited to 'builtin')
66 files changed, 3063 insertions, 1627 deletions
diff --git a/builtin/add.c b/builtin/add.c index 09e684585d..ef6b619c45 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -30,6 +30,7 @@ static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static int add_renormalize; static int pathspec_file_nul; +static int include_sparse; static const char *pathspec_from_file; static int legacy_stash_p; /* support for the scripted `git stash` */ @@ -46,7 +47,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only) struct cache_entry *ce = active_cache[i]; int err; - if (ce_skip_worktree(ce)) + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) continue; if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) @@ -94,6 +97,10 @@ static void update_callback(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; + + if (!include_sparse && !path_in_sparse_checkout(path, &the_index)) + continue; + switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); @@ -144,12 +151,12 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) { int i, retval = 0; - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (ce_skip_worktree(ce)) + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) continue; if (ce_stage(ce)) continue; /* do not touch unmerged paths */ @@ -198,7 +205,10 @@ static int refresh(int verbose, const struct pathspec *pathspec) _("Unstaged changes after refreshing the index:")); for (i = 0; i < pathspec->nr; i++) { if (!seen[i]) { - if (matches_skip_worktree(pathspec, i, &skip_worktree_seen)) { + const char *path = pathspec->items[i].original; + + if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) || + !path_in_sparse_checkout(path, &the_index)) { string_list_append(&only_match_skip_worktree, pathspec->items[i].original); } else { @@ -313,9 +323,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) rev.diffopt.output_format = DIFF_FORMAT_PATCH; rev.diffopt.use_color = 0; rev.diffopt.flags.ignore_dirty_submodules = 1; - out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (out < 0) - die(_("Could not open '%s' for writing."), file); + out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) @@ -378,6 +386,7 @@ static struct option builtin_add_options[] = { OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), + OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x", N_("override the executable bit of the listed files")), OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, @@ -419,6 +428,7 @@ static const char embedded_advice[] = N_( static void check_embedded_repo(const char *path) { struct strbuf name = STRBUF_INIT; + static int adviced_on_embedded_repo = 0; if (!warn_on_embedded_repo) return; @@ -430,10 +440,10 @@ static void check_embedded_repo(const char *path) strbuf_strip_suffix(&name, "/"); warning(_("adding embedded git repository: %s"), name.buf); - if (advice_add_embedded_repo) { + if (!adviced_on_embedded_repo && + advice_enabled(ADVICE_ADD_EMBEDDED_REPO)) { advise(embedded_advice, name.buf, name.buf); - /* there may be multiple entries; advise only once */ - advice_add_embedded_repo = 0; + adviced_on_embedded_repo = 1; } strbuf_release(&name); @@ -442,12 +452,13 @@ static void check_embedded_repo(const char *path) static int add_files(struct dir_struct *dir, int flags) { int i, exit_status = 0; + struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP; if (dir->ignored_nr) { fprintf(stderr, _(ignore_error)); for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); - if (advice_add_ignored_file) + if (advice_enabled(ADVICE_ADD_IGNORED_FILE)) advise(_("Use -f if you really want to add them.\n" "Turn this message off by running\n" "\"git config advice.addIgnoredFile false\"")); @@ -455,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags) } for (i = 0; i < dir->nr; i++) { + if (!include_sparse && + !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) { + string_list_append(&matched_sparse_paths, + dir->entries[i]->name); + continue; + } if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); @@ -463,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags) check_embedded_repo(dir->entries[i]->name); } } + + if (matched_sparse_paths.nr) { + advise_on_updating_sparse_paths(&matched_sparse_paths); + exit_status = 1; + } + + string_list_clear(&matched_sparse_paths, 0); + return exit_status; } @@ -528,6 +553,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize; require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); /* @@ -553,7 +581,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (require_pathspec && pathspec.nr == 0) { fprintf(stderr, _("Nothing specified, nothing added.\n")); - if (advice_add_empty_pathspec) + if (advice_enabled(ADVICE_ADD_EMPTY_PATHSPEC)) advise( _("Maybe you wanted to say 'git add .'?\n" "Turn this message off by running\n" "\"git config advice.addEmptyPathspec false\"")); @@ -624,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (seen[i]) continue; - if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) { + if (!include_sparse && + matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) { string_list_append(&only_match_skip_worktree, pathspec.items[i].original); continue; diff --git a/builtin/am.c b/builtin/am.c index 0b2d886c81..8677ea2348 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -11,6 +11,7 @@ #include "parse-options.h" #include "dir.h" #include "run-command.h" +#include "hook.h" #include "quote.h" #include "tempfile.h" #include "lockfile.h" @@ -210,6 +211,7 @@ static void write_state_bool(const struct am_state *state, * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline * at the end. */ +__attribute__((format (printf, 3, 4))) static void say(const struct am_state *state, FILE *fp, const char *fmt, ...) { va_list ap; @@ -1819,7 +1821,7 @@ static void am_run(struct am_state *state, int resume) printf_ln(_("Patch failed at %s %.*s"), msgnum(state), linelen(state->msg), state->msg); - if (advice_amworkdir) + if (advice_enabled(ADVICE_AM_WORK_DIR)) advise(_("Use 'git am --show-current-patch=diff' to see the failed patch")); die_user_resolve(state); @@ -1847,7 +1849,6 @@ next: */ if (!state->rebasing) { am_destroy(state); - close_object_store(the_repository->objects); run_auto_maintenance(state->quiet); } } @@ -1917,7 +1918,8 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) opts.dst_index = &the_index; opts.update = 1; opts.merge = 1; - opts.reset = reset; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = twoway_merge; init_tree_desc(&t[0], head->buffer, head->size); init_tree_desc(&t[1], remote->buffer, remote->size); @@ -2105,7 +2107,8 @@ static void am_abort(struct am_state *state) if (!has_orig_head) oidcpy(&orig_head, the_hash_algo->empty_tree); - clean_index(&curr_head, &orig_head); + if (clean_index(&curr_head, &orig_head)) + die(_("failed to clean index")); if (has_orig_head) update_ref("am --abort", "HEAD", &orig_head, diff --git a/builtin/archive.c b/builtin/archive.c index 45d11669aa..7176b041b6 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -12,9 +12,7 @@ static void create_output_file(const char *output_file) { - int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (output_fd < 0) - die_errno(_("could not create archive file '%s'"), output_file); + int output_fd = xopen(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (output_fd != 1) { if (dup2(output_fd, 1) < 0) die_errno(_("could not redirect output")); diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 9d9540a0ab..28a2e6a575 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -18,10 +18,10 @@ static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") static GIT_PATH_FUNC(git_path_head_name, "head-name") static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") +static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset [<commit>]"), - N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]" " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"), @@ -30,6 +30,8 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-state (good|old) [<rev>...]"), N_("git bisect--helper --bisect-replay <filename>"), N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"), + N_("git bisect--helper --bisect-visualize"), + N_("git bisect--helper --bisect-run <cmd>..."), NULL }; @@ -117,6 +119,7 @@ static int write_in_file(const char *path, const char *mode, const char *format, return fclose(fp); } +__attribute__((format (printf, 2, 3))) static int write_to_file(const char *path, const char *format, ...) { int res; @@ -129,6 +132,7 @@ static int write_to_file(const char *path, const char *format, ...) return res; } +__attribute__((format (printf, 2, 3))) static int append_to_file(const char *path, const char *format, ...) { int res; @@ -141,6 +145,19 @@ static int append_to_file(const char *path, const char *format, ...) return res; } +static int print_file_to_stdout(const char *path) +{ + int fd = open(path, O_RDONLY); + int ret = 0; + + if (fd < 0) + return error_errno(_("cannot open file '%s' for reading"), path); + if (copy_fd(fd, 1) < 0) + ret = error_errno(_("failed to read '%s'"), path); + close(fd); + return ret; +} + static int check_term_format(const char *term, const char *orig_term) { int res; @@ -1034,6 +1051,125 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **ar return res; } +static int bisect_visualize(struct bisect_terms *terms, const char **argv, int argc) +{ + struct strvec args = STRVEC_INIT; + int flags = RUN_COMMAND_NO_STDIN, res = 0; + struct strbuf sb = STRBUF_INIT; + + if (bisect_next_check(terms, NULL) != 0) + return BISECT_FAILED; + + if (!argc) { + if ((getenv("DISPLAY") || getenv("SESSIONNAME") || getenv("MSYSTEM") || + getenv("SECURITYSESSIONID")) && exists_in_PATH("gitk")) { + strvec_push(&args, "gitk"); + } else { + strvec_push(&args, "log"); + flags |= RUN_GIT_CMD; + } + } else { + if (argv[0][0] == '-') { + strvec_push(&args, "log"); + flags |= RUN_GIT_CMD; + } else if (strcmp(argv[0], "tig") && !starts_with(argv[0], "git")) + flags |= RUN_GIT_CMD; + + strvec_pushv(&args, argv); + } + + strvec_pushl(&args, "--bisect", "--", NULL); + + strbuf_read_file(&sb, git_path_bisect_names(), 0); + sq_dequote_to_strvec(sb.buf, &args); + strbuf_release(&sb); + + res = run_command_v_opt(args.v, flags); + strvec_clear(&args); + return res; +} + +static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) +{ + int res = BISECT_OK; + struct strbuf command = STRBUF_INIT; + struct strvec args = STRVEC_INIT; + struct strvec run_args = STRVEC_INIT; + const char *new_state; + int temporary_stdout_fd, saved_stdout; + + if (bisect_next_check(terms, NULL)) + return BISECT_FAILED; + + if (argc) + sq_quote_argv(&command, argv); + else { + error(_("bisect run failed: no command provided.")); + return BISECT_FAILED; + } + + strvec_push(&run_args, command.buf); + + while (1) { + strvec_clear(&args); + + printf(_("running %s\n"), command.buf); + res = run_command_v_opt(run_args.v, RUN_USING_SHELL); + + if (res < 0 || 128 <= res) { + error(_("bisect run failed: exit code %d from" + " '%s' is < 0 or >= 128"), res, command.buf); + strbuf_release(&command); + return res; + } + + if (res == 125) + new_state = "skip"; + else if (!res) + new_state = terms->term_good; + else + new_state = terms->term_bad; + + temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666); + + if (temporary_stdout_fd < 0) + return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run()); + + fflush(stdout); + saved_stdout = dup(1); + dup2(temporary_stdout_fd, 1); + + res = bisect_state(terms, &new_state, 1); + + fflush(stdout); + dup2(saved_stdout, 1); + close(saved_stdout); + close(temporary_stdout_fd); + + print_file_to_stdout(git_path_bisect_run()); + + if (res == BISECT_ONLY_SKIPPED_LEFT) + error(_("bisect run cannot continue any more")); + else if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) { + printf(_("bisect run success")); + res = BISECT_OK; + } else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { + printf(_("bisect found first bad commit")); + res = BISECT_OK; + } else if (res) { + error(_("bisect run failed: 'git bisect--helper --bisect-state" + " %s' exited with error code %d"), args.v[0], res); + } else { + continue; + } + + strbuf_release(&command); + strvec_clear(&args); + strvec_clear(&run_args); + return res; + } +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -1046,7 +1182,9 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) BISECT_STATE, BISECT_LOG, BISECT_REPLAY, - BISECT_SKIP + BISECT_SKIP, + BISECT_VISUALIZE, + BISECT_RUN, } cmdmode = 0; int res = 0, nolog = 0; struct option options[] = { @@ -1068,6 +1206,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("replay the bisection process from the given file"), BISECT_REPLAY), OPT_CMDMODE(0, "bisect-skip", &cmdmode, N_("skip some commits for checkout"), BISECT_SKIP), + OPT_CMDMODE(0, "bisect-visualize", &cmdmode, + N_("visualize the bisection"), BISECT_VISUALIZE), + OPT_CMDMODE(0, "bisect-run", &cmdmode, + N_("use <cmd>... to automatically bisect."), BISECT_RUN), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -1087,12 +1229,6 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) return error(_("--bisect-reset requires either no argument or a commit")); res = bisect_reset(argc ? argv[0] : NULL); break; - case BISECT_NEXT_CHECK: - if (argc != 2 && argc != 3) - return error(_("--bisect-next-check requires 2 or 3 arguments")); - set_terms(&terms, argv[1], argv[0]); - res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL); - break; case BISECT_TERMS: if (argc > 1) return error(_("--bisect-terms requires 0 or 1 argument")); @@ -1129,6 +1265,16 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) get_terms(&terms); res = bisect_skip(&terms, argv, argc); break; + case BISECT_VISUALIZE: + get_terms(&terms); + res = bisect_visualize(&terms, argv, argc); + break; + case BISECT_RUN: + if (!argc) + return error(_("bisect run failed: no command provided.")); + get_terms(&terms); + res = bisect_run(&terms, argv, argc); + break; default: BUG("unknown subcommand %d", cmdmode); } diff --git a/builtin/blame.c b/builtin/blame.c index 641523ff9a..1c31a99640 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -101,6 +101,16 @@ struct commit_info { struct strbuf summary; }; +#define COMMIT_INFO_INIT { \ + .author = STRBUF_INIT, \ + .author_mail = STRBUF_INIT, \ + .author_tz = STRBUF_INIT, \ + .committer = STRBUF_INIT, \ + .committer_mail = STRBUF_INIT, \ + .committer_tz = STRBUF_INIT, \ + .summary = STRBUF_INIT, \ +} + /* * Parse author/committer line in the commit object buffer */ @@ -160,18 +170,6 @@ static void get_ac_line(const char *inbuf, const char *what, strbuf_add(name, namebuf, namelen); } -static void commit_info_init(struct commit_info *ci) -{ - - strbuf_init(&ci->author, 0); - strbuf_init(&ci->author_mail, 0); - strbuf_init(&ci->author_tz, 0); - strbuf_init(&ci->committer, 0); - strbuf_init(&ci->committer_mail, 0); - strbuf_init(&ci->committer_tz, 0); - strbuf_init(&ci->summary, 0); -} - static void commit_info_destroy(struct commit_info *ci) { @@ -192,8 +190,6 @@ static void get_commit_info(struct commit *commit, const char *subject, *encoding; const char *message; - commit_info_init(ret); - encoding = get_log_output_encoding(); message = logmsg_reencode(commit, NULL, encoding); get_ac_line(message, "\nauthor ", @@ -246,7 +242,7 @@ static void write_filename_info(struct blame_origin *suspect) */ static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat) { - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN)) return 0; @@ -440,7 +436,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int int cnt; const char *cp; struct blame_origin *suspect = ent->suspect; - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); const char *default_color = NULL, *color = NULL, *reset = NULL; @@ -630,7 +626,7 @@ static void find_alignment(struct blame_scoreboard *sb, int *option) if (longest_file < num) longest_file = num; if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) diff --git a/builtin/branch.c b/builtin/branch.c index b23b1d1752..0b7ed82654 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -168,7 +168,7 @@ static int check_branch_commit(const char *branchname, const char *refname, int kinds, int force) { struct commit *rev = lookup_commit_reference(the_repository, oid); - if (!rev) { + if (!force && !rev) { error(_("Couldn't look up commit object for '%s'"), refname); return -1; } @@ -427,7 +427,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin memset(&array, 0, sizeof(array)); - filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); + filter_refs(&array, filter, filter->kind); if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 9915a5841d..9de32bc96e 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -3,7 +3,8 @@ #include "strbuf.h" #include "help.h" #include "compat/compiler.h" -#include "run-command.h" +#include "hook.h" +#include "hook-list.h" static void get_system_info(struct strbuf *sys_info) @@ -41,39 +42,7 @@ static void get_system_info(struct strbuf *sys_info) static void get_populated_hooks(struct strbuf *hook_info, int nongit) { - /* - * NEEDSWORK: Doesn't look like there is a list of all possible hooks; - * so below is a transcription of `git help hooks`. Later, this should - * be replaced with some programmatically generated list (generated from - * doc or else taken from some library which tells us about all the - * hooks) - */ - static const char *hook[] = { - "applypatch-msg", - "pre-applypatch", - "post-applypatch", - "pre-commit", - "pre-merge-commit", - "prepare-commit-msg", - "commit-msg", - "post-commit", - "pre-rebase", - "post-checkout", - "post-merge", - "pre-push", - "pre-receive", - "update", - "post-receive", - "post-update", - "push-to-checkout", - "pre-auto-gc", - "post-rewrite", - "sendemail-validate", - "fsmonitor-watchman", - "p4-pre-submit", - "post-index-change", - }; - int i; + const char **p; if (nongit) { strbuf_addstr(hook_info, @@ -81,9 +50,12 @@ static void get_populated_hooks(struct strbuf *hook_info, int nongit) return; } - for (i = 0; i < ARRAY_SIZE(hook); i++) - if (find_hook(hook[i])) - strbuf_addf(hook_info, "%s\n", hook[i]); + for (p = hook_name_list; *p; p++) { + const char *hook = *p; + + if (hook_exists(hook)) + strbuf_addf(hook_info, "%s\n", hook); + } } static const char * const bugreport_usage[] = { @@ -171,10 +143,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) get_populated_hooks(&buffer, !startup_info->have_repository); /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */ - report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666); - - if (report < 0) - die(_("couldn't create a new file at '%s'"), report_path.buf); + report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666); if (write_in_full(report, buffer.buf, buffer.len) < 0) die_errno(_("unable to write to %s"), report_path.buf); diff --git a/builtin/bundle.c b/builtin/bundle.c index 053a51bea1..5a85d7cd0f 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -39,8 +39,6 @@ static const char * const builtin_bundle_unbundle_usage[] = { NULL }; -static int verbose; - static int parse_options_cmd_bundle(int argc, const char **argv, const char* prefix, @@ -162,10 +160,15 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; int ret; + int progress = isatty(2); + struct option options[] = { + OPT_BOOL(0, "progress", &progress, + N_("show progress meter")), OPT_END() }; char *bundle_file; + struct strvec extra_index_pack_args = STRVEC_INIT; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_unbundle_usage, options, &bundle_file); @@ -177,7 +180,11 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) } if (!startup_info->have_repository) die(_("Need a repository to unbundle.")); - ret = !!unbundle(the_repository, &header, bundle_fd, 0) || + if (progress) + strvec_pushl(&extra_index_pack_args, "-v", "--progress-title", + _("Unbundling objects"), NULL); + ret = !!unbundle(the_repository, &header, bundle_fd, + &extra_index_pack_args) || list_bundle_refs(&header, argc, argv); bundle_header_release(&header); cleanup: @@ -188,7 +195,6 @@ cleanup: int cmd_bundle(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), OPT_END() }; int result; diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 243fe6844b..86fc03242b 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -355,18 +355,34 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d } } +/* + * If "pack" is non-NULL, then "offset" is the byte offset within the pack from + * which the object may be accessed (though note that we may also rely on + * data->oid, too). If "pack" is NULL, then offset is ignored. + */ static void batch_object_write(const char *obj_name, struct strbuf *scratch, struct batch_options *opt, - struct expand_data *data) + struct expand_data *data, + struct packed_git *pack, + off_t offset) { - if (!data->skip_object_info && - oid_object_info_extended(the_repository, &data->oid, &data->info, - OBJECT_INFO_LOOKUP_REPLACE) < 0) { - printf("%s missing\n", - obj_name ? obj_name : oid_to_hex(&data->oid)); - fflush(stdout); - return; + if (!data->skip_object_info) { + int ret; + + if (pack) + ret = packed_object_info(the_repository, pack, offset, + &data->info); + else + ret = oid_object_info_extended(the_repository, + &data->oid, &data->info, + OBJECT_INFO_LOOKUP_REPLACE); + if (ret < 0) { + printf("%s missing\n", + obj_name ? obj_name : oid_to_hex(&data->oid)); + fflush(stdout); + return; + } } strbuf_reset(scratch); @@ -428,7 +444,7 @@ static void batch_one_object(const char *obj_name, return; } - batch_object_write(obj_name, scratch, opt, data); + batch_object_write(obj_name, scratch, opt, data, NULL, 0); } struct object_cb_data { @@ -442,7 +458,8 @@ static int batch_object_cb(const struct object_id *oid, void *vdata) { struct object_cb_data *data = vdata; oidcpy(&data->expand->oid, oid); - batch_object_write(NULL, data->scratch, data->opt, data->expand); + batch_object_write(NULL, data->scratch, data->opt, data->expand, + NULL, 0); return 0; } @@ -463,21 +480,26 @@ static int collect_packed_object(const struct object_id *oid, return 0; } -static int batch_unordered_object(const struct object_id *oid, void *vdata) +static int batch_unordered_object(const struct object_id *oid, + struct packed_git *pack, off_t offset, + void *vdata) { struct object_cb_data *data = vdata; if (oidset_insert(data->seen, oid)) return 0; - return batch_object_cb(oid, data); + oidcpy(&data->expand->oid, oid); + batch_object_write(NULL, data->scratch, data->opt, data->expand, + pack, offset); + return 0; } static int batch_unordered_loose(const struct object_id *oid, const char *path, void *data) { - return batch_unordered_object(oid, data); + return batch_unordered_object(oid, NULL, 0, data); } static int batch_unordered_packed(const struct object_id *oid, @@ -485,7 +507,9 @@ static int batch_unordered_packed(const struct object_id *oid, uint32_t pos, void *data) { - return batch_unordered_object(oid, data); + return batch_unordered_object(oid, pack, + nth_packed_object_offset(pack, pos), + data); } static int batch_objects(struct batch_options *opt) @@ -529,6 +553,8 @@ static int batch_objects(struct batch_options *opt) if (has_promisor_remote()) warning("This repository uses promisor remotes. Some objects may not be loaded."); + read_replace_refs = 0; + cb.opt = opt; cb.expand = &data; cb.scratch = &output; diff --git a/builtin/checkout.c b/builtin/checkout.c index f4cd7747d3..cbf73b8c9f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -378,9 +378,6 @@ static int checkout_worktree(const struct checkout_opts *opts, if (pc_workers > 1) init_parallel_checkout(); - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); - for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -407,7 +404,7 @@ static int checkout_worktree(const struct checkout_opts *opts, mem_pool_discard(&ce_mem_pool, should_validate_cache_entries()); remove_marked_cache_entries(&the_index, 1); remove_scheduled_dirs(); - errs |= finish_delayed_checkout(&state, &nr_checkouts); + errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress); if (opts->count_checkout_paths) { if (nr_unmerged) @@ -530,8 +527,6 @@ static int checkout_paths(const struct checkout_opts *opts, * Make sure all pathspecs participated in locating the paths * to be checked out. */ - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (pos = 0; pos < active_nr; pos++) if (opts->overlay_mode) mark_ce_for_checkout_overlay(active_cache[pos], @@ -651,7 +646,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.head_idx = -1; opts.update = worktree; opts.skip_unmerged = !worktree; - opts.reset = 1; + opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED : + UNPACK_RESET_PROTECT_UNTRACKED; + opts.preserve_ignored = (!o->force && !o->overwrite_ignore); opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = o->show_progress; @@ -751,11 +748,7 @@ static int merge_working_tree(const struct checkout_opts *opts, new_branch_info->commit ? &new_branch_info->commit->object.oid : &new_branch_info->oid, NULL); - if (opts->overwrite_ignore) { - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(topts.dir); - } + topts.preserve_ignored = !opts->overwrite_ignore; tree = parse_tree_indirect(old_branch_info->commit ? &old_branch_info->commit->object.oid : the_hash_algo->empty_tree); @@ -923,7 +916,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { if (old_branch_info->path && - advice_detached_head && !opts->force_detach) + advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach) detach_advice(new_branch_info->name); describe_detached_head(_("HEAD is now at"), new_branch_info->commit); } @@ -1016,7 +1009,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) sb.buf); strbuf_release(&sb); - if (advice_detached_head) + if (advice_enabled(ADVICE_DETACHED_HEAD)) fprintf(stderr, Q_( /* The singular version */ @@ -1187,7 +1180,7 @@ static const char *parse_remote_branch(const char *arg, } if (!remote && num_matches > 1) { - if (advice_checkout_ambiguous_remote_branch_name) { + if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) { advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" "you can do so by fully qualifying the name with the --track option:\n" "\n" @@ -1593,6 +1586,9 @@ static int checkout_main(int argc, const char **argv, const char *prefix, git_config(git_checkout_config, opts); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + opts->track = BRANCH_TRACK_UNSPECIFIED; if (!opts->accept_pathspec && !opts->accept_ref) diff --git a/builtin/clone.c b/builtin/clone.c index 66fe66679c..559acf9e03 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -217,120 +217,6 @@ static char *get_repo_path(const char *repo, int *is_bundle) return canon; } -static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) -{ - const char *end = repo + strlen(repo), *start, *ptr; - size_t len; - char *dir; - - /* - * Skip scheme. - */ - start = strstr(repo, "://"); - if (start == NULL) - start = repo; - else - start += 3; - - /* - * Skip authentication data. The stripping does happen - * greedily, such that we strip up to the last '@' inside - * the host part. - */ - for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) { - if (*ptr == '@') - start = ptr + 1; - } - - /* - * Strip trailing spaces, slashes and /.git - */ - while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) - end--; - if (end - start > 5 && is_dir_sep(end[-5]) && - !strncmp(end - 4, ".git", 4)) { - end -= 5; - while (start < end && is_dir_sep(end[-1])) - end--; - } - - /* - * Strip trailing port number if we've got only a - * hostname (that is, there is no dir separator but a - * colon). This check is required such that we do not - * strip URI's like '/foo/bar:2222.git', which should - * result in a dir '2222' being guessed due to backwards - * compatibility. - */ - if (memchr(start, '/', end - start) == NULL - && memchr(start, ':', end - start) != NULL) { - ptr = end; - while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':') - ptr--; - if (start < ptr && ptr[-1] == ':') - end = ptr - 1; - } - - /* - * Find last component. To remain backwards compatible we - * also regard colons as path separators, such that - * cloning a repository 'foo:bar.git' would result in a - * directory 'bar' being guessed. - */ - ptr = end; - while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':') - ptr--; - start = ptr; - - /* - * Strip .{bundle,git}. - */ - len = end - start; - strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git"); - - if (!len || (len == 1 && *start == '/')) - die(_("No directory name could be guessed.\n" - "Please specify a directory on the command line")); - - if (is_bare) - dir = xstrfmt("%.*s.git", (int)len, start); - else - dir = xstrndup(start, len); - /* - * Replace sequences of 'control' characters and whitespace - * with one ascii space, remove leading and trailing spaces. - */ - if (*dir) { - char *out = dir; - int prev_space = 1 /* strip leading whitespace */; - for (end = dir; *end; ++end) { - char ch = *end; - if ((unsigned char)ch < '\x20') - ch = '\x20'; - if (isspace(ch)) { - if (prev_space) - continue; - prev_space = 1; - } else - prev_space = 0; - *out++ = ch; - } - *out = '\0'; - if (out > dir && prev_space) - out[-1] = '\0'; - } - return dir; -} - -static void strip_trailing_slashes(char *dir) -{ - char *end = dir + strlen(dir); - - while (dir < end - 1 && is_dir_sep(end[-1])) - end--; - *end = '\0'; -} - static int add_one_reference(struct string_list_item *item, void *cb_data) { struct strbuf err = STRBUF_INIT; @@ -657,7 +543,7 @@ static void write_followtags(const struct ref *refs, const char *msg) } } -static int iterate_ref_map(void *cb_data, struct object_id *oid) +static const struct object_id *iterate_ref_map(void *cb_data) { struct ref **rm = cb_data; struct ref *ref = *rm; @@ -668,13 +554,11 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid) */ while (ref && !ref->peer_ref) ref = ref->next; - /* Returning -1 notes "end of list" to the caller. */ if (!ref) - return -1; + return NULL; - oidcpy(oid, &ref->old_oid); *rm = ref->next; - return 0; + return &ref->old_oid; } static void update_remote_refs(const struct ref *refs, @@ -786,7 +670,7 @@ static int checkout(int submodule_progress) return 0; } if (!strcmp(head, "HEAD")) { - if (advice_detached_head) + if (advice_enabled(ADVICE_DETACHED_HEAD)) detach_advice(oid_to_hex(&oid)); FREE_AND_NULL(head); } else { @@ -803,6 +687,7 @@ static int checkout(int submodule_progress) opts.update = 1; opts.merge = 1; opts.clone = 1; + opts.preserve_ignored = 0; opts.fn = oneway_merge; opts.verbose_update = (option_verbosity >= 0); opts.src_index = &the_index; @@ -1041,8 +926,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (argc == 2) dir = xstrdup(argv[1]); else - dir = guess_dir_name(repo_name, is_bundle, option_bare); - strip_trailing_slashes(dir); + dir = git_url_basename(repo_name, is_bundle, option_bare); + strip_dir_trailing_slashes(dir); dest_exists = path_exists(dir); if (dest_exists && !is_empty_dir(dir)) @@ -1114,6 +999,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_recurse_submodules.nr > 0) { struct string_list_item *item; struct strbuf sb = STRBUF_INIT; + int val; /* remove duplicates */ string_list_sort(&option_recurse_submodules); @@ -1130,6 +1016,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_detach(&sb, NULL)); } + if (!git_config_get_bool("submodule.stickyRecursiveClone", &val) && + val) + string_list_append(&option_config, "submodule.recurse=true"); + if (option_required_reference.nr && option_optional_reference.nr) die(_("clone --recursive is not compatible with " @@ -1340,6 +1230,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) our_head_points_at = remote_head_points_at; } else { + const char *branch; + char *ref; + if (option_branch) die(_("Remote branch %s not found in upstream %s"), option_branch, remote_name); @@ -1350,24 +1243,22 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; - if (!option_bare) { - const char *branch; - char *ref; - - if (transport_ls_refs_options.unborn_head_target && - skip_prefix(transport_ls_refs_options.unborn_head_target, - "refs/heads/", &branch)) { - ref = transport_ls_refs_options.unborn_head_target; - transport_ls_refs_options.unborn_head_target = NULL; - create_symref("HEAD", ref, reflog_msg.buf); - } else { - branch = git_default_branch_name(0); - ref = xstrfmt("refs/heads/%s", branch); - } - install_branch_config(0, branch, remote_name, ref); - free(ref); + if (transport_ls_refs_options.unborn_head_target && + skip_prefix(transport_ls_refs_options.unborn_head_target, + "refs/heads/", &branch)) { + ref = transport_ls_refs_options.unborn_head_target; + transport_ls_refs_options.unborn_head_target = NULL; + create_symref("HEAD", ref, reflog_msg.buf); + } else { + branch = git_default_branch_name(0); + ref = xstrfmt("refs/heads/%s", branch); } + + if (!option_bare) + install_branch_config(0, branch, remote_name, ref); + + free(ref); } write_refspec_config(src_ref_prefix, our_head_points_at, diff --git a/builtin/column.c b/builtin/column.c index 40d4b3bee2..158fdf53d9 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -29,7 +29,7 @@ int cmd_column(int argc, const char **argv, const char *prefix) OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")), OPT_INTEGER(0, "width", &copts.width, N_("maximum width")), OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")), - OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")), + OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")), OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")), OPT_END() }; diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index cd86315221..3c3de3a156 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -9,26 +9,29 @@ #include "progress.h" #include "tag.h" -static char const * const builtin_commit_graph_usage[] = { - N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), - N_("git commit-graph write [--object-dir <objdir>] [--append] " - "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " - "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " - "<split options>"), +#define BUILTIN_COMMIT_GRAPH_VERIFY_USAGE \ + N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]") + +#define BUILTIN_COMMIT_GRAPH_WRITE_USAGE \ + N_("git commit-graph write [--object-dir <objdir>] [--append] " \ + "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " \ + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " \ + "<split options>") + +static const char * builtin_commit_graph_verify_usage[] = { + BUILTIN_COMMIT_GRAPH_VERIFY_USAGE, NULL }; -static const char * const builtin_commit_graph_verify_usage[] = { - N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), +static const char * builtin_commit_graph_write_usage[] = { + BUILTIN_COMMIT_GRAPH_WRITE_USAGE, NULL }; -static const char * const builtin_commit_graph_write_usage[] = { - N_("git commit-graph write [--object-dir <objdir>] [--append] " - "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " - "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " - "<split options>"), - NULL +static char const * const builtin_commit_graph_usage[] = { + BUILTIN_COMMIT_GRAPH_VERIFY_USAGE, + BUILTIN_COMMIT_GRAPH_WRITE_USAGE, + NULL, }; static struct opts_commit_graph { @@ -43,26 +46,16 @@ static struct opts_commit_graph { int enable_changed_paths; } opts; -static struct object_directory *find_odb(struct repository *r, - const char *obj_dir) -{ - struct object_directory *odb; - char *obj_dir_real = real_pathdup(obj_dir, 1); - struct strbuf odb_path_real = STRBUF_INIT; - - prepare_alt_odb(r); - for (odb = r->objects->odb; odb; odb = odb->next) { - strbuf_realpath(&odb_path_real, odb->path, 1); - if (!strcmp(obj_dir_real, odb_path_real.buf)) - break; - } - - free(obj_dir_real); - strbuf_release(&odb_path_real); +static struct option common_opts[] = { + OPT_STRING(0, "object-dir", &opts.obj_dir, + N_("dir"), + N_("the object directory to store the graph")), + OPT_END() +}; - if (!odb) - die(_("could not find object directory matching %s"), obj_dir); - return odb; +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) @@ -76,21 +69,22 @@ static int graph_verify(int argc, const char **argv) int flags = 0; static struct option builtin_commit_graph_verify_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("the object directory to store the graph")), OPT_BOOL(0, "shallow", &opts.shallow, N_("if the commit-graph is split, only verify the tip file")), - OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + OPT_BOOL(0, "progress", &opts.progress, + N_("force progress reporting")), OPT_END(), }; + struct option *options = add_common_options(builtin_commit_graph_verify_options); trace2_cmd_mode("verify"); opts.progress = isatty(2); argc = parse_options(argc, argv, NULL, - builtin_commit_graph_verify_options, + options, builtin_commit_graph_verify_usage, 0); + if (argc) + usage_with_options(builtin_commit_graph_verify_usage, options); if (!opts.obj_dir) opts.obj_dir = get_object_directory(); @@ -106,6 +100,7 @@ static int graph_verify(int argc, const char **argv) die_errno(_("Could not open commit-graph '%s'"), graph_name); FREE_AND_NULL(graph_name); + FREE_AND_NULL(options); if (open_ok) graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb); @@ -206,9 +201,6 @@ static int graph_write(int argc, const char **argv) struct progress *progress = NULL; static struct option builtin_commit_graph_write_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("the object directory to store the graph")), OPT_BOOL(0, "reachable", &opts.reachable, N_("start walk at all refs")), OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, @@ -219,7 +211,6 @@ static int graph_write(int argc, const char **argv) N_("include all commits already in the commit-graph file")), OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths, N_("enable computation for changed paths")), - OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL, N_("allow writing an incremental commit-graph file"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, @@ -233,8 +224,11 @@ static int graph_write(int argc, const char **argv) OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters, NULL, N_("maximum number of changed-path Bloom filters to compute"), 0, write_option_max_new_filters), + OPT_BOOL(0, "progress", &opts.progress, + N_("force progress reporting")), OPT_END(), }; + struct option *options = add_common_options(builtin_commit_graph_write_options); opts.progress = isatty(2); opts.enable_changed_paths = -1; @@ -248,8 +242,10 @@ static int graph_write(int argc, const char **argv) git_config(git_commit_graph_write_config, &opts); argc = parse_options(argc, argv, NULL, - builtin_commit_graph_write_options, + options, builtin_commit_graph_write_usage, 0); + if (argc) + usage_with_options(builtin_commit_graph_write_usage, options); if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1) die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs")); @@ -304,6 +300,7 @@ static int graph_write(int argc, const char **argv) result = 1; cleanup: + FREE_AND_NULL(options); string_list_clear(&pack_indexes, 0); strbuf_release(&buf); return result; @@ -311,32 +308,25 @@ cleanup: int cmd_commit_graph(int argc, const char **argv, const char *prefix) { - static struct option builtin_commit_graph_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("the object directory to store the graph")), - OPT_END(), - }; - - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(builtin_commit_graph_usage, - builtin_commit_graph_options); + struct option *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; save_commit_buffer = 0; - if (argc > 0) { - if (!strcmp(argv[0], "verify")) - return graph_verify(argc, argv); - if (!strcmp(argv[0], "write")) - return graph_write(argc, argv); - } + if (!strcmp(argv[0], "verify")) + return graph_verify(argc, argv); + else if (argc && !strcmp(argv[0], "write")) + return graph_write(argc, argv); + error(_("unrecognized subcommand: %s"), argv[0]); +usage: usage_with_options(builtin_commit_graph_usage, builtin_commit_graph_options); } diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 1031b9a491..63ea322933 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -88,9 +88,7 @@ static int parse_file_arg_callback(const struct option *opt, if (!strcmp(arg, "-")) fd = 0; else { - fd = open(arg, O_RDONLY); - if (fd < 0) - die_errno(_("git commit-tree: failed to open '%s'"), arg); + fd = xopen(arg, O_RDONLY); } if (strbuf_read(buf, fd, 0) < 0) die_errno(_("git commit-tree: failed to read '%s'"), arg); diff --git a/builtin/commit.c b/builtin/commit.c index 190d215d43..883c16256c 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -19,6 +19,7 @@ #include "revision.h" #include "wt-status.h" #include "run-command.h" +#include "hook.h" #include "refs.h" #include "log-tree.h" #include "strbuf.h" @@ -203,7 +204,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn) init_diff_ui_defaults(); git_config(fn, s); determine_whence(s); - s->hints = advice_status_hints; /* must come after git_config() */ + s->hints = advice_enabled(ADVICE_STATUS_HINTS); /* must come after git_config() */ } static void rollback_index_files(void) @@ -889,7 +890,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int ident_shown = 0; int saved_color_setting; struct ident_split ci, ai; - + const char *hint_cleanup_all = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\nwith '%c' will be ignored.\n") : + _("Please enter the commit message for your changes." + " Lines starting\nwith '%c' will be ignored, and an empty" + " message aborts the commit.\n"); + const char *hint_cleanup_space = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n") : + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n" + "An empty message aborts the commit.\n"); if (whence != FROM_COMMIT) { if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && !merge_contains_scissors) @@ -911,20 +927,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix, fprintf(s->fp, "\n"); if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL) - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\nwith '%c' will be ignored, and an empty" - " message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_char); else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { if (whence == FROM_COMMIT && !merge_contains_scissors) wt_status_add_cut_line(s->fp); } else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\n" - "with '%c' will be kept; you may remove them" - " yourself if you want to.\n" - "An empty message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_char); /* * These should never fail because they come from our own @@ -1026,7 +1034,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ if (!committable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { - s->hints = advice_status_hints; + s->hints = advice_enabled(ADVICE_STATUS_HINTS); s->display_comment_prefix = old_display_comment_prefix; run_status(stdout, index_file, prefix, 0, s); if (amend) @@ -1044,7 +1052,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (!no_verify && find_hook("pre-commit")) { + if (!no_verify && hook_exists("pre-commit")) { /* * Re-read the index as pre-commit hook could have updated it, * and write it out as a tree. We must do this before we invoke @@ -1246,8 +1254,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (logfile || have_option_m || use_message) use_editor = 0; - if (0 <= edit_flag) - use_editor = edit_flag; /* Sanity check options */ if (amend && !current_head) @@ -1337,6 +1343,9 @@ static int parse_and_validate_options(int argc, const char *argv[], } } + if (0 <= edit_flag) + use_editor = edit_flag; + cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); @@ -1510,6 +1519,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_status_usage, builtin_status_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_status_config); argc = parse_options(argc, argv, prefix, builtin_status_options, @@ -1679,6 +1691,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_commit_config); s.commit_template = 1; status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ diff --git a/builtin/config.c b/builtin/config.c index 865fddd6ce..542d8d02b2 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -575,7 +575,7 @@ static int get_urlmatch(const char *var, const char *url) int ret; char *section_tail; struct string_list_item *item; - struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + struct urlmatch_config config = URLMATCH_CONFIG_INIT; struct string_list values = STRING_LIST_INIT_DUP; config.collect_fn = urlmatch_collect_fn; diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c index 76a6ba3722..78c02ad531 100644 --- a/builtin/credential-cache.c +++ b/builtin/credential-cache.c @@ -11,6 +11,32 @@ #define FLAG_SPAWN 0x1 #define FLAG_RELAY 0x2 +#ifdef GIT_WINDOWS_NATIVE + +static int connection_closed(int error) +{ + return (error == EINVAL); +} + +static int connection_fatally_broken(int error) +{ + return (error != ENOENT) && (error != ENETDOWN); +} + +#else + +static int connection_closed(int error) +{ + return (error == ECONNRESET); +} + +static int connection_fatally_broken(int error) +{ + return (error != ENOENT) && (error != ECONNREFUSED); +} + +#endif + static int send_request(const char *socket, const struct strbuf *out) { int got_data = 0; @@ -28,7 +54,7 @@ static int send_request(const char *socket, const struct strbuf *out) int r; r = read_in_full(fd, in, sizeof(in)); - if (r == 0 || (r < 0 && errno == ECONNRESET)) + if (r == 0 || (r < 0 && connection_closed(errno))) break; if (r < 0) die_errno("read error from cache daemon"); @@ -75,7 +101,7 @@ static void do_cache(const char *socket, const char *action, int timeout, } if (send_request(socket, &buf) < 0) { - if (errno != ENOENT && errno != ECONNREFUSED) + if (connection_fatally_broken(errno)) die_errno("unable to connect to cache daemon"); if (flags & FLAG_SPAWN) { spawn_daemon(socket); @@ -90,7 +116,7 @@ static char *get_socket_path(void) { struct stat sb; char *old_dir, *socket; - old_dir = expand_user_path("~/.git-credential-cache", 0); + old_dir = interpolate_path("~/.git-credential-cache", 0); if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode)) socket = xstrfmt("%s/socket", old_dir); else diff --git a/builtin/credential-store.c b/builtin/credential-store.c index ae3c1ba75f..62a4f3c265 100644 --- a/builtin/credential-store.c +++ b/builtin/credential-store.c @@ -173,7 +173,7 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix) if (file) { string_list_append(&fns, file); } else { - if ((file = expand_user_path("~/.git-credentials", 0))) + if ((file = interpolate_path("~/.git-credentials", 0))) string_list_append_nodup(&fns, file); file = xdg_config_home("credentials"); if (file) diff --git a/builtin/diff-index.c b/builtin/diff-index.c index cf09559e42..5fd23ab5b6 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -29,10 +29,10 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) prefix = precompose_argv_prefix(argc, argv, prefix); /* - * We need no diff for merges options, and we need to avoid conflict - * with our own meaning of "-m". + * We need (some of) diff for merges options (e.g., --cc), and we need + * to avoid conflict with our own meaning of "-m". */ - diff_merges_suppress_options_parsing(); + diff_merges_suppress_m_parsing(); argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { diff --git a/builtin/diff.c b/builtin/diff.c index 2d87c37a17..dd8ce688ba 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -26,8 +26,8 @@ static const char builtin_diff_usage[] = "git diff [<options>] [<commit>] [--] [<path>...]\n" -" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n" -" or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n" +" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n" " or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" " or: git diff [<options>] <blob> <blob>]\n" " or: git diff [<options>] --no-index [--] <path> <path>]\n" diff --git a/builtin/difftool.c b/builtin/difftool.c index 6a9242a803..4931c10845 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -252,16 +252,6 @@ static void changed_files(struct hashmap *result, const char *index_path, strbuf_release(&buf); } -static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) -{ - struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, tmpdir); - remove_dir_recursively(&buf, 0); - if (exit_code) - warning(_("failed: %d"), exit_code); - exit(exit_code); -} - static int ensure_leading_directories(char *path) { switch (safe_create_leading_directories(path)) { @@ -330,19 +320,44 @@ static int checkout_path(unsigned mode, struct object_id *oid, return ret; } +static void write_file_in_directory(struct strbuf *dir, size_t dir_len, + const char *path, const char *content) +{ + add_path(dir, dir_len, path); + ensure_leading_directories(dir->buf); + unlink(dir->buf); + write_file(dir->buf, "%s", content); +} + +/* Write the file contents for the left and right sides of the difftool + * dir-diff representation for submodules and symlinks. Symlinks and submodules + * are written as regular text files so that external diff tools can diff them + * as text files, resulting in behavior that is analogous to to what "git diff" + * displays for symlink and submodule diffs. + */ +static void write_standin_files(struct pair_entry *entry, + struct strbuf *ldir, size_t ldir_len, + struct strbuf *rdir, size_t rdir_len) +{ + if (*entry->left) + write_file_in_directory(ldir, ldir_len, entry->path, entry->left); + if (*entry->right) + write_file_in_directory(rdir, rdir_len, entry->path, entry->right); +} + static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, - int argc, const char **argv) + struct child_process *child) { - char tmpdir[PATH_MAX]; struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; struct strbuf wtdir = STRBUF_INIT; - char *lbase_dir, *rbase_dir; + struct strbuf tmpdir = STRBUF_INIT; + char *lbase_dir = NULL, *rbase_dir = NULL; size_t ldir_len, rdir_len, wtdir_len; const char *workdir, *tmp; int ret = 0, i; - FILE *fp; + FILE *fp = NULL; struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp, NULL); struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL); @@ -351,8 +366,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, struct pair_entry *entry; struct index_state wtindex; struct checkout lstate, rstate; - int rc, flags = RUN_GIT_CMD, err = 0; - struct child_process child = CHILD_PROCESS_INIT; + int flags = RUN_GIT_CMD, err = 0; const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; struct hashmap wt_modified, tmp_modified; int indices_loaded = 0; @@ -361,11 +375,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, /* Setup temp directories */ tmp = getenv("TMPDIR"); - xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); - if (!mkdtemp(tmpdir)) - return error("could not create '%s'", tmpdir); - strbuf_addf(&ldir, "%s/left/", tmpdir); - strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_add_absolute_path(&tmpdir, tmp ? tmp : "/tmp"); + strbuf_trim_trailing_dir_sep(&tmpdir); + strbuf_addstr(&tmpdir, "/git-difftool.XXXXXX"); + if (!mkdtemp(tmpdir.buf)) { + ret = error("could not create '%s'", tmpdir.buf); + goto finish; + } + strbuf_addf(&ldir, "%s/left/", tmpdir.buf); + strbuf_addf(&rdir, "%s/right/", tmpdir.buf); strbuf_addstr(&wtdir, workdir); if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) strbuf_addch(&wtdir, '/'); @@ -387,19 +405,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, rdir_len = rdir.len; wtdir_len = wtdir.len; - child.no_stdin = 1; - child.git_cmd = 1; - child.use_shell = 0; - child.clean_on_exit = 1; - child.dir = prefix; - child.out = -1; - strvec_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", - NULL); - for (i = 0; i < argc; i++) - strvec_push(&child.args, argv[i]); - if (start_command(&child)) + child->no_stdin = 1; + child->git_cmd = 1; + child->use_shell = 0; + child->clean_on_exit = 1; + child->dir = prefix; + child->out = -1; + if (start_command(child)) die("could not obtain raw diff"); - fp = xfdopen(child.out, "r"); + fp = xfdopen(child->out, "r"); /* Build index info for left and right sides of the diff */ i = 0; @@ -410,9 +424,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, const char *src_path, *dst_path; if (starts_with(info.buf, "::")) - die(N_("combined diff formats('-c' and '--cc') are " + die(N_("combined diff formats ('-c' and '--cc') are " "not supported in\n" - "directory diff mode('-d' and '--dir-diff').")); + "directory diff mode ('-d' and '--dir-diff').")); if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, &status)) @@ -525,7 +539,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, fclose(fp); fp = NULL; - if (finish_command(&child)) { + if (finish_command(child)) { ret = error("error occurred running diff --raw"); goto finish; } @@ -540,38 +554,19 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, */ hashmap_for_each_entry(&submodules, &iter, entry, entry /* member name */) { - if (*entry->left) { - add_path(&ldir, ldir_len, entry->path); - ensure_leading_directories(ldir.buf); - write_file(ldir.buf, "%s", entry->left); - } - if (*entry->right) { - add_path(&rdir, rdir_len, entry->path); - ensure_leading_directories(rdir.buf); - write_file(rdir.buf, "%s", entry->right); - } + write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); } /* - * Symbolic links require special treatment.The standard "git diff" + * Symbolic links require special treatment. The standard "git diff" * shows only the link itself, not the contents of the link target. * This loop replicates that behavior. */ hashmap_for_each_entry(&symlinks2, &iter, entry, entry /* member name */) { - if (*entry->left) { - add_path(&ldir, ldir_len, entry->path); - ensure_leading_directories(ldir.buf); - write_file(ldir.buf, "%s", entry->left); - } - if (*entry->right) { - add_path(&rdir, rdir_len, entry->path); - ensure_leading_directories(rdir.buf); - write_file(rdir.buf, "%s", entry->right); - } - } - strbuf_release(&buf); + write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); + } strbuf_setlen(&ldir, ldir_len); helper_argv[1] = ldir.buf; @@ -583,7 +578,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, flags = 0; } else setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); - rc = run_command_v_opt(helper_argv, flags); + ret = run_command_v_opt(helper_argv, flags); /* TODO: audit for interaction with sparse-index. */ ensure_full_index(&wtindex); @@ -617,7 +612,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, if (!indices_loaded) { struct lock_file lock = LOCK_INIT; strbuf_reset(&buf); - strbuf_addf(&buf, "%s/wtindex", tmpdir); + strbuf_addf(&buf, "%s/wtindex", tmpdir.buf); if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { ret = error("could not write %s", buf.buf); @@ -647,11 +642,14 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, } if (err) { - warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("temporary files exist in '%s'."), tmpdir.buf); warning(_("you may want to cleanup or recover these.")); - exit(1); - } else - exit_cleanup(tmpdir, rc); + ret = 1; + } else { + remove_dir_recursively(&tmpdir, 0); + if (ret) + warning(_("failed: %d"), ret); + } finish: if (fp) @@ -663,30 +661,29 @@ finish: strbuf_release(&rdir); strbuf_release(&wtdir); strbuf_release(&buf); + strbuf_release(&tmpdir); - return ret; + return (ret < 0) ? 1 : ret; } static int run_file_diff(int prompt, const char *prefix, - int argc, const char **argv) + struct child_process *child) { - struct strvec args = STRVEC_INIT; const char *env[] = { "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, NULL }; - int i; if (prompt > 0) env[2] = "GIT_DIFFTOOL_PROMPT=true"; else if (!prompt) env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + child->git_cmd = 1; + child->dir = prefix; + strvec_pushv(&child->env_array, env); - strvec_push(&args, "diff"); - for (i = 0; i < argc; i++) - strvec_push(&args, argv[i]); - return run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env); + return run_command(child); } int cmd_difftool(int argc, const char **argv, const char *prefix) @@ -713,12 +710,13 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) "`--tool`")), OPT_BOOL(0, "trust-exit-code", &trust_exit_code, N_("make 'git-difftool' exit when an invoked diff " - "tool returns a non - zero exit code")), + "tool returns a non-zero exit code")), OPT_STRING('x', "extcmd", &extcmd, N_("command"), N_("specify a custom command for viewing diffs")), - OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")), + OPT_BOOL(0, "no-index", &no_index, N_("passed to `diff`")), OPT_END() }; + struct child_process child = CHILD_PROCESS_INIT; git_config(difftool_config, NULL); symlinks = has_symlinks; @@ -768,7 +766,14 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) * will invoke a separate instance of 'git-difftool--helper' for * each file that changed. */ + strvec_push(&child.args, "diff"); + if (no_index) + strvec_push(&child.args, "--no-index"); + if (dir_diff) + strvec_pushl(&child.args, "--raw", "--no-abbrev", "-z", NULL); + strvec_pushv(&child.args, argv); + if (dir_diff) - return run_dir_diff(extcmd, symlinks, prefix, argc, argv); - return run_file_diff(prompt, prefix, argc, argv); + return run_dir_diff(extcmd, symlinks, prefix, &child); + return run_file_diff(prompt, prefix, &child); } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 3c20f164f0..8e2caf7281 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -312,7 +312,7 @@ static void export_blob(const struct object_id *oid) if (!buf) die("could not read blob %s", oid_to_hex(oid)); if (check_object_signature(the_repository, oid, buf, size, - type_name(type)) < 0) + type_name(type), NULL) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); @@ -821,6 +821,7 @@ static void handle_tag(const char *name, struct tag *tag) static struct hashmap tags; message = anonymize_str(&tags, anonymize_tag, message, message_size, NULL); + message_size = strlen(message); } } diff --git a/builtin/fetch.c b/builtin/fetch.c index 25740c13df..f7abbc31ff 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -712,7 +712,7 @@ static void adjust_refcol_width(const struct ref *ref) int max, rlen, llen, len; /* uptodate lines are only shown on high verbosity level */ - if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid)) + if (verbosity <= 0 && oideq(&ref->peer_ref->old_oid, &ref->old_oid)) return; max = term_columns(); @@ -748,6 +748,9 @@ static void prepare_format_display(struct ref *ref_map) struct ref *rm; const char *format = "full"; + if (verbosity < 0) + return; + git_config_get_string_tmp("fetch.output", &format); if (!strcasecmp(format, "full")) compact_format = 0; @@ -827,7 +830,12 @@ static void format_display(struct strbuf *display, char code, const char *remote, const char *local, int summary_width) { - int width = (summary_width + strlen(summary) - gettext_width(summary)); + int width; + + if (verbosity < 0) + return; + + width = (summary_width + strlen(summary) - gettext_width(summary)); strbuf_addf(display, "%c %-*s ", code, width, summary); if (!compact_format) @@ -846,13 +854,11 @@ static int update_local_ref(struct ref *ref, int summary_width) { struct commit *current = NULL, *updated; - enum object_type type; struct branch *current_branch = branch_get(NULL); const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; - type = oid_object_info(the_repository, &ref->new_oid, NULL); - if (type < 0) + if (!repo_has_object_file(the_repository, &ref->new_oid)) die(_("object %s not found"), oid_to_hex(&ref->new_oid)); if (oideq(&ref->old_oid, &ref->new_oid)) { @@ -964,7 +970,7 @@ static int update_local_ref(struct ref *ref, } } -static int iterate_ref_map(void *cb_data, struct object_id *oid) +static const struct object_id *iterate_ref_map(void *cb_data) { struct ref **rm = cb_data; struct ref *ref = *rm; @@ -972,10 +978,9 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid) while (ref && ref->status == REF_STATUS_REJECT_SHALLOW) ref = ref->next; if (!ref) - return -1; /* end of the list */ + return NULL; *rm = ref->next; - oidcpy(oid, &ref->old_oid); - return 0; + return &ref->old_oid; } struct fetch_head { @@ -1074,7 +1079,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref *ref_map) { struct fetch_head fetch_head; - struct commit *commit; int url_len, i, rc = 0; struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; struct ref_transaction *transaction = NULL; @@ -1122,6 +1126,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, want_status <= FETCH_HEAD_IGNORE; want_status++) { for (rm = ref_map; rm; rm = rm->next) { + struct commit *commit = NULL; struct ref *ref = NULL; if (rm->status == REF_STATUS_REJECT_SHALLOW) { @@ -1131,11 +1136,23 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, continue; } - commit = lookup_commit_reference_gently(the_repository, - &rm->old_oid, - 1); - if (!commit) - rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + /* + * References in "refs/tags/" are often going to point + * to annotated tags, which are not part of the + * commit-graph. We thus only try to look up refs in + * the graph which are not in that namespace to not + * regress performance in repositories with many + * annotated tags. + */ + if (!starts_with(rm->name, "refs/tags/")) + commit = lookup_commit_in_graph(the_repository, &rm->old_oid); + if (!commit) { + commit = lookup_commit_reference_gently(the_repository, + &rm->old_oid, + 1); + if (!commit) + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + } if (rm->fetch_head_status != want_status) continue; @@ -1202,13 +1219,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, "FETCH_HEAD", summary_width); } if (note.len) { - if (verbosity >= 0 && !shown_url) { + if (!shown_url) { fprintf(stderr, _("From %.*s\n"), url_len, url); shown_url = 1; } - if (verbosity >= 0) - fprintf(stderr, " %s\n", note.buf); + fprintf(stderr, " %s\n", note.buf); } } } @@ -1229,7 +1245,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, " 'git remote prune %s' to remove any old, conflicting " "branches"), remote_name); - if (advice_fetch_show_forced_updates) { + if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) { if (!fetch_show_forced_updates) { warning(_(warn_show_forced_updates)); } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) { @@ -1282,37 +1298,35 @@ static int check_exist_and_connected(struct ref *ref_map) return check_connected(iterate_ref_map, &rm, &opt); } -static int fetch_refs(struct transport *transport, struct ref *ref_map) +static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map) { - int ret = check_exist_and_connected(ref_map); + int connectivity_checked = 1; + int ret; + + /* + * We don't need to perform a fetch in case we can already satisfy all + * refs. + */ + ret = check_exist_and_connected(ref_map); if (ret) { trace2_region_enter("fetch", "fetch_refs", the_repository); ret = transport_fetch_refs(transport, ref_map); trace2_region_leave("fetch", "fetch_refs", the_repository); + if (ret) + goto out; + connectivity_checked = transport->smart_options ? + transport->smart_options->connectivity_checked : 0; } - if (!ret) - /* - * Keep the new pack's ".keep" file around to allow the caller - * time to update refs to reference the new objects. - */ - return 0; - transport_unlock_pack(transport); - return ret; -} -/* Update local refs based on the ref values fetched from a remote */ -static int consume_refs(struct transport *transport, struct ref *ref_map) -{ - int connectivity_checked = transport->smart_options - ? transport->smart_options->connectivity_checked : 0; - int ret; trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(transport->url, transport->remote->name, connectivity_checked, ref_map); - transport_unlock_pack(transport); trace2_region_leave("fetch", "consume_refs", the_repository); + +out: + transport_unlock_pack(transport); return ret; } @@ -1428,7 +1442,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) if (!has_glob_specials(s)) { struct object_id oid; if (get_oid(s, &oid)) - die("%s is not a valid object", s); + die(_("%s is not a valid object"), s); + if (!has_object(the_repository, &oid, 0)) + die(_("the object %s does not exist"), s); oid_array_append(oids, &oid); continue; } @@ -1499,8 +1515,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) 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); - if (!fetch_refs(transport, ref_map)) - consume_refs(transport, ref_map); + fetch_and_consume_refs(transport, ref_map); if (gsecondary) { transport_disconnect(gsecondary); @@ -1591,7 +1606,7 @@ static int do_fetch(struct transport *transport, transport->url); } } - if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) { + if (fetch_and_consume_refs(transport, ref_map)) { free_refs(ref_map); retcode = 1; goto cleanup; @@ -2133,8 +2148,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) NULL); } - close_object_store(the_repository->objects); - if (enable_auto_gc) run_auto_maintenance(verbosity < 0); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 89cb6307d4..642b4b888f 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -77,7 +77,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) filter.name_patterns = argv; filter.match_as_path = 1; - filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); + filter_refs(&array, &filter, FILTER_REFS_ALL); ref_array_sort(sorting, &array); if (!maxcount || array.nr < maxcount) diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c index 52be64a437..fd86e5a861 100644 --- a/builtin/for-each-repo.c +++ b/builtin/for-each-repo.c @@ -10,18 +10,16 @@ static const char * const for_each_repo_usage[] = { NULL }; -static int run_command_on_repo(const char *path, - void *cbdata) +static int run_command_on_repo(const char *path, int argc, const char ** argv) { int i; struct child_process child = CHILD_PROCESS_INIT; - struct strvec *args = (struct strvec *)cbdata; child.git_cmd = 1; strvec_pushl(&child.args, "-C", path, NULL); - for (i = 0; i < args->nr; i++) - strvec_push(&child.args, args->v[i]); + for (i = 0; i < argc; i++) + strvec_push(&child.args, argv[i]); return run_command(&child); } @@ -31,7 +29,6 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix) static const char *config_key = NULL; int i, result = 0; const struct string_list *values; - struct strvec args = STRVEC_INIT; const struct option options[] = { OPT_STRING(0, "config", &config_key, N_("config"), @@ -45,9 +42,6 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix) if (!config_key) die(_("missing --config=<config>")); - for (i = 0; i < argc; i++) - strvec_push(&args, argv[i]); - values = repo_config_get_value_multi(the_repository, config_key); @@ -59,7 +53,7 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix) return 0; for (i = 0; !result && i < values->nr; i++) - result = run_command_on_repo(values->items[i].string, &args); + result = run_command_on_repo(values->items[i].string, argc, argv); return result; } diff --git a/builtin/fsck.c b/builtin/fsck.c index b42b6fe21f..30a516da29 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -593,18 +593,43 @@ static void get_default_heads(void) } } +struct for_each_loose_cb +{ + struct progress *progress; + struct strbuf obj_type; +}; + static int fsck_loose(const struct object_id *oid, const char *path, void *data) { + struct for_each_loose_cb *cb_data = data; struct object *obj; - enum object_type type; + enum object_type type = OBJ_NONE; unsigned long size; void *contents; int eaten; + struct object_info oi = OBJECT_INFO_INIT; + struct object_id real_oid = *null_oid(); + int err = 0; - if (read_loose_object(path, oid, &type, &size, &contents) < 0) { + strbuf_reset(&cb_data->obj_type); + oi.type_name = &cb_data->obj_type; + oi.sizep = &size; + oi.typep = &type; + + if (read_loose_object(path, oid, &real_oid, &contents, &oi) < 0) { + if (contents && !oideq(&real_oid, oid)) + err = error(_("%s: hash-path mismatch, found at: %s"), + oid_to_hex(&real_oid), path); + else + err = error(_("%s: object corrupt or missing: %s"), + oid_to_hex(oid), path); + } + if (type != OBJ_NONE && type < 0) + err = error(_("%s: object is of unknown type '%s': %s"), + oid_to_hex(&real_oid), cb_data->obj_type.buf, + path); + if (err < 0) { errors_found |= ERROR_OBJECT; - error(_("%s: object corrupt or missing: %s"), - oid_to_hex(oid), path); return 0; /* keep checking other objects */ } @@ -640,8 +665,10 @@ static int fsck_cruft(const char *basename, const char *path, void *data) return 0; } -static int fsck_subdir(unsigned int nr, const char *path, void *progress) +static int fsck_subdir(unsigned int nr, const char *path, void *data) { + struct for_each_loose_cb *cb_data = data; + struct progress *progress = cb_data->progress; display_progress(progress, nr + 1); return 0; } @@ -649,6 +676,10 @@ static int fsck_subdir(unsigned int nr, const char *path, void *progress) static void fsck_object_dir(const char *path) { struct progress *progress = NULL; + struct for_each_loose_cb cb_data = { + .obj_type = STRBUF_INIT, + .progress = progress, + }; if (verbose) fprintf_ln(stderr, _("Checking object directory")); @@ -657,9 +688,10 @@ static void fsck_object_dir(const char *path) progress = start_progress(_("Checking object directories"), 256); for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir, - progress); + &cb_data); display_progress(progress, 256); stop_progress(&progress); + strbuf_release(&cb_data.obj_type); } static int fsck_head_link(const char *head_ref_name, diff --git a/builtin/gc.c b/builtin/gc.c index f05d2f0a1a..6b3de3dd51 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -502,7 +502,7 @@ static int report_last_gc_error(void) */ warning(_("The last gc run reported the following. " "Please correct the root cause\n" - "and remove %s.\n" + "and remove %s\n" "Automatic cleanup will not be performed " "until the file is removed.\n\n" "%s"), @@ -663,8 +663,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) gc_before_repack(); if (!repository_format_precious_objects) { - close_object_store(the_repository->objects); - if (run_command_v_opt(repack.v, RUN_GIT_CMD)) + if (run_command_v_opt(repack.v, + RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE)) die(FAILED_RUN, repack.v[0]); if (prune_expire) { @@ -848,7 +848,7 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = 1; + child.git_cmd = child.close_object_store = 1; strvec_pushl(&child.args, "commit-graph", "write", "--split", "--reachable", NULL); @@ -864,7 +864,6 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) if (!the_repository->settings.core_commit_graph) return 0; - close_object_store(the_repository->objects); if (run_write_commit_graph(opts)) { error(_("failed to write commit-graph")); return 1; @@ -913,7 +912,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = 1; + child.git_cmd = child.close_object_store = 1; strvec_push(&child.args, "gc"); if (opts->auto_flag) @@ -923,7 +922,6 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts) else strvec_push(&child.args, "--no-quiet"); - close_object_store(the_repository->objects); return run_command(&child); } @@ -1097,14 +1095,12 @@ static int multi_pack_index_expire(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = 1; + child.git_cmd = child.close_object_store = 1; strvec_pushl(&child.args, "multi-pack-index", "expire", NULL); if (opts->quiet) strvec_push(&child.args, "--no-progress"); - close_object_store(the_repository->objects); - if (run_command(&child)) return error(_("'git multi-pack-index expire' failed")); @@ -1155,7 +1151,7 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = 1; + child.git_cmd = child.close_object_store = 1; strvec_pushl(&child.args, "multi-pack-index", "repack", NULL); if (opts->quiet) @@ -1164,8 +1160,6 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts) strvec_pushf(&child.args, "--batch-size=%"PRIuMAX, (uintmax_t)get_auto_pack_size()); - close_object_store(the_repository->objects); - if (run_command(&child)) return error(_("'git multi-pack-index repack' failed")); @@ -1529,6 +1523,93 @@ static const char *get_frequency(enum schedule_priority schedule) } } +/* + * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable + * to mock the schedulers that `git maintenance start` rely on. + * + * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated + * list of colon-separated key/value pairs where each pair contains a scheduler + * and its corresponding mock. + * + * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the + * arguments unmodified. + * + * * If $GIT_TEST_MAINT_SCHEDULER is set, return true. + * In this case, the *cmd value is read as input. + * + * * if the input value *cmd is the key of one of the comma-separated list + * item, then *is_available is set to true and *cmd is modified and becomes + * the mock command. + * + * * if the input value *cmd isn’t the key of any of the comma-separated list + * item, then *is_available is set to false. + * + * Ex.: + * GIT_TEST_MAINT_SCHEDULER not set + * +-------+-------------------------------------------------+ + * | Input | Output | + * | *cmd | return code | *cmd | *is_available | + * +-------+-------------+-------------------+---------------+ + * | "foo" | false | "foo" (unchanged) | (unchanged) | + * +-------+-------------+-------------------+---------------+ + * + * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh” + * +-------+-------------------------------------------------+ + * | Input | Output | + * | *cmd | return code | *cmd | *is_available | + * +-------+-------------+-------------------+---------------+ + * | "foo" | true | "./mock.foo.sh" | true | + * | "qux" | true | "qux" (unchanged) | false | + * +-------+-------------+-------------------+---------------+ + */ +static int get_schedule_cmd(const char **cmd, int *is_available) +{ + char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER")); + struct string_list_item *item; + struct string_list list = STRING_LIST_INIT_NODUP; + + if (!testing) + return 0; + + if (is_available) + *is_available = 0; + + string_list_split_in_place(&list, testing, ',', -1); + for_each_string_list_item(item, &list) { + struct string_list pair = STRING_LIST_INIT_NODUP; + + if (string_list_split_in_place(&pair, item->string, ':', 2) != 2) + continue; + + if (!strcmp(*cmd, pair.items[0].string)) { + *cmd = pair.items[1].string; + if (is_available) + *is_available = 1; + string_list_clear(&list, 0); + UNLEAK(testing); + return 1; + } + } + + string_list_clear(&list, 0); + free(testing); + return 1; +} + +static int is_launchctl_available(void) +{ + const char *cmd = "launchctl"; + int is_available; + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + +#ifdef __APPLE__ + return 1; +#else + return 0; +#endif +} + static char *launchctl_service_name(const char *frequency) { struct strbuf label = STRBUF_INIT; @@ -1542,7 +1623,7 @@ static char *launchctl_service_filename(const char *name) struct strbuf filename = STRBUF_INIT; strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name); - expanded = expand_user_path(filename.buf, 1); + expanded = interpolate_path(filename.buf, 1); if (!expanded) die(_("failed to expand path '%s'"), filename.buf); @@ -1555,19 +1636,17 @@ static char *launchctl_get_uid(void) return xstrfmt("gui/%d", getuid()); } -static int launchctl_boot_plist(int enable, const char *filename, const char *cmd) +static int launchctl_boot_plist(int enable, const char *filename) { + const char *cmd = "launchctl"; int result; struct child_process child = CHILD_PROCESS_INIT; char *uid = launchctl_get_uid(); + get_schedule_cmd(&cmd, NULL); strvec_split(&child.args, cmd); - if (enable) - strvec_push(&child.args, "bootstrap"); - else - strvec_push(&child.args, "bootout"); - strvec_push(&child.args, uid); - strvec_push(&child.args, filename); + strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid, + filename, NULL); child.no_stderr = 1; child.no_stdout = 1; @@ -1581,38 +1660,56 @@ static int launchctl_boot_plist(int enable, const char *filename, const char *cm return result; } -static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd) +static int launchctl_remove_plist(enum schedule_priority schedule) { const char *frequency = get_frequency(schedule); char *name = launchctl_service_name(frequency); char *filename = launchctl_service_filename(name); - int result = launchctl_boot_plist(0, filename, cmd); + int result = launchctl_boot_plist(0, filename); unlink(filename); free(filename); free(name); return result; } -static int launchctl_remove_plists(const char *cmd) +static int launchctl_remove_plists(void) { - return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) || - launchctl_remove_plist(SCHEDULE_DAILY, cmd) || - launchctl_remove_plist(SCHEDULE_WEEKLY, cmd); + return launchctl_remove_plist(SCHEDULE_HOURLY) || + launchctl_remove_plist(SCHEDULE_DAILY) || + launchctl_remove_plist(SCHEDULE_WEEKLY); } -static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd) +static int launchctl_list_contains_plist(const char *name, const char *cmd) { - FILE *plist; - int i; + struct child_process child = CHILD_PROCESS_INIT; + + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "list", name, NULL); + + child.no_stderr = 1; + child.no_stdout = 1; + + if (start_command(&child)) + die(_("failed to start launchctl")); + + /* Returns failure if 'name' doesn't exist. */ + return !finish_command(&child); +} + +static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule) +{ + int i, fd; const char *preamble, *repeat; const char *frequency = get_frequency(schedule); char *name = launchctl_service_name(frequency); char *filename = launchctl_service_filename(name); + struct lock_file lk = LOCK_INIT; + static unsigned long lock_file_timeout_ms = ULONG_MAX; + struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT; + struct stat st; + const char *cmd = "launchctl"; - if (safe_create_leading_directories(filename)) - die(_("failed to create directories for '%s'"), filename); - plist = xfopen(filename, "w"); - + get_schedule_cmd(&cmd, NULL); preamble = "<?xml version=\"1.0\"?>\n" "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" "<plist version=\"1.0\">" @@ -1630,7 +1727,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit "</array>\n" "<key>StartCalendarInterval</key>\n" "<array>\n"; - fprintf(plist, preamble, name, exec_path, exec_path, frequency); + strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency); switch (schedule) { case SCHEDULE_HOURLY: @@ -1639,7 +1736,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit "<key>Minute</key><integer>0</integer>\n" "</dict>\n"; for (i = 1; i <= 23; i++) - fprintf(plist, repeat, i); + strbuf_addf(&plist, repeat, i); break; case SCHEDULE_DAILY: @@ -1649,50 +1746,91 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit "<key>Minute</key><integer>0</integer>\n" "</dict>\n"; for (i = 1; i <= 6; i++) - fprintf(plist, repeat, i); + strbuf_addf(&plist, repeat, i); break; case SCHEDULE_WEEKLY: - fprintf(plist, - "<dict>\n" - "<key>Day</key><integer>0</integer>\n" - "<key>Hour</key><integer>0</integer>\n" - "<key>Minute</key><integer>0</integer>\n" - "</dict>\n"); + strbuf_addstr(&plist, + "<dict>\n" + "<key>Day</key><integer>0</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"); break; default: /* unreachable */ break; } - fprintf(plist, "</array>\n</dict>\n</plist>\n"); - fclose(plist); + strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n"); + + if (safe_create_leading_directories(filename)) + die(_("failed to create directories for '%s'"), filename); - /* bootout might fail if not already running, so ignore */ - launchctl_boot_plist(0, filename, cmd); - if (launchctl_boot_plist(1, filename, cmd)) - die(_("failed to bootstrap service %s"), filename); + if ((long)lock_file_timeout_ms < 0 && + git_config_get_ulong("gc.launchctlplistlocktimeoutms", + &lock_file_timeout_ms)) + lock_file_timeout_ms = 150; + + fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR, + lock_file_timeout_ms); + + /* + * Does this file already exist? With the intended contents? Is it + * registered already? Then it does not need to be re-registered. + */ + if (!stat(filename, &st) && st.st_size == plist.len && + strbuf_read_file(&plist2, filename, plist.len) == plist.len && + !strbuf_cmp(&plist, &plist2) && + launchctl_list_contains_plist(name, cmd)) + rollback_lock_file(&lk); + else { + if (write_in_full(fd, plist.buf, plist.len) < 0 || + commit_lock_file(&lk)) + die_errno(_("could not write '%s'"), filename); + + /* bootout might fail if not already running, so ignore */ + launchctl_boot_plist(0, filename); + if (launchctl_boot_plist(1, filename)) + die(_("failed to bootstrap service %s"), filename); + } free(filename); free(name); + strbuf_release(&plist); + strbuf_release(&plist2); return 0; } -static int launchctl_add_plists(const char *cmd) +static int launchctl_add_plists(void) { const char *exec_path = git_exec_path(); - return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) || - launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) || - launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd); + return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) || + launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) || + launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY); } -static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd) +static int launchctl_update_schedule(int run_maintenance, int fd) { if (run_maintenance) - return launchctl_add_plists(cmd); + return launchctl_add_plists(); else - return launchctl_remove_plists(cmd); + return launchctl_remove_plists(); +} + +static int is_schtasks_available(void) +{ + const char *cmd = "schtasks"; + int is_available; + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + +#ifdef GIT_WINDOWS_NATIVE + return 1; +#else + return 0; +#endif } static char *schtasks_task_name(const char *frequency) @@ -1702,13 +1840,15 @@ static char *schtasks_task_name(const char *frequency) return strbuf_detach(&label, NULL); } -static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd) +static int schtasks_remove_task(enum schedule_priority schedule) { + const char *cmd = "schtasks"; int result; struct strvec args = STRVEC_INIT; const char *frequency = get_frequency(schedule); char *name = schtasks_task_name(frequency); + get_schedule_cmd(&cmd, NULL); strvec_split(&args, cmd); strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL); @@ -1719,15 +1859,16 @@ static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd return result; } -static int schtasks_remove_tasks(const char *cmd) +static int schtasks_remove_tasks(void) { - return schtasks_remove_task(SCHEDULE_HOURLY, cmd) || - schtasks_remove_task(SCHEDULE_DAILY, cmd) || - schtasks_remove_task(SCHEDULE_WEEKLY, cmd); + return schtasks_remove_task(SCHEDULE_HOURLY) || + schtasks_remove_task(SCHEDULE_DAILY) || + schtasks_remove_task(SCHEDULE_WEEKLY); } -static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd) +static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule) { + const char *cmd = "schtasks"; int result; struct child_process child = CHILD_PROCESS_INIT; const char *xml; @@ -1736,6 +1877,8 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority char *name = schtasks_task_name(frequency); struct strbuf tfilename = STRBUF_INIT; + get_schedule_cmd(&cmd, NULL); + strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX", get_git_common_dir(), frequency); tfile = xmks_tempfile(tfilename.buf); @@ -1840,28 +1983,52 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority return result; } -static int schtasks_schedule_tasks(const char *cmd) +static int schtasks_schedule_tasks(void) { const char *exec_path = git_exec_path(); - return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) || - schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) || - schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd); + return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) || + schtasks_schedule_task(exec_path, SCHEDULE_DAILY) || + schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY); } -static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd) +static int schtasks_update_schedule(int run_maintenance, int fd) { if (run_maintenance) - return schtasks_schedule_tasks(cmd); + return schtasks_schedule_tasks(); else - return schtasks_remove_tasks(cmd); + return schtasks_remove_tasks(); +} + +static int is_crontab_available(void) +{ + const char *cmd = "crontab"; + int is_available; + struct child_process child = CHILD_PROCESS_INIT; + + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + + strvec_split(&child.args, cmd); + strvec_push(&child.args, "-l"); + child.no_stdin = 1; + child.no_stdout = 1; + child.no_stderr = 1; + child.silent_exec_failure = 1; + + if (start_command(&child)) + return 0; + /* Ignore exit code, as an empty crontab will return error. */ + finish_command(&child); + return 1; } #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE" #define END_LINE "# END GIT MAINTENANCE SCHEDULE" -static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd) +static int crontab_update_schedule(int run_maintenance, int fd) { + const char *cmd = "crontab"; int result = 0; int in_old_region = 0; struct child_process crontab_list = CHILD_PROCESS_INIT; @@ -1869,6 +2036,7 @@ static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd) FILE *cron_list, *cron_in; struct strbuf line = STRBUF_INIT; + get_schedule_cmd(&cmd, NULL); strvec_split(&crontab_list.args, cmd); strvec_push(&crontab_list.args, "-l"); crontab_list.in = -1; @@ -1945,66 +2113,376 @@ done_editing: return result; } +static int real_is_systemd_timer_available(void) +{ + struct child_process child = CHILD_PROCESS_INIT; + + strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL); + child.no_stdin = 1; + child.no_stdout = 1; + child.no_stderr = 1; + child.silent_exec_failure = 1; + + if (start_command(&child)) + return 0; + if (finish_command(&child)) + return 0; + return 1; +} + +static int is_systemd_timer_available(void) +{ + const char *cmd = "systemctl"; + int is_available; + + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + + return real_is_systemd_timer_available(); +} + +static char *xdg_config_home_systemd(const char *filename) +{ + return xdg_config_home_for("systemd/user", filename); +} + +static int systemd_timer_enable_unit(int enable, + enum schedule_priority schedule) +{ + const char *cmd = "systemctl"; + struct child_process child = CHILD_PROCESS_INIT; + const char *frequency = get_frequency(schedule); + + /* + * Disabling the systemd unit while it is already disabled makes + * systemctl print an error. + * Let's ignore it since it means we already are in the expected state: + * the unit is disabled. + * + * On the other hand, enabling a systemd unit which is already enabled + * produces no error. + */ + if (!enable) + child.no_stderr = 1; + + get_schedule_cmd(&cmd, NULL); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "--user", enable ? "enable" : "disable", + "--now", NULL); + strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency); + + if (start_command(&child)) + return error(_("failed to start systemctl")); + if (finish_command(&child)) + /* + * Disabling an already disabled systemd unit makes + * systemctl fail. + * Let's ignore this failure. + * + * Enabling an enabled systemd unit doesn't fail. + */ + if (enable) + return error(_("failed to run systemctl")); + return 0; +} + +static int systemd_timer_delete_unit_templates(void) +{ + int ret = 0; + char *filename = xdg_config_home_systemd("git-maintenance@.timer"); + if (unlink(filename) && !is_missing_file_error(errno)) + ret = error_errno(_("failed to delete '%s'"), filename); + FREE_AND_NULL(filename); + + filename = xdg_config_home_systemd("git-maintenance@.service"); + if (unlink(filename) && !is_missing_file_error(errno)) + ret = error_errno(_("failed to delete '%s'"), filename); + + free(filename); + return ret; +} + +static int systemd_timer_delete_units(void) +{ + return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) || + systemd_timer_enable_unit(0, SCHEDULE_DAILY) || + systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) || + systemd_timer_delete_unit_templates(); +} + +static int systemd_timer_write_unit_templates(const char *exec_path) +{ + char *filename; + FILE *file; + const char *unit; + + filename = xdg_config_home_systemd("git-maintenance@.timer"); + if (safe_create_leading_directories(filename)) { + error(_("failed to create directories for '%s'"), filename); + goto error; + } + file = fopen_or_warn(filename, "w"); + if (file == NULL) + goto error; + + unit = "# This file was created and is maintained by Git.\n" + "# Any edits made in this file might be replaced in the future\n" + "# by a Git command.\n" + "\n" + "[Unit]\n" + "Description=Optimize Git repositories data\n" + "\n" + "[Timer]\n" + "OnCalendar=%i\n" + "Persistent=true\n" + "\n" + "[Install]\n" + "WantedBy=timers.target\n"; + if (fputs(unit, file) == EOF) { + error(_("failed to write to '%s'"), filename); + fclose(file); + goto error; + } + if (fclose(file) == EOF) { + error_errno(_("failed to flush '%s'"), filename); + goto error; + } + free(filename); + + filename = xdg_config_home_systemd("git-maintenance@.service"); + file = fopen_or_warn(filename, "w"); + if (file == NULL) + goto error; + + unit = "# This file was created and is maintained by Git.\n" + "# Any edits made in this file might be replaced in the future\n" + "# by a Git command.\n" + "\n" + "[Unit]\n" + "Description=Optimize Git repositories data\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n" + "LockPersonality=yes\n" + "MemoryDenyWriteExecute=yes\n" + "NoNewPrivileges=yes\n" + "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n" + "RestrictNamespaces=yes\n" + "RestrictRealtime=yes\n" + "RestrictSUIDSGID=yes\n" + "SystemCallArchitectures=native\n" + "SystemCallFilter=@system-service\n"; + if (fprintf(file, unit, exec_path, exec_path) < 0) { + error(_("failed to write to '%s'"), filename); + fclose(file); + goto error; + } + if (fclose(file) == EOF) { + error_errno(_("failed to flush '%s'"), filename); + goto error; + } + free(filename); + return 0; + +error: + free(filename); + systemd_timer_delete_unit_templates(); + return -1; +} + +static int systemd_timer_setup_units(void) +{ + const char *exec_path = git_exec_path(); + + int ret = systemd_timer_write_unit_templates(exec_path) || + systemd_timer_enable_unit(1, SCHEDULE_HOURLY) || + systemd_timer_enable_unit(1, SCHEDULE_DAILY) || + systemd_timer_enable_unit(1, SCHEDULE_WEEKLY); + if (ret) + systemd_timer_delete_units(); + return ret; +} + +static int systemd_timer_update_schedule(int run_maintenance, int fd) +{ + if (run_maintenance) + return systemd_timer_setup_units(); + else + return systemd_timer_delete_units(); +} + +enum scheduler { + SCHEDULER_INVALID = -1, + SCHEDULER_AUTO, + SCHEDULER_CRON, + SCHEDULER_SYSTEMD, + SCHEDULER_LAUNCHCTL, + SCHEDULER_SCHTASKS, +}; + +static const struct { + const char *name; + int (*is_available)(void); + int (*update_schedule)(int run_maintenance, int fd); +} scheduler_fn[] = { + [SCHEDULER_CRON] = { + .name = "crontab", + .is_available = is_crontab_available, + .update_schedule = crontab_update_schedule, + }, + [SCHEDULER_SYSTEMD] = { + .name = "systemctl", + .is_available = is_systemd_timer_available, + .update_schedule = systemd_timer_update_schedule, + }, + [SCHEDULER_LAUNCHCTL] = { + .name = "launchctl", + .is_available = is_launchctl_available, + .update_schedule = launchctl_update_schedule, + }, + [SCHEDULER_SCHTASKS] = { + .name = "schtasks", + .is_available = is_schtasks_available, + .update_schedule = schtasks_update_schedule, + }, +}; + +static enum scheduler parse_scheduler(const char *value) +{ + if (!value) + return SCHEDULER_INVALID; + else if (!strcasecmp(value, "auto")) + return SCHEDULER_AUTO; + else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab")) + return SCHEDULER_CRON; + else if (!strcasecmp(value, "systemd") || + !strcasecmp(value, "systemd-timer")) + return SCHEDULER_SYSTEMD; + else if (!strcasecmp(value, "launchctl")) + return SCHEDULER_LAUNCHCTL; + else if (!strcasecmp(value, "schtasks")) + return SCHEDULER_SCHTASKS; + else + return SCHEDULER_INVALID; +} + +static int maintenance_opt_scheduler(const struct option *opt, const char *arg, + int unset) +{ + enum scheduler *scheduler = opt->value; + + BUG_ON_OPT_NEG(unset); + + *scheduler = parse_scheduler(arg); + if (*scheduler == SCHEDULER_INVALID) + return error(_("unrecognized --scheduler argument '%s'"), arg); + return 0; +} + +struct maintenance_start_opts { + enum scheduler scheduler; +}; + +static enum scheduler resolve_scheduler(enum scheduler scheduler) +{ + if (scheduler != SCHEDULER_AUTO) + return scheduler; + #if defined(__APPLE__) -static const char platform_scheduler[] = "launchctl"; + return SCHEDULER_LAUNCHCTL; + #elif defined(GIT_WINDOWS_NATIVE) -static const char platform_scheduler[] = "schtasks"; + return SCHEDULER_SCHTASKS; + +#elif defined(__linux__) + if (is_systemd_timer_available()) + return SCHEDULER_SYSTEMD; + else if (is_crontab_available()) + return SCHEDULER_CRON; + else + die(_("neither systemd timers nor crontab are available")); + #else -static const char platform_scheduler[] = "crontab"; + return SCHEDULER_CRON; #endif +} -static int update_background_schedule(int enable) +static void validate_scheduler(enum scheduler scheduler) { - int result; - const char *scheduler = platform_scheduler; - const char *cmd = scheduler; - char *testing; + if (scheduler == SCHEDULER_INVALID) + BUG("invalid scheduler"); + if (scheduler == SCHEDULER_AUTO) + BUG("resolve_scheduler should have been called before"); + + if (!scheduler_fn[scheduler].is_available()) + die(_("%s scheduler is not available"), + scheduler_fn[scheduler].name); +} + +static int update_background_schedule(const struct maintenance_start_opts *opts, + int enable) +{ + unsigned int i; + int result = 0; struct lock_file lk; char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); - testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER")); - if (testing) { - char *sep = strchr(testing, ':'); - if (!sep) - die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing); - *sep = '\0'; - scheduler = testing; - cmd = sep + 1; + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + free(lock_path); + return error(_("another process is scheduling background maintenance")); } - if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { - result = error(_("another process is scheduling background maintenance")); - goto cleanup; + for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) { + if (enable && opts->scheduler == i) + continue; + if (!scheduler_fn[i].is_available()) + continue; + scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk)); } - if (!strcmp(scheduler, "launchctl")) - result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd); - else if (!strcmp(scheduler, "schtasks")) - result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd); - else if (!strcmp(scheduler, "crontab")) - result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd); - else - die("unknown background scheduler: %s", scheduler); + if (enable) + result = scheduler_fn[opts->scheduler].update_schedule( + 1, get_lock_file_fd(&lk)); rollback_lock_file(&lk); -cleanup: free(lock_path); - free(testing); return result; } -static int maintenance_start(void) +static const char *const builtin_maintenance_start_usage[] = { + N_("git maintenance start [--scheduler=<scheduler>]"), + NULL +}; + +static int maintenance_start(int argc, const char **argv, const char *prefix) { + struct maintenance_start_opts opts = { 0 }; + struct option options[] = { + OPT_CALLBACK_F( + 0, "scheduler", &opts.scheduler, N_("scheduler"), + N_("scheduler to trigger git maintenance run"), + PARSE_OPT_NONEG, maintenance_opt_scheduler), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_start_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_start_usage, options); + + opts.scheduler = resolve_scheduler(opts.scheduler); + validate_scheduler(opts.scheduler); + if (maintenance_register()) warning(_("failed to add repo to global config")); - - return update_background_schedule(1); + return update_background_schedule(&opts, 1); } static int maintenance_stop(void) { - return update_background_schedule(0); + return update_background_schedule(NULL, 0); } static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]"); @@ -2018,7 +2496,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "run")) return maintenance_run(argc - 1, argv + 1, prefix); if (!strcmp(argv[1], "start")) - return maintenance_start(); + return maintenance_start(argc - 1, argv + 1, prefix); if (!strcmp(argv[1], "stop")) return maintenance_stop(); if (!strcmp(argv[1], "register")) diff --git a/builtin/grep.c b/builtin/grep.c index 7d2f8e5adb..8af5249a7b 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -65,6 +65,9 @@ static int todo_done; /* Has all work items been added? */ static int all_work_added; +static struct repository **repos_to_free; +static size_t repos_to_free_nr, repos_to_free_alloc; + /* This lock protects all the variables above. */ static pthread_mutex_t grep_mutex; @@ -168,6 +171,19 @@ static void work_done(struct work_item *w) grep_unlock(); } +static void free_repos(void) +{ + int i; + + for (i = 0; i < repos_to_free_nr; i++) { + repo_clear(repos_to_free[i]); + free(repos_to_free[i]); + } + FREE_AND_NULL(repos_to_free); + repos_to_free_nr = 0; + repos_to_free_alloc = 0; +} + static void *run(void *arg) { int hit = 0; @@ -333,7 +349,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, struct grep_source gs; grep_source_name(opt, filename, tree_name_len, &pathbuf); - grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid); + grep_source_init_oid(&gs, pathbuf.buf, path, oid, opt->repo); strbuf_release(&pathbuf); if (num_threads > 1) { @@ -359,7 +375,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) struct grep_source gs; grep_source_name(opt, filename, 0, &buf); - grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); + grep_source_init_file(&gs, buf.buf, filename); strbuf_release(&buf); if (num_threads > 1) { @@ -415,19 +431,21 @@ static int grep_submodule(struct grep_opt *opt, const struct object_id *oid, const char *filename, const char *path, int cached) { - struct repository subrepo; + struct repository *subrepo; struct repository *superproject = opt->repo; - const struct submodule *sub; struct grep_opt subopt; - int hit; - - sub = submodule_from_path(superproject, null_oid(), path); + int hit = 0; if (!is_submodule_active(superproject, path)) return 0; - if (repo_submodule_init(&subrepo, superproject, sub)) + subrepo = xmalloc(sizeof(*subrepo)); + if (repo_submodule_init(subrepo, superproject, path, null_oid())) { + free(subrepo); return 0; + } + ALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc); + repos_to_free[repos_to_free_nr++] = subrepo; /* * NEEDSWORK: repo_read_gitmodules() might call @@ -438,53 +456,49 @@ static int grep_submodule(struct grep_opt *opt, * subrepo's odbs to the in-memory alternates list. */ obj_read_lock(); - repo_read_gitmodules(&subrepo, 0); + repo_read_gitmodules(subrepo, 0); /* - * NEEDSWORK: This adds the submodule's object directory to the list of - * alternates for the single in-memory object store. This has some bad - * consequences for memory (processed objects will never be freed) and - * performance (this increases the number of pack files git has to pay - * attention to, to the sum of the number of pack files in all the - * repositories processed so far). This can be removed once the object - * store is no longer global and instead is a member of the repository - * object. + * All code paths tested by test code no longer need submodule ODBs to + * be added as alternates, but add it to the list just in case. + * Submodule ODBs added through add_submodule_odb_by_path() will be + * lazily registered as alternates when needed (and except in an + * unexpected code interaction, it won't be needed). */ - add_to_alternates_memory(subrepo.objects->odb->path); + add_submodule_odb_by_path(subrepo->objects->odb->path); obj_read_unlock(); memcpy(&subopt, opt, sizeof(subopt)); - subopt.repo = &subrepo; + subopt.repo = subrepo; if (oid) { - struct object *object; + enum object_type object_type; struct tree_desc tree; void *data; unsigned long size; struct strbuf base = STRBUF_INIT; obj_read_lock(); - object = parse_object_or_die(oid, NULL); + object_type = oid_object_info(subrepo, oid, NULL); obj_read_unlock(); - data = read_object_with_reference(&subrepo, - &object->oid, tree_type, + data = read_object_with_reference(subrepo, + oid, tree_type, &size, NULL); if (!data) - die(_("unable to read tree (%s)"), oid_to_hex(&object->oid)); + die(_("unable to read tree (%s)"), oid_to_hex(oid)); strbuf_addstr(&base, filename); strbuf_addch(&base, '/'); init_tree_desc(&tree, data, size); hit = grep_tree(&subopt, pathspec, &tree, &base, base.len, - object->type == OBJ_COMMIT); + object_type == OBJ_COMMIT); strbuf_release(&base); free(data); } else { hit = grep_cache(&subopt, pathspec, cached); } - repo_clear(&subrepo); return hit; } @@ -1182,5 +1196,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) run_pager(&opt, prefix); clear_pathspec(&pathspec); free_grep_patterns(&opt); + free_repos(); return !hit; } diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 640ef4ded5..c7b3ad74c6 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -53,9 +53,7 @@ static void hash_object(const char *path, const char *type, const char *vpath, unsigned flags, int literally) { int fd; - fd = open(path, O_RDONLY); - if (fd < 0) - die_errno("Cannot open '%s'", path); + fd = xopen(path, O_RDONLY); hash_fd(fd, type, vpath, flags, literally); } @@ -117,7 +115,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) prefix = setup_git_directory_gently(&nongit); if (vpath && prefix) - vpath = xstrdup(prefix_filename(prefix, vpath)); + vpath = prefix_filename(prefix, vpath); git_config(git_default_config, NULL); diff --git a/builtin/help.c b/builtin/help.c index b7eec06c3d..75cd2fb407 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -7,7 +7,6 @@ #include "exec-cmd.h" #include "parse-options.h" #include "run-command.h" -#include "column.h" #include "config-list.h" #include "help.h" #include "alias.h" @@ -34,32 +33,52 @@ enum help_format { HELP_FORMAT_WEB }; -static const char *html_path; +enum show_config_type { + SHOW_CONFIG_HUMAN, + SHOW_CONFIG_VARS, + SHOW_CONFIG_SECTIONS, +}; + +static enum help_action { + HELP_ACTION_ALL = 1, + HELP_ACTION_GUIDES, + HELP_ACTION_CONFIG, + HELP_ACTION_CONFIG_FOR_COMPLETION, + HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, +} cmd_mode; -static int show_all = 0; -static int show_guides = 0; -static int show_config; +static const char *html_path; static int verbose = 1; -static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static int exclude_guides; static struct option builtin_help_options[] = { - OPT_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"), + HELP_ACTION_ALL), OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), - OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), - OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")), - OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), OPT_SET_INT('i', "info", &help_format, N_("show info page"), HELP_FORMAT_INFO), OPT__VERBOSE(&verbose, N_("print command description")), + + OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"), + HELP_ACTION_GUIDES), + OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"), + HELP_ACTION_CONFIG), + OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "", + HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "", + HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_END(), }; static const char * const builtin_help_usage[] = { - N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"), + N_("git help [-a|--all] [--[no-]verbose]]\n" + " [[-i|--info] [-m|--man] [-w|--web]] [<command>]"), + N_("git help [-g|--guides]"), + N_("git help [-c|--config]"), NULL }; @@ -70,7 +89,7 @@ struct slot_expansion { int found; }; -static void list_config_help(int for_human) +static void list_config_help(enum show_config_type type) { struct slot_expansion slot_expansions[] = { { "advice", "*", list_config_advices }, @@ -88,6 +107,8 @@ static void list_config_help(int for_human) const char **p; struct slot_expansion *e; struct string_list keys = STRING_LIST_INIT_DUP; + struct string_list keys_uniq = STRING_LIST_INIT_DUP; + struct string_list_item *item; int i; for (p = config_name_list; *p; p++) { @@ -118,34 +139,46 @@ static void list_config_help(int for_human) for (i = 0; i < keys.nr; i++) { const char *var = keys.items[i].string; const char *wildcard, *tag, *cut; + const char *dot = NULL; + struct strbuf sb = STRBUF_INIT; - if (for_human) { + switch (type) { + case SHOW_CONFIG_HUMAN: puts(var); continue; + case SHOW_CONFIG_SECTIONS: + dot = strchr(var, '.'); + break; + case SHOW_CONFIG_VARS: + break; } - wildcard = strchr(var, '*'); tag = strchr(var, '<'); - if (!wildcard && !tag) { - puts(var); + if (!dot && !wildcard && !tag) { + string_list_append(&keys_uniq, var); continue; } - if (wildcard && !tag) + if (dot) + cut = dot; + else if (wildcard && !tag) cut = wildcard; else if (!wildcard && tag) cut = tag; else cut = wildcard < tag ? wildcard : tag; - /* - * We may produce duplicates, but that's up to - * git-completion.bash to handle - */ - printf("%.*s\n", (int)(cut - var), var); + strbuf_add(&sb, var, cut - var); + string_list_append(&keys_uniq, sb.buf); + strbuf_release(&sb); + } string_list_clear(&keys, 0); + string_list_remove_duplicates(&keys_uniq, 0); + for_each_string_list_item(item, &keys_uniq) + puts(item->string); + string_list_clear(&keys_uniq, 0); } static enum help_format parse_help_format(const char *format) @@ -349,8 +382,6 @@ static int add_man_viewer_info(const char *var, const char *value) static int git_help_config(const char *var, const char *value, void *cb) { - if (starts_with(var, "column.")) - return git_column_config(var, value, "help", &colopts); if (!strcmp(var, "help.format")) { if (!value) return config_error_nonbool(var); @@ -467,11 +498,14 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) if (!html_path) html_path = to_free = system_path(GIT_HTML_PATH); - /* Check that we have a git documentation directory. */ + /* + * Check that the page we're looking for exists. + */ if (!strstr(html_path, "://")) { - if (stat(mkpath("%s/git.html", html_path), &st) + if (stat(mkpath("%s/%s.html", html_path, page), &st) || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); + die("'%s/%s.html': documentation file not found.", + html_path, page); } strbuf_init(page_path, 0); @@ -541,6 +575,13 @@ static const char *check_git_cmd(const char* cmd) return cmd; } +static void no_extra_argc(int argc) +{ + if (argc) + usage_msg_opt(_("this option doesn't take any other arguments"), + builtin_help_usage, builtin_help_options); +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; @@ -551,8 +592,8 @@ int cmd_help(int argc, const char **argv, const char *prefix) builtin_help_usage, 0); parsed_help_format = help_format; - if (show_all) { - git_config(git_help_config, NULL); + switch (cmd_mode) { + case HELP_ACTION_ALL: if (verbose) { setup_pager(); list_all_cmds_help(); @@ -560,30 +601,27 @@ int cmd_help(int argc, const char **argv, const char *prefix) } printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); load_command_list("git-", &main_cmds, &other_cmds); - list_commands(colopts, &main_cmds, &other_cmds); - } - - if (show_config) { - int for_human = show_config == 1; - - if (!for_human) { - list_config_help(for_human); - return 0; - } - setup_pager(); - list_config_help(for_human); - printf("\n%s\n", _("'git help config' for more information")); - return 0; - } - - if (show_guides) + list_commands(&main_cmds, &other_cmds); + printf("%s\n", _(git_more_info_string)); + break; + case HELP_ACTION_GUIDES: + no_extra_argc(argc); list_guides_help(); - - if (show_all || show_guides) { printf("%s\n", _(git_more_info_string)); - /* - * We're done. Ignore any remaining args - */ + return 0; + case HELP_ACTION_CONFIG_FOR_COMPLETION: + no_extra_argc(argc); + list_config_help(SHOW_CONFIG_VARS); + return 0; + case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: + no_extra_argc(argc); + list_config_help(SHOW_CONFIG_SECTIONS); + return 0; + case HELP_ACTION_CONFIG: + no_extra_argc(argc); + setup_pager(); + list_config_help(SHOW_CONFIG_HUMAN); + printf("\n%s\n", _("'git help config' for more information")); return 0; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 3fbc5d7077..15ae406e6b 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -122,6 +122,7 @@ static int strict; static int do_fsck_object; static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES; static int verbose; +static const char *progress_title; static int show_resolving_progress; static int show_stat; static int check_self_contained_and_connected; @@ -187,9 +188,7 @@ static void init_thread(void) pthread_key_create(&key, NULL); CALLOC_ARRAY(thread_data, nr_threads); for (i = 0; i < nr_threads; i++) { - thread_data[i].pack_fd = open(curr_pack, O_RDONLY); - if (thread_data[i].pack_fd == -1) - die_errno(_("unable to open %s"), curr_pack); + thread_data[i].pack_fd = xopen(curr_pack, O_RDONLY); } threads_active = 1; @@ -338,15 +337,11 @@ static const char *open_pack_file(const char *pack_name) "pack/tmp_pack_XXXXXX"); pack_name = strbuf_detach(&tmp_file, NULL); } else { - output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); - if (output_fd < 0) - die_errno(_("unable to create '%s'"), pack_name); + output_fd = xopen(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); } nothread_data.pack_fd = output_fd; } else { - input_fd = open(pack_name, O_RDONLY); - if (input_fd < 0) - die_errno(_("cannot open packfile '%s'"), pack_name); + input_fd = xopen(pack_name, O_RDONLY); output_fd = -1; nothread_data.pack_fd = input_fd; } @@ -369,9 +364,7 @@ static void parse_pack_header(void) use(sizeof(struct pack_header)); } -static NORETURN void bad_object(off_t offset, const char *format, - ...) __attribute__((format (printf, 2, 3))); - +__attribute__((format (printf, 2, 3))) static NORETURN void bad_object(off_t offset, const char *format, ...) { va_list params; @@ -1159,6 +1152,7 @@ static void parse_pack_objects(unsigned char *hash) if (verbose) progress = start_progress( + progress_title ? progress_title : from_stdin ? _("Receiving objects") : _("Indexing objects"), nr_objects); for (i = 0; i < nr_objects; i++) { @@ -1421,7 +1415,7 @@ static void fix_unresolved_deltas(struct hashfile *f) if (check_object_signature(the_repository, &d->oid, data, size, - type_name(type))) + type_name(type), NULL)) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); /* @@ -1483,6 +1477,22 @@ static void write_special_file(const char *suffix, const char *msg, strbuf_release(&name_buf); } +static void rename_tmp_packfile(const char **final_name, + const char *curr_name, + struct strbuf *name, unsigned char *hash, + const char *ext, int make_read_only_if_same) +{ + if (*final_name != curr_name) { + if (!*final_name) + *final_name = odb_pack_name(name, hash, ext); + if (finalize_object_file(curr_name, *final_name)) + die(_("unable to rename temporary '*.%s' file to '%s"), + ext, *final_name); + } else if (make_read_only_if_same) { + chmod(*final_name, 0444); + } +} + static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, const char *final_rev_index_name, const char *curr_rev_index_name, @@ -1511,31 +1521,13 @@ static void final(const char *final_pack_name, const char *curr_pack_name, write_special_file("promisor", promisor_msg, final_pack_name, hash, NULL); - if (final_pack_name != curr_pack_name) { - if (!final_pack_name) - final_pack_name = odb_pack_name(&pack_name, hash, "pack"); - if (finalize_object_file(curr_pack_name, final_pack_name)) - die(_("cannot store pack file")); - } else if (from_stdin) - chmod(final_pack_name, 0444); - - if (final_index_name != curr_index_name) { - if (!final_index_name) - final_index_name = odb_pack_name(&index_name, hash, "idx"); - if (finalize_object_file(curr_index_name, final_index_name)) - die(_("cannot store index file")); - } else - chmod(final_index_name, 0444); - - if (curr_rev_index_name) { - if (final_rev_index_name != curr_rev_index_name) { - if (!final_rev_index_name) - final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev"); - if (finalize_object_file(curr_rev_index_name, final_rev_index_name)) - die(_("cannot store reverse index file")); - } else - chmod(final_rev_index_name, 0444); - } + rename_tmp_packfile(&final_pack_name, curr_pack_name, &pack_name, + hash, "pack", from_stdin); + if (curr_rev_index_name) + rename_tmp_packfile(&final_rev_index_name, curr_rev_index_name, + &rev_index_name, hash, "rev", 1); + rename_tmp_packfile(&final_index_name, curr_index_name, &index_name, + hash, "idx", 1); if (do_fsck_object) { struct packed_git *p; @@ -1808,6 +1800,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) input_len = sizeof(*hdr); } else if (!strcmp(arg, "-v")) { verbose = 1; + } else if (!strcmp(arg, "--progress-title")) { + if (progress_title || (i+1) >= argc) + usage(index_pack_usage); + progress_title = argv[++i]; } else if (!strcmp(arg, "--show-resolving-progress")) { show_resolving_progress = 1; } else if (!strcmp(arg, "--report-end-of-input")) { diff --git a/builtin/log.c b/builtin/log.c index 516a1142dd..f75d87e8d7 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -245,6 +245,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->abbrev_commit = 0; } + if (rev->commit_format == CMIT_FMT_USERFORMAT && !w.decorate) + decoration_style = 0; + if (decoration_style) { const struct string_list *config_exclude = repo_config_get_value_multi(the_repository, @@ -634,7 +637,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &rev, prefix); rev.diff = 1; rev.always_show_header = 1; - rev.no_walk = REVISION_WALK_NO_WALK_SORTED; + rev.no_walk = 1; rev.diffopt.stat_width = -1; /* Scale to real terminal size */ memset(&opt, 0, sizeof(opt)); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 29a26ad8ae..a2000ed6bf 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -209,10 +209,8 @@ static void show_submodule(struct repository *superproject, struct dir_struct *dir, const char *path) { struct repository subrepo; - const struct submodule *sub = submodule_from_path(superproject, - null_oid(), path); - if (repo_submodule_init(&subrepo, superproject, sub)) + if (repo_submodule_init(&subrepo, superproject, path, null_oid())) return; if (repo_read_index(&subrepo) < 0) @@ -614,7 +612,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) struct option builtin_ls_files_options[] = { /* Think twice before adding "--nul" synonym to this */ OPT_SET_INT('z', NULL, &line_terminator, - N_("paths are separated with NUL character"), '\0'), + N_("separate paths with the NUL character"), '\0'), OPT_BOOL('t', NULL, &show_tag, N_("identify the file status with tags")), OPT_BOOL('v', NULL, &show_valid_bit, @@ -651,7 +649,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("skip files matching pattern"), PARSE_OPT_NONEG, option_parse_exclude), OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"), - N_("exclude patterns are read from <file>"), + N_("read exclude patterns from <file>"), PARSE_OPT_NONEG, option_parse_exclude_from), OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"), N_("read additional per-directory exclude patterns in <file>")), diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 1794548c71..318949c3d7 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -7,8 +7,8 @@ static const char * const ls_remote_usage[] = { N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n" - " [-q | --quiet] [--exit-code] [--get-url]\n" - " [--symref] [<repository> [<refs>...]]"), + " [-q | --quiet] [--exit-code] [--get-url]\n" + " [--symref] [<repository> [<refs>...]]"), NULL }; @@ -84,6 +84,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION); dest = argv[0]; + packet_trace_identity("ls-remote"); + UNLEAK(sorting); if (argc > 1) { diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 664400b816..7baef30569 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -75,9 +75,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) fprintf(stderr, "corrupt mailbox\n"); exit(1); } - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) - die_errno("cannot open output file '%s'", name); + fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666); output = xfdopen(fd, "w"); /* Copy it out, while searching for a line that begins with diff --git a/builtin/merge.c b/builtin/merge.c index a8a843b1f5..cc4a910c69 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -13,6 +13,7 @@ #include "builtin.h" #include "lockfile.h" #include "run-command.h" +#include "hook.h" #include "diff.h" #include "diff-merges.h" #include "refs.h" @@ -88,9 +89,9 @@ static int autostash; static int no_verify; static struct strategy all_strategy[] = { - { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, + { "recursive", NO_TRIVIAL }, { "octopus", DEFAULT_OCTOPUS }, - { "ort", NO_TRIVIAL }, + { "ort", DEFAULT_TWOHEAD | NO_TRIVIAL }, { "resolve", 0 }, { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, @@ -469,7 +470,6 @@ static void finish(struct commit *head_commit, * We ignore errors in 'gc --auto', since the * user should see them. */ - close_object_store(the_repository->objects); run_auto_maintenance(verbosity < 0); } } @@ -503,7 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg) struct strbuf bname = STRBUF_INIT; struct merge_remote_desc *desc; const char *ptr; - char *found_ref; + char *found_ref = NULL; int len, early; strbuf_branchname(&bname, remote, 0); @@ -586,6 +586,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addf(msg, "%s\t\tcommit '%s'\n", oid_to_hex(&remote_head->object.oid), remote); cleanup: + free(found_ref); strbuf_release(&buf); strbuf_release(&bname); } @@ -680,6 +681,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, opts.verbose_update = 1; opts.trivial_merges_only = 1; opts.merge = 1; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ trees[nr_trees] = parse_tree_indirect(common); if (!trees[nr_trees++]) return -1; @@ -738,7 +740,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, for (x = 0; x < xopts_nr; x++) if (parse_merge_opt(&o, xopts[x])) - die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); + die(_("unknown strategy option: -X%s"), xopts[x]); o.branch1 = head_arg; o.branch2 = merge_remote_util(remoteheads->item)->name; @@ -848,7 +850,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) * and write it out as a tree. We must do this before we invoke * the editor and after we invoke run_status above. */ - if (find_hook("pre-merge-commit")) + if (hook_exists("pre-merge-commit")) discard_cache(); read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); @@ -861,9 +863,11 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_commented_addf(&msg, "\n"); } strbuf_commented_addf(&msg, _(merge_editor_comment)); - strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ? - scissors_editor_comment : - no_scissors_editor_comment), comment_line_char); + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) + strbuf_commented_addf(&msg, _(scissors_editor_comment)); + else + strbuf_commented_addf(&msg, + _(no_scissors_editor_comment), comment_line_char); } if (signoff) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); @@ -1135,9 +1139,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge merge_names = &fetch_head_file; filename = git_path_fetch_head(the_repository); - fd = open(filename, O_RDONLY); - if (fd < 0) - die_errno(_("could not open '%s' for reading"), filename); + fd = xopen(filename, O_RDONLY); if (strbuf_read(merge_names, fd, 0) < 0) die_errno(_("could not read '%s'"), filename); @@ -1275,6 +1277,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. @@ -1367,14 +1372,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * There is no unmerged entry, don't advise 'git * add/rm <file>', just 'git commit'. */ - if (advice_resolve_conflict) + if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) die(_("You have not concluded your merge (MERGE_HEAD exists).\n" "Please, commit your changes before you merge.")); else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } if (ref_exists("CHERRY_PICK_HEAD")) { - if (advice_resolve_conflict) + if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); else @@ -1484,6 +1489,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) fast_forward = FF_NO; } + if (!use_strategies && !pull_twohead && + remoteheads && !remoteheads->next) { + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + if (default_strategy) + append_strategy(get_strategy(default_strategy)); + } if (!use_strategies) { if (!remoteheads) ; /* already up-to-date */ @@ -1560,6 +1571,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) &head_commit->object.oid, &commit->object.oid, overwrite_ignore)) { + apply_autostash(git_path_merge_autostash(the_repository)); ret = 1; goto done; } @@ -1620,7 +1632,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (fast_forward == FF_ONLY) - die(_("Not possible to fast-forward, aborting.")); + die_ff_impossible(); if (autostash) create_autostash(the_repository, @@ -1708,6 +1720,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); + apply_autostash(git_path_merge_autostash(the_repository)); ret = 2; goto done; } else if (best_strategy == wt_strategy) @@ -1715,7 +1728,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else { printf(_("Rewinding the tree to pristine...\n")); restore_state(&head_commit->object.oid, &stash); - printf(_("Using the %s to prepare resolving by hand.\n"), + printf(_("Using the %s strategy to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, remoteheads, head_commit); diff --git a/builtin/mktag.c b/builtin/mktag.c index dddcccdd36..3b2dbbb37e 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -62,7 +62,8 @@ static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) repl = lookup_replace_object(the_repository, tagged_oid); ret = check_object_signature(the_repository, repl, - buffer, size, type_name(*tagged_type)); + buffer, size, type_name(*tagged_type), + NULL); free(buffer); return ret; diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 5d3ea445fd..075d15d706 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -7,7 +7,8 @@ #include "object-store.h" #define BUILTIN_MIDX_WRITE_USAGE \ - N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]") + N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]" \ + "[--refs-snapshot=<path>]") #define BUILTIN_MIDX_VERIFY_USAGE \ N_("git multi-pack-index [<options>] verify") @@ -45,14 +46,15 @@ static char const * const builtin_multi_pack_index_usage[] = { static struct opts_multi_pack_index { const char *object_dir; const char *preferred_pack; + const char *refs_snapshot; unsigned long batch_size; unsigned flags; + int stdin_packs; } opts; static struct option common_opts[] = { OPT_FILENAME(0, "object-dir", &opts.object_dir, N_("object directory containing set of packfile and pack-index pairs")), - OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; @@ -61,6 +63,33 @@ static struct option *add_common_options(struct option *prev) return parse_options_concat(common_opts, prev); } +static int git_multi_pack_index_write_config(const char *var, const char *value, + void *cb) +{ + if (!strcmp(var, "pack.writebitmaphashcache")) { + if (git_config_bool(var, value)) + opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; + else + opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE; + } + + /* + * We should never make a fall-back call to 'git_default_config', since + * this was already called in 'cmd_multi_pack_index()'. + */ + return 0; +} + +static void read_packs_from_stdin(struct string_list *to) +{ + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) + string_list_append(to, buf.buf); + string_list_sort(to); + + strbuf_release(&buf); +} + static int cmd_multi_pack_index_write(int argc, const char **argv) { struct option *options; @@ -68,13 +97,27 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) OPT_STRING(0, "preferred-pack", &opts.preferred_pack, N_("preferred-pack"), N_("pack for reuse when computing a multi-pack bitmap")), + OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), + MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, + N_("write multi-pack index containing only given indexes")), + OPT_FILENAME(0, "refs-snapshot", &opts.refs_snapshot, + N_("refs snapshot for selecting bitmap commits")), OPT_END(), }; + opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; + + git_config(git_multi_pack_index_write_config, NULL); + options = add_common_options(builtin_multi_pack_index_write_options); trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_write_usage, PARSE_OPT_KEEP_UNKNOWN); @@ -84,16 +127,39 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) FREE_AND_NULL(options); + if (opts.stdin_packs) { + struct string_list packs = STRING_LIST_INIT_DUP; + int ret; + + read_packs_from_stdin(&packs); + + ret = write_midx_file_only(opts.object_dir, &packs, + opts.preferred_pack, + opts.refs_snapshot, opts.flags); + + string_list_clear(&packs, 0); + + return ret; + + } return write_midx_file(opts.object_dir, opts.preferred_pack, - opts.flags); + opts.refs_snapshot, opts.flags); } static int cmd_multi_pack_index_verify(int argc, const char **argv) { - struct option *options = common_opts; + struct option *options; + static struct option builtin_multi_pack_index_verify_options[] = { + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), + }; + options = add_common_options(builtin_multi_pack_index_verify_options); trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_verify_usage, PARSE_OPT_KEEP_UNKNOWN); @@ -106,10 +172,18 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) static int cmd_multi_pack_index_expire(int argc, const char **argv) { - struct option *options = common_opts; + struct option *options; + static struct option builtin_multi_pack_index_expire_options[] = { + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), + }; + options = add_common_options(builtin_multi_pack_index_expire_options); trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_expire_usage, PARSE_OPT_KEEP_UNKNOWN); @@ -126,6 +200,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) static struct option builtin_multi_pack_index_repack_options[] = { OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; @@ -133,6 +209,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_repack_usage, @@ -154,8 +232,6 @@ int cmd_multi_pack_index(int argc, const char **argv, git_config(git_default_config, NULL); - if (isatty(2)) - opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, builtin_multi_pack_index_usage, @@ -164,7 +240,7 @@ int cmd_multi_pack_index(int argc, const char **argv, if (!opts.object_dir) opts.object_dir = get_object_directory(); - if (argc == 0) + if (!argc) goto usage; if (!strcmp(argv[0], "repack")) @@ -175,10 +251,9 @@ int cmd_multi_pack_index(int argc, const char **argv, return cmd_multi_pack_index_verify(argc, argv); else if (!strcmp(argv[0], "expire")) return cmd_multi_pack_index_expire(argc, argv); - else { + + error(_("unrecognized subcommand: %s"), argv[0]); usage: - error(_("unrecognized subcommand: %s"), argv[0]); - usage_with_options(builtin_multi_pack_index_usage, - builtin_multi_pack_index_options); - } + usage_with_options(builtin_multi_pack_index_usage, + builtin_multi_pack_index_options); } diff --git a/builtin/mv.c b/builtin/mv.c index 3fccdcb645..83a465ba83 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -118,21 +118,23 @@ static int index_range_of_same_dir(const char *src, int length, int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; - int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; + int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT__DRY_RUN(&show_only, N_("dry run")), OPT__FORCE(&force, N_("force move/rename even if target exists"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), + OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; + enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; 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; git_config(git_default_config, NULL); @@ -176,14 +178,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) const char *src = source[i], *dst = destination[i]; int length, src_is_dir; const char *bad = NULL; + int skip_sparse = 0; if (show_only) printf(_("Checking rename of '%s' to '%s'\n"), src, dst); length = strlen(src); - if (lstat(src, &st) < 0) - bad = _("bad source"); - else if (!strncmp(src, dst, length) && + if (lstat(src, &st) < 0) { + /* only error if existence is expected. */ + if (modes[i] != SPARSE) + bad = _("bad source"); + } else 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)) @@ -212,11 +217,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dst_len = strlen(dst); for (j = 0; j < last - first; j++) { - const char *path = active_cache[first + j]->name; + 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] = INDEX; + modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; submodule_gitfile[argc + j] = NULL; } argc += last - first; @@ -244,14 +250,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix) bad = _("multiple sources for the same target"); else if (is_dir_sep(dst[strlen(dst) - 1])) bad = _("destination directory does not exist"); - else + 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; + } + + if (skip_sparse) + goto remove_entry; + string_list_insert(&src_for_dst, dst); + } if (!bad) continue; if (!ignore_errors) die(_("%s, source=%s, destination=%s"), bad, src, dst); +remove_entry: if (--argc > 0) { int n = argc - i; memmove(source + i, source + i + 1, @@ -266,6 +294,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } } + if (only_match_skip_worktree.nr) { + advise_on_updating_sparse_paths(&only_match_skip_worktree); + if (!ignore_errors) + return 1; + } + for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; @@ -274,7 +308,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && rename(src, dst) < 0) { + if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); @@ -303,5 +337,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + string_list_clear(&src_for_dst, 0); + UNLEAK(source); + UNLEAK(dest_path); + free(submodule_gitfile); + free(modes); return 0; } diff --git a/builtin/notes.c b/builtin/notes.c index 74bba39ca8..71c59583a1 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -172,9 +172,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data * /* write the template message before editing: */ d->edit_path = git_pathdup("NOTES_EDITMSG"); - fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); - if (fd < 0) - die_errno(_("could not create file '%s'"), d->edit_path); + fd = xopen(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (d->given) write_or_die(fd, d->buf.buf, d->buf.len); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index de00adbb9e..1a3dd445f8 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1124,6 +1124,11 @@ static void write_reused_pack(struct hashfile *f) break; offset += ewah_bit_ctz64(word >> offset); + /* + * Can use bit positions directly, even for MIDX + * bitmaps. See comment in try_partial_reuse() + * for why. + */ write_reused_pack_one(pos + offset, f, &w_curs); display_progress(progress_state, ++written); } @@ -1217,6 +1222,7 @@ static void write_pack_file(void) if (!pack_to_stdout) { struct stat st; struct strbuf tmpname = STRBUF_INIT; + char *idx_tmp_name = NULL; /* * Packs are runtime accessed in their mtime @@ -1237,7 +1243,8 @@ static void write_pack_file(void) warning_errno(_("failed utime() on %s"), pack_tmp_name); } - strbuf_addf(&tmpname, "%s-", base_name); + strbuf_addf(&tmpname, "%s-%s.", base_name, + hash_to_hex(hash)); if (write_bitmap_index) { bitmap_writer_set_checksum(hash); @@ -1245,23 +1252,29 @@ static void write_pack_file(void) &to_pack, written_list, nr_written); } - finish_tmp_packfile(&tmpname, pack_tmp_name, + stage_tmp_packfiles(&tmpname, pack_tmp_name, written_list, nr_written, - &pack_idx_opts, hash); + &pack_idx_opts, hash, &idx_tmp_name); if (write_bitmap_index) { - strbuf_addf(&tmpname, "%s.bitmap", hash_to_hex(hash)); + size_t tmpname_len = tmpname.len; + strbuf_addstr(&tmpname, "bitmap"); stop_progress(&progress_state); bitmap_writer_show_progress(progress); bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1); - bitmap_writer_build(&to_pack); + if (bitmap_writer_build(&to_pack) < 0) + die(_("failed to write bitmap index")); bitmap_writer_finish(written_list, nr_written, tmpname.buf, write_bitmap_options); write_bitmap_index = 0; + strbuf_setlen(&tmpname, tmpname_len); } + rename_tmp_packfile_idx(&tmpname, &idx_tmp_name); + + free(idx_tmp_name); strbuf_release(&tmpname); free(pack_tmp_name); puts(hash_to_hex(hash)); @@ -3311,9 +3324,26 @@ static void read_packs_list_from_stdin(void) } /* - * First handle all of the excluded packs, marking them as kept in-core - * so that later calls to add_object_entry() discards any objects that - * are also found in excluded packs. + * Arguments we got on stdin may not even be packs. First + * check that to avoid segfaulting later on in + * e.g. pack_mtime_cmp(), excluded packs are handled below. + * + * Since we first parsed our STDIN and then sorted the input + * lines the pack we error on will be whatever line happens to + * sort first. This is lazy, it's enough that we report one + * bad case here, we don't need to report the first/last one, + * or all of them. + */ + for_each_string_list_item(item, &include_packs) { + struct packed_git *p = item->util; + if (!p) + die(_("could not find pack '%s'"), item->string); + } + + /* + * Then, handle all of the excluded packs, marking them as + * kept in-core so that later calls to add_object_entry() + * discards any objects that are also found in excluded packs. */ for_each_string_list_item(item, &exclude_packs) { struct packed_git *p = item->util; @@ -3388,13 +3418,9 @@ static void read_object_list_from_stdin(void) } } -/* Remember to update object flag allocation in object.h */ -#define OBJECT_ADDED (1u<<20) - static void show_commit(struct commit *commit, void *data) { add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0); - commit->object.flags |= OBJECT_ADDED; if (write_bitmap_index) index_commit_for_bitmap(commit); @@ -3407,7 +3433,6 @@ static void show_object(struct object *obj, const char *name, void *data) { add_preferred_base_object(name); add_object_entry(&obj->oid, obj->type, name, 0); - obj->flags |= OBJECT_ADDED; if (use_delta_islands) { const char *p; @@ -3488,79 +3513,23 @@ static void show_edge(struct commit *commit) add_preferred_base(&commit->object.oid); } -struct in_pack_object { - off_t offset; - struct object *object; -}; - -struct in_pack { - unsigned int alloc; - unsigned int nr; - struct in_pack_object *array; -}; - -static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack) -{ - in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p); - in_pack->array[in_pack->nr].object = object; - in_pack->nr++; -} - -/* - * Compare the objects in the offset order, in order to emulate the - * "git rev-list --objects" output that produced the pack originally. - */ -static int ofscmp(const void *a_, const void *b_) +static int add_object_in_unpacked_pack(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *_data) { - struct in_pack_object *a = (struct in_pack_object *)a_; - struct in_pack_object *b = (struct in_pack_object *)b_; - - if (a->offset < b->offset) - return -1; - else if (a->offset > b->offset) - return 1; - else - return oidcmp(&a->object->oid, &b->object->oid); + add_object_entry(oid, OBJ_NONE, "", 0); + return 0; } static void add_objects_in_unpacked_packs(void) { - struct packed_git *p; - struct in_pack in_pack; - uint32_t i; - - memset(&in_pack, 0, sizeof(in_pack)); - - for (p = get_all_packs(the_repository); p; p = p->next) { - struct object_id oid; - struct object *o; - - if (!p->pack_local || p->pack_keep || p->pack_keep_in_core) - continue; - if (open_pack_index(p)) - die(_("cannot open pack index")); - - ALLOC_GROW(in_pack.array, - in_pack.nr + p->num_objects, - in_pack.alloc); - - for (i = 0; i < p->num_objects; i++) { - nth_packed_object_id(&oid, p, i); - o = lookup_unknown_object(the_repository, &oid); - if (!(o->flags & OBJECT_ADDED)) - mark_in_pack_object(o, p, &in_pack); - o->flags |= OBJECT_ADDED; - } - } - - if (in_pack.nr) { - QSORT(in_pack.array, in_pack.nr, ofscmp); - for (i = 0; i < in_pack.nr; i++) { - struct object *o = in_pack.array[i].object; - add_object_entry(&o->oid, o->type, "", 0); - } - } - free(in_pack.array); + if (for_each_packed_object(add_object_in_unpacked_pack, NULL, + FOR_EACH_OBJECT_PACK_ORDER | + FOR_EACH_OBJECT_LOCAL_ONLY | + FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS | + FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS)) + die(_("cannot open pack index")); } static int add_loose_object(const struct object_id *oid, const char *path, diff --git a/builtin/prune.c b/builtin/prune.c index 02c6ab7cba..485c9a3c56 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -143,7 +143,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix) expire = TIME_MAX; save_commit_buffer = 0; read_replace_refs = 0; - ref_paranoia = 1; repo_init_revisions(the_repository, &revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); diff --git a/builtin/pull.c b/builtin/pull.c index 3e13f81084..ae9f5bd7cc 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -26,13 +26,13 @@ #include "wt-status.h" #include "commit-reach.h" #include "sequencer.h" +#include "packfile.h" /** * Parses the value of --rebase. If value is a false value, returns * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is - * "merges", returns REBASE_MERGES. If value is "preserve", returns - * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if - * fatal is true, otherwise returns REBASE_INVALID. + * "merges", returns REBASE_MERGES. If value is a invalid value, dies with + * a fatal error if fatal is true, otherwise returns REBASE_INVALID. */ static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) @@ -126,7 +126,7 @@ static struct option pull_options[] = { /* Options passed to git-merge or git-rebase */ OPT_GROUP(N_("Options related to merging")), OPT_CALLBACK_F('r', "rebase", &opt_rebase, - "(false|true|merges|preserve|interactive)", + "(false|true|merges|interactive)", N_("incorporate changes by rebasing rather than merging"), PARSE_OPT_OPTARG, parse_opt_rebase), OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, @@ -577,7 +577,7 @@ static int run_fetch(const char *repo, const char **refspecs) strvec_pushv(&args, refspecs); } else if (*refspecs) BUG("refspecs without repo?"); - ret = run_command_v_opt(args.v, RUN_GIT_CMD); + ret = run_command_v_opt(args.v, RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE); strvec_clear(&args); return ret; } @@ -883,8 +883,6 @@ static int run_rebase(const struct object_id *newbase, /* Options passed to git-rebase */ if (opt_rebase == REBASE_MERGES) strvec_push(&args, "--rebase-merges"); - else if (opt_rebase == REBASE_PRESERVE) - strvec_push(&args, "--preserve-merges"); else if (opt_rebase == REBASE_INTERACTIVE) strvec_push(&args, "--interactive"); if (opt_diffstat) @@ -893,6 +891,8 @@ static int run_rebase(const struct object_id *newbase, strvec_pushv(&args, opt_strategy_opts.v); if (opt_gpg_sign) strvec_push(&args, opt_gpg_sign); + if (opt_signoff) + strvec_push(&args, opt_signoff); if (opt_autostash == 0) strvec_push(&args, "--no-autostash"); else if (opt_autostash == 1) @@ -911,12 +911,18 @@ static int run_rebase(const struct object_id *newbase, return ret; } -static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head) +static int get_can_ff(struct object_id *orig_head, + struct oid_array *merge_heads) { int ret; struct commit_list *list = NULL; struct commit *merge_head, *head; + struct object_id *orig_merge_head; + if (merge_heads->nr > 1) + return 0; + + orig_merge_head = &merge_heads->oid[0]; head = lookup_commit_reference(the_repository, orig_head); commit_list_insert(head, &list); merge_head = lookup_commit_reference(the_repository, orig_merge_head); @@ -927,9 +933,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_ static void show_advice_pull_non_ff(void) { - advise(_("Pulling without specifying how to reconcile divergent branches is\n" - "discouraged. You can squelch this message by running one of the following\n" - "commands sometime before your next pull:\n" + advise(_("You have divergent branches and need to specify how to reconcile them.\n" + "You can do so by running one of the following commands sometime before\n" + "your next pull:\n" "\n" " git config pull.rebase false # merge (the default strategy)\n" " git config pull.rebase true # rebase\n" @@ -966,8 +972,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix) parse_repo_refspecs(argc, argv, &repo, &refspecs); - if (!opt_ff) + if (!opt_ff) { opt_ff = xstrdup_or_null(config_get_ff()); + /* + * A subtle point: opt_ff was set on the line above via + * reading from config. opt_rebase, in contrast, is set + * before this point via command line options. The setting + * of opt_rebase via reading from config (using + * config_get_rebase()) does not happen until later. We + * are relying on the next if-condition happening before + * the config_get_rebase() call so that an explicit + * "--rebase" can override a config setting of + * pull.ff=only. + */ + if (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, "--ff-only")) + opt_ff = "--ff"; + } if (opt_rebase < 0) opt_rebase = config_get_rebase(&rebase_unspecified); @@ -1041,14 +1061,25 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Cannot merge multiple branches into empty head.")); return pull_into_void(merge_heads.oid, &curr_head); } - if (opt_rebase && merge_heads.nr > 1) - die(_("Cannot rebase onto multiple branches.")); + if (merge_heads.nr > 1) { + if (opt_rebase) + die(_("Cannot rebase onto multiple branches.")); + if (opt_ff && !strcmp(opt_ff, "--ff-only")) + die(_("Cannot fast-forward to multiple branches.")); + } - can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]); + can_ff = get_can_ff(&orig_head, &merge_heads); - if (rebase_unspecified && !opt_ff && !can_ff) { - if (opt_verbosity >= 0) - show_advice_pull_non_ff(); + /* ff-only takes precedence over rebase */ + if (opt_ff && !strcmp(opt_ff, "--ff-only")) { + if (!can_ff) + die_ff_impossible(); + opt_rebase = REBASE_FALSE; + } + /* If no action specified and we can't fast forward, then warn. */ + if (!opt_ff && rebase_unspecified && !can_ff) { + show_advice_pull_non_ff(); + die(_("Need to specify how to reconcile divergent branches.")); } if (opt_rebase) { diff --git a/builtin/push.c b/builtin/push.c index e8b10a9b7e..4b026ce6c6 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -289,42 +289,42 @@ static const char message_advice_ref_needs_update[] = static void advise_pull_before_push(void) { - if (!advice_push_non_ff_current || !advice_push_update_rejected) + if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_pull_before_push)); } static void advise_checkout_pull_push(void) { - if (!advice_push_non_ff_matching || !advice_push_update_rejected) + if (!advice_enabled(ADVICE_PUSH_NON_FF_MATCHING) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_checkout_pull_push)); } static void advise_ref_already_exists(void) { - if (!advice_push_already_exists || !advice_push_update_rejected) + if (!advice_enabled(ADVICE_PUSH_ALREADY_EXISTS) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_ref_already_exists)); } static void advise_ref_fetch_first(void) { - if (!advice_push_fetch_first || !advice_push_update_rejected) + if (!advice_enabled(ADVICE_PUSH_FETCH_FIRST) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_ref_fetch_first)); } static void advise_ref_needs_force(void) { - if (!advice_push_needs_force || !advice_push_update_rejected) + if (!advice_enabled(ADVICE_PUSH_NEEDS_FORCE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_ref_needs_force)); } static void advise_ref_needs_update(void) { - if (!advice_push_ref_needs_update || !advice_push_update_rejected) + if (!advice_enabled(ADVICE_PUSH_REF_NEEDS_UPDATE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_ref_needs_update)); } diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 485e7b0479..2109c4c9e5 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -38,7 +38,7 @@ static int list_tree(struct object_id *oid) } static const char * const read_tree_usage[] = { - N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), + N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), NULL }; @@ -53,24 +53,16 @@ static int index_output_cb(const struct option *opt, const char *arg, static int exclude_per_directory_cb(const struct option *opt, const char *arg, int unset) { - struct dir_struct *dir; struct unpack_trees_options *opts; BUG_ON_OPT_NEG(unset); opts = (struct unpack_trees_options *)opt->value; - if (opts->dir) - die("more than one --exclude-per-directory given."); - - dir = xcalloc(1, sizeof(*opts->dir)); - dir->flags |= DIR_SHOW_IGNORED; - dir->exclude_per_dir = arg; - opts->dir = dir; - /* We do not need to nor want to do read-directory - * here; we are merely interested in reusing the - * per directory ignore stack mechanism. - */ + if (!opts->update) + die("--exclude-per-directory is meaningless unless -u"); + if (strcmp(arg, ".gitignore")) + die("--exclude-per-directory argument must be .gitignore"); return 0; } @@ -174,6 +166,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); + if (opts.reset) + opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED; + /* * NEEDSWORK * @@ -209,8 +204,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if ((opts.update || opts.index_only) && !opts.merge) die("%s is meaningless without -m, --reset, or --prefix", opts.update ? "-u" : "-i"); - if ((opts.dir && !opts.update)) - die("--exclude-per-directory is meaningless unless -u"); + if (opts.update && !opts.reset) + opts.preserve_ignored = 0; + /* otherwise, opts.preserve_ignored is irrelevant */ if (opts.merge && !opts.index_only) setup_work_tree(); diff --git a/builtin/rebase.c b/builtin/rebase.c index 12f093121d..34b4744e5f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -48,8 +48,7 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge") enum rebase_type { REBASE_UNSPECIFIED = -1, REBASE_APPLY, - REBASE_MERGE, - REBASE_PRESERVE_MERGES + REBASE_MERGE }; enum empty_type { @@ -139,7 +138,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.ignore_date = opts->ignore_date; replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); if (opts->strategy) - replay.strategy = opts->strategy; + replay.strategy = xstrdup_or_null(opts->strategy); else if (!replay.strategy && replay.default_strategy) { replay.strategy = replay.default_strategy; replay.default_strategy = NULL; @@ -163,12 +162,7 @@ enum action { ACTION_ABORT, ACTION_QUIT, ACTION_EDIT_TODO, - ACTION_SHOW_CURRENT_PATCH, - ACTION_SHORTEN_OIDS, - ACTION_EXPAND_OIDS, - ACTION_CHECK_TODO_LIST, - ACTION_REARRANGE_SQUASH, - ACTION_ADD_EXEC + ACTION_SHOW_CURRENT_PATCH }; static const char *action_names[] = { "undefined", @@ -179,81 +173,6 @@ static const char *action_names[] = { "undefined", "edit_todo", "show_current_patch" }; -static int add_exec_commands(struct string_list *commands) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, - &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - todo_list_add_exec_commands(&todo_list, commands); - res = todo_list_write_to_file(the_repository, &todo_list, - todo_file, NULL, NULL, -1, 0); - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - -static int rearrange_squash_in_todo_file(void) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res = 0; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, - &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - res = todo_list_rearrange_squash(&todo_list); - if (!res) - res = todo_list_write_to_file(the_repository, &todo_list, - todo_file, NULL, NULL, -1, 0); - - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - -static int transform_todo_file(unsigned flags) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, - &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - res = todo_list_write_to_file(the_repository, &todo_list, todo_file, - NULL, NULL, -1, flags); - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - static int edit_todo_file(unsigned flags) { const char *todo_file = rebase_path_todo(); @@ -403,8 +322,8 @@ static int run_sequencer_rebase(struct rebase_options *opts, flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; - flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0; + flags |= opts->flags & REBASE_NO_QUIET ? TODO_LIST_WARN_SKIPPED_CHERRY_PICKS : 0; switch (command) { case ACTION_NONE: { @@ -438,24 +357,6 @@ static int run_sequencer_rebase(struct rebase_options *opts, break; } - case ACTION_SHORTEN_OIDS: - case ACTION_EXPAND_OIDS: - ret = transform_todo_file(flags); - break; - case ACTION_CHECK_TODO_LIST: - ret = check_todo_list_from_file(the_repository); - break; - case ACTION_REARRANGE_SQUASH: - ret = rearrange_squash_in_todo_file(); - break; - case ACTION_ADD_EXEC: { - struct string_list commands = STRING_LIST_INIT_DUP; - - split_exec_commands(opts->cmd, &commands); - ret = add_exec_commands(&commands); - string_list_clear(&commands, 0); - break; - } default: BUG("invalid command '%d'", command); } @@ -477,102 +378,9 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg, return 0; } -static const char * const builtin_rebase_interactive_usage[] = { - N_("git rebase--interactive [<options>]"), - NULL -}; - -int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) -{ - struct rebase_options opts = REBASE_OPTIONS_INIT; - struct object_id squash_onto = *null_oid(); - enum action command = ACTION_NONE; - struct option options[] = { - OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), - REBASE_FORCE), - OPT_CALLBACK_F('k', "keep-empty", &options, NULL, - N_("keep commits which start empty"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, - parse_opt_keep_empty), - OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages"), - PARSE_OPT_HIDDEN), - OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")), - OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins, - N_("keep original branch points of cousins")), - OPT_BOOL(0, "autosquash", &opts.autosquash, - N_("move commits that begin with squash!/fixup!")), - OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")), - OPT_BIT('v', "verbose", &opts.flags, - N_("display a diffstat of what changed upstream"), - REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), - OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), - ACTION_CONTINUE), - OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP), - OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"), - ACTION_EDIT_TODO), - OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"), - ACTION_SHOW_CURRENT_PATCH), - OPT_CMDMODE(0, "shorten-ids", &command, - N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS), - OPT_CMDMODE(0, "expand-ids", &command, - N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS), - OPT_CMDMODE(0, "check-todo-list", &command, - N_("check the todo list"), ACTION_CHECK_TODO_LIST), - OPT_CMDMODE(0, "rearrange-squash", &command, - N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH), - OPT_CMDMODE(0, "add-exec-commands", &command, - N_("insert exec commands in todo list"), ACTION_ADD_EXEC), - { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"), - PARSE_OPT_NONEG, parse_opt_commit, 0 }, - { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision, - N_("restrict-revision"), N_("restrict revision"), - PARSE_OPT_NONEG, parse_opt_commit, 0 }, - { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"), - N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 }, - { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"), - N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit, - 0 }, - OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")), - { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"), - N_("GPG-sign commits"), - PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"), - N_("rebase strategy")), - OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"), - N_("strategy options")), - OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"), - N_("the branch or commit to checkout")), - OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")), - OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")), - OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate), - OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, - N_("automatically re-schedule any `exec` that fails")), - OPT_END() - }; - - opts.rebase_cousins = -1; - - if (argc == 1) - usage_with_options(builtin_rebase_interactive_usage, options); - - argc = parse_options(argc, argv, prefix, options, - builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0); - - if (!is_null_oid(&squash_onto)) - opts.squash_onto = &squash_onto; - - if (opts.rebase_cousins >= 0 && !opts.rebase_merges) - warning(_("--[no-]rebase-cousins has no effect without " - "--rebase-merges")); - - return !!run_sequencer_rebase(&opts, command); -} - static int is_merge(struct rebase_options *opts) { - return opts->type == REBASE_MERGE || - opts->type == REBASE_PRESERVE_MERGES; + return opts->type == REBASE_MERGE; } static void imply_merge(struct rebase_options *opts, const char *option) @@ -582,7 +390,6 @@ static void imply_merge(struct rebase_options *opts, const char *option) die(_("%s requires the merge backend"), option); break; case REBASE_MERGE: - case REBASE_PRESERVE_MERGES: break; default: opts->type = REBASE_MERGE; /* implied */ @@ -740,7 +547,6 @@ static int finish_rebase(struct rebase_options *opts) delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); unlink(git_path_auto_merge(the_repository)); apply_autostash(state_dir_path("autostash", opts)); - close_object_store(the_repository->objects); /* * We ignore errors in 'git maintenance run --auto', since the * user should see them. @@ -762,28 +568,6 @@ static int finish_rebase(struct rebase_options *opts) return ret; } -static struct commit *peel_committish(const char *name) -{ - struct object *obj; - struct object_id oid; - - if (get_oid(name, &oid)) - return NULL; - obj = parse_object(the_repository, &oid); - return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); -} - -static void add_var(struct strbuf *buf, const char *name, const char *value) -{ - if (!value) - strbuf_addf(buf, "unset %s; ", name); - else { - strbuf_addf(buf, "%s=", name); - sq_quote_buf(buf, value); - strbuf_addstr(buf, "; "); - } -} - static int move_to_original_branch(struct rebase_options *opts) { struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; @@ -940,10 +724,7 @@ static int run_am(struct rebase_options *opts) static int run_specific_rebase(struct rebase_options *opts, enum action action) { - const char *argv[] = { NULL, NULL }; - struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT; int status; - const char *backend, *backend_func; if (opts->type == REBASE_MERGE) { /* Run sequencer-based rebase */ @@ -960,87 +741,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) } status = run_sequencer_rebase(opts, action); - goto finished_rebase; - } - - if (opts->type == REBASE_APPLY) { + } else if (opts->type == REBASE_APPLY) status = run_am(opts); - goto finished_rebase; - } - - add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir())); - add_var(&script_snippet, "state_dir", opts->state_dir); - - add_var(&script_snippet, "upstream_name", opts->upstream_name); - add_var(&script_snippet, "upstream", opts->upstream ? - oid_to_hex(&opts->upstream->object.oid) : NULL); - add_var(&script_snippet, "head_name", - opts->head_name ? opts->head_name : "detached HEAD"); - add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head)); - add_var(&script_snippet, "onto", opts->onto ? - oid_to_hex(&opts->onto->object.oid) : NULL); - add_var(&script_snippet, "onto_name", opts->onto_name); - add_var(&script_snippet, "revisions", opts->revisions); - add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? - oid_to_hex(&opts->restrict_revision->object.oid) : NULL); - sq_quote_argv_pretty(&buf, opts->git_am_opts.v); - add_var(&script_snippet, "git_am_opt", buf.buf); - strbuf_release(&buf); - add_var(&script_snippet, "verbose", - opts->flags & REBASE_VERBOSE ? "t" : ""); - add_var(&script_snippet, "diffstat", - opts->flags & REBASE_DIFFSTAT ? "t" : ""); - add_var(&script_snippet, "force_rebase", - opts->flags & REBASE_FORCE ? "t" : ""); - if (opts->switch_to) - add_var(&script_snippet, "switch_to", opts->switch_to); - add_var(&script_snippet, "action", opts->action ? opts->action : ""); - add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); - add_var(&script_snippet, "allow_rerere_autoupdate", - opts->allow_rerere_autoupdate ? - opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ? - "--rerere-autoupdate" : "--no-rerere-autoupdate" : ""); - add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); - add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); - add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); - add_var(&script_snippet, "cmd", opts->cmd); - add_var(&script_snippet, "allow_empty_message", - opts->allow_empty_message ? "--allow-empty-message" : ""); - add_var(&script_snippet, "rebase_merges", - opts->rebase_merges ? "t" : ""); - add_var(&script_snippet, "rebase_cousins", - opts->rebase_cousins ? "t" : ""); - add_var(&script_snippet, "strategy", opts->strategy); - add_var(&script_snippet, "strategy_opts", opts->strategy_opts); - add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); - add_var(&script_snippet, "squash_onto", - opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); - add_var(&script_snippet, "git_format_patch_opt", - opts->git_format_patch_opt.buf); - - if (is_merge(opts) && - !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { - strbuf_addstr(&script_snippet, - "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; "); - opts->autosquash = 0; - } - - switch (opts->type) { - case REBASE_PRESERVE_MERGES: - backend = "git-rebase--preserve-merges"; - backend_func = "git_rebase__preserve_merges"; - break; - default: + else BUG("Unhandled rebase type %d", opts->type); - break; - } - - strbuf_addf(&script_snippet, - ". git-sh-setup && . %s && %s", backend, backend_func); - argv[0] = script_snippet.buf; - status = run_command_v_opt(argv, RUN_USING_SHELL); -finished_rebase: if (opts->dont_finish_rebase) ; /* do nothing */ else if (opts->type == REBASE_MERGE) @@ -1058,8 +763,6 @@ finished_rebase: die("Nothing to do"); } - strbuf_release(&script_snippet); - return status ? -1 : 0; } @@ -1195,7 +898,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) return 0; } -/* -i followed by -p is still explicitly interactive, but -p alone is not */ +/* -i followed by -r is still explicitly interactive, but -r alone is not */ static int parse_opt_interactive(const struct option *opt, const char *arg, int unset) { @@ -1313,6 +1016,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) char *squash_onto_name = NULL; int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; + int preserve_merges_selected = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1377,10 +1081,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("let the user edit the list of commits to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_interactive), - OPT_SET_INT_F('p', "preserve-merges", &options.type, + OPT_SET_INT_F('p', "preserve-merges", &preserve_merges_selected, N_("(DEPRECATED) try to recreate merges instead of " "ignoring them"), - REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), + 1, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}", N_("how to handle commits that become empty"), @@ -1430,6 +1134,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + options.allow_empty_message = 1; git_config(rebase_config, &options); /* options.gpg_sign_opt will be either "-S" or NULL */ @@ -1448,8 +1155,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_reset(&buf); strbuf_addf(&buf, "%s/rewritten", merge_dir()); if (is_directory(buf.buf)) { - options.type = REBASE_PRESERVE_MERGES; - options.flags |= REBASE_INTERACTIVE_EXPLICIT; + die("`rebase -p` is no longer supported"); } else { strbuf_reset(&buf); strbuf_addf(&buf, "%s/interactive", merge_dir()); @@ -1470,6 +1176,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) builtin_rebase_options, builtin_rebase_usage, 0); + if (preserve_merges_selected) + die(_("--preserve-merges was replaced by --rebase-merges")); + if (action != ACTION_NONE && total_argc != 2) { usage_with_options(builtin_rebase_usage, builtin_rebase_options); @@ -1479,10 +1188,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); - if (options.type == REBASE_PRESERVE_MERGES) - warning(_("git rebase --preserve-merges is deprecated. " - "Use --rebase-merges instead.")); - if (keep_base) { if (options.onto_name) die(_("cannot combine '--keep-base' with '--onto'")); @@ -1574,7 +1279,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); - ret = !!finish_rebase(&options); + ret = finish_rebase(&options); goto cleanup; } case ACTION_QUIT: { @@ -1583,11 +1288,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; - ret = !!sequencer_remove_state(&replay); + ret = sequencer_remove_state(&replay); } else { strbuf_reset(&buf); strbuf_addstr(&buf, options.state_dir); - ret = !!remove_dir_recursively(&buf, 0); + ret = remove_dir_recursively(&buf, 0); if (ret) error(_("could not remove '%s'"), options.state_dir); @@ -1702,7 +1407,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.ignore_date) strvec_push(&options.git_am_opts, "--ignore-date"); } else { - /* REBASE_MERGE and PRESERVE_MERGES */ + /* REBASE_MERGE */ if (ignore_whitespace) { string_list_append(&strategy_options, "ignore-space-change"); @@ -1713,7 +1418,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int i; if (!options.strategy) - options.strategy = "recursive"; + options.strategy = "ort"; strbuf_reset(&buf); for (i = 0; i < strategy_options.nr; i++) @@ -1728,7 +1433,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) case REBASE_APPLY: die(_("--strategy requires --merge or --interactive")); case REBASE_MERGE: - case REBASE_PRESERVE_MERGES: /* compatible */ break; case REBASE_UNSPECIFIED: @@ -1780,7 +1484,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) switch (options.type) { case REBASE_MERGE: - case REBASE_PRESERVE_MERGES: options.state_dir = merge_dir(); break; case REBASE_APPLY: @@ -1805,28 +1508,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.reschedule_failed_exec = reschedule_failed_exec; if (options.signoff) { - if (options.type == REBASE_PRESERVE_MERGES) - die("cannot combine '--signoff' with " - "'--preserve-merges'"); strvec_push(&options.git_am_opts, "--signoff"); options.flags |= REBASE_FORCE; } - if (options.type == REBASE_PRESERVE_MERGES) { - /* - * Note: incompatibility with --signoff handled in signoff block above - * Note: incompatibility with --interactive is just a strong warning; - * git-rebase.txt caveats with "unless you know what you are doing" - */ - if (options.rebase_merges) - die(_("cannot combine '--preserve-merges' with " - "'--rebase-merges'")); - - if (options.reschedule_failed_exec) - die(_("error: cannot combine '--preserve-merges' with " - "'--reschedule-failed-exec'")); - } - if (!options.root) { if (argc < 1) { struct branch *branch; @@ -1845,7 +1530,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!strcmp(options.upstream_name, "-")) options.upstream_name = "@{-1}"; } - options.upstream = peel_committish(options.upstream_name); + options.upstream = + lookup_commit_reference_by_name(options.upstream_name); if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); options.upstream_arg = options.upstream_name; @@ -1888,7 +1574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { - options.onto = peel_committish(options.onto_name); + options.onto = + lookup_commit_reference_by_name(options.onto_name); if (!options.onto) die(_("Does not point to a valid commit '%s'"), options.onto_name); @@ -1913,13 +1600,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die_if_checked_out(buf.buf, 1); options.head_name = xstrdup(buf.buf); /* If not is it a valid ref (branch or commit)? */ - } else if (!get_oid(branch_name, &options.orig_head) && - lookup_commit_reference(the_repository, - &options.orig_head)) + } else { + struct commit *commit = + lookup_commit_reference_by_name(branch_name); + if (!commit) + die(_("no such branch/commit '%s'"), + branch_name); + oidcpy(&options.orig_head, &commit->object.oid); options.head_name = NULL; - else - die(_("fatal: no such branch/commit '%s'"), - branch_name); + } } else if (argc == 0) { /* Do not need to switch branches, we are already on it. */ options.head_name = @@ -1959,7 +1648,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (require_clean_work_tree(the_repository, "rebase", _("Please commit or stash them."), 1, 1)) { - ret = 1; + ret = -1; goto cleanup; } @@ -1994,7 +1683,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0) { - ret = !!error(_("could not switch to " + ret = error(_("could not switch to " "%s"), options.switch_to); goto cleanup; @@ -2009,7 +1698,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) else printf(_("Current branch %s is up to date.\n"), branch_name); - ret = !!finish_rebase(&options); + ret = finish_rebase(&options); goto cleanup; } else if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ @@ -2087,7 +1776,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, DEFAULT_REFLOG_ACTION); strbuf_release(&msg); - ret = !!finish_rebase(&options); + ret = finish_rebase(&options); goto cleanup; } @@ -2101,7 +1790,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.revisions = revisions.buf; run_rebase: - ret = !!run_specific_rebase(&options, action); + ret = run_specific_rebase(&options, action); cleanup: strbuf_release(&buf); @@ -2109,7 +1798,8 @@ cleanup: free(options.head_name); free(options.gpg_sign_opt); free(options.cmd); + free(options.strategy); strbuf_release(&options.git_format_patch_opt); free(squash_onto_name); - return ret; + return !!ret; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index f17c7d2246..49b846d960 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -7,6 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" +#include "hook.h" #include "exec-cmd.h" #include "commit.h" #include "object.h" @@ -429,9 +430,6 @@ static int proc_receive_ref_matches(struct command *cmd) return 0; } -static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); -static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); - static void report_message(const char *prefix, const char *err, va_list params) { int sz; @@ -449,6 +447,7 @@ static void report_message(const char *prefix, const char *err, va_list params) xwrite(2, msg, sz); } +__attribute__((format (printf, 1, 2))) static void rp_warning(const char *err, ...) { va_list params; @@ -457,6 +456,7 @@ static void rp_warning(const char *err, ...) va_end(params); } +__attribute__((format (printf, 1, 2))) static void rp_error(const char *err, ...) { va_list params; @@ -1311,7 +1311,7 @@ static void refuse_unconfigured_deny_delete_current(void) rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg)); } -static int command_singleton_iterator(void *cb_data, struct object_id *oid); +static const struct object_id *command_singleton_iterator(void *cb_data); static int update_shallow_ref(struct command *cmd, struct shallow_info *si) { struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT; @@ -1468,7 +1468,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); - if (!find_hook(push_to_checkout_hook)) + if (!hook_exists(push_to_checkout_hook)) retval = push_to_deploy(sha1, &env, work_tree); else retval = push_to_checkout(sha1, &env, work_tree); @@ -1736,16 +1736,15 @@ static void check_aliased_updates(struct command *commands) string_list_clear(&ref_list, 0); } -static int command_singleton_iterator(void *cb_data, struct object_id *oid) +static const struct object_id *command_singleton_iterator(void *cb_data) { struct command **cmd_list = cb_data; struct command *cmd = *cmd_list; if (!cmd || is_null_oid(&cmd->new_oid)) - return -1; /* end of list */ + return NULL; *cmd_list = NULL; /* this returns only one */ - oidcpy(oid, &cmd->new_oid); - return 0; + return &cmd->new_oid; } static void set_connectivity_errors(struct command *commands, @@ -1775,7 +1774,7 @@ struct iterate_data { struct shallow_info *si; }; -static int iterate_receive_command_list(void *cb_data, struct object_id *oid) +static const struct object_id *iterate_receive_command_list(void *cb_data) { struct iterate_data *data = cb_data; struct command **cmd_list = &data->cmds; @@ -1786,13 +1785,11 @@ static int iterate_receive_command_list(void *cb_data, struct object_id *oid) /* to be checked in update_shallow_ref() */ continue; if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) { - oidcpy(oid, &cmd->new_oid); *cmd_list = cmd->next; - return 0; + return &cmd->new_oid; } } - *cmd_list = NULL; - return -1; /* end of list */ + return NULL; } static void reject_updates_to_hidden(struct command *commands) @@ -2482,7 +2479,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__QUIET(&quiet, N_("quiet")), OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL), - OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL), + OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs, NULL), + OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"), OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL), OPT_END() }; @@ -2585,10 +2583,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; - proc.git_cmd = 1; + proc.git_cmd = proc.close_object_store = 1; proc.argv = argv_gc_auto; - close_object_store(the_repository->objects); if (!start_command(&proc)) { if (use_sideband) copy_to_sideband(proc.err, -1, NULL); diff --git a/builtin/reflog.c b/builtin/reflog.c index 09541d1c80..bd4c669918 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -629,8 +629,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) free_worktrees(worktrees); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; + set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); - status |= reflog_expire(e->reflog, &e->oid, flags, + status |= reflog_expire(e->reflog, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, @@ -642,13 +643,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (; i < argc; i++) { char *ref; - struct object_id oid; - if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) { + if (!dwim_log(argv[i], strlen(argv[i]), NULL, &ref)) { status |= error(_("%s points nowhere!"), argv[i]); continue; } set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); - status |= reflog_expire(ref, &oid, flags, + status |= reflog_expire(ref, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, @@ -700,7 +700,6 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) for ( ; i < argc; i++) { const char *spec = strstr(argv[i], "@{"); - struct object_id oid; char *ep, *ref; int recno; @@ -709,7 +708,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) continue; } - if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) { + if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) { status |= error(_("no reflog for '%s'"), argv[i]); continue; } @@ -724,7 +723,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) cb.cmd.expire_total = 0; } - status |= reflog_expire(ref, &oid, flags, + status |= reflog_expire(ref, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, diff --git a/builtin/remote.c b/builtin/remote.c index 7f88e6ce9d..299c466116 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -318,6 +318,9 @@ static int config_read_branches(const char *key, const char *value, void *cb) * truth value with >= REBASE_TRUE. */ info->rebase = rebase_parse_value(value); + if (info->rebase == REBASE_INVALID) + warning(_("unhandled branch.%s.rebase=%s; assuming " + "'true'"), name, value); break; case PUSH_REMOTE: if (info->push_remote_name) @@ -344,6 +347,14 @@ struct ref_states { int queried; }; +#define REF_STATES_INIT { \ + .new_refs = STRING_LIST_INIT_DUP, \ + .stale = STRING_LIST_INIT_DUP, \ + .tracked = STRING_LIST_INIT_DUP, \ + .heads = STRING_LIST_INIT_DUP, \ + .push = STRING_LIST_INIT_DUP, \ +} + static int get_ref_states(const struct ref *remote_refs, struct ref_states *states) { struct ref *fetch_map = NULL, **tail = &fetch_map; @@ -355,9 +366,6 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat die(_("Could not get fetch map for refspec %s"), states->remote->fetch.raw[i]); - states->new_refs.strdup_strings = 1; - states->tracked.strdup_strings = 1; - states->stale.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new_refs, abbrev_branch(ref->name)); @@ -406,7 +414,6 @@ static int get_push_ref_states(const struct ref *remote_refs, match_push_refs(local_refs, &push_map, &remote->push, MATCH_REFS_NONE); - states->push.strdup_strings = 1; for (ref = push_map; ref; ref = ref->next) { struct string_list_item *item; struct push_info *info; @@ -449,7 +456,6 @@ static int get_push_ref_states_noquery(struct ref_states *states) if (remote->mirror) return 0; - states->push.strdup_strings = 1; if (!remote->push.nr) { item = string_list_append(&states->push, _("(matching)")); info = item->util = xcalloc(1, sizeof(struct push_info)); @@ -483,7 +489,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat refspec.force = 0; refspec.pattern = 1; refspec.src = refspec.dst = "refs/heads/*"; - states->heads.strdup_strings = 1; get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), fetch_map, 1); @@ -970,26 +975,31 @@ static int get_remote_ref_states(const char *name, } struct show_info { - struct string_list *list; - struct ref_states *states; + struct string_list list; + struct ref_states states; int width, width2; int any_rebase; }; +#define SHOW_INFO_INIT { \ + .list = STRING_LIST_INIT_DUP, \ + .states = REF_STATES_INIT, \ +} + static int add_remote_to_show_info(struct string_list_item *item, void *cb_data) { struct show_info *info = cb_data; int n = strlen(item->string); if (n > info->width) info->width = n; - string_list_insert(info->list, item->string); + string_list_insert(&info->list, item->string); return 0; } static int show_remote_info_item(struct string_list_item *item, void *cb_data) { struct show_info *info = cb_data; - struct ref_states *states = info->states; + struct ref_states *states = &info->states; const char *name = item->string; if (states->queried) { @@ -1016,7 +1026,7 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) static int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data) { struct show_info *show_info = cb_data; - struct ref_states *states = show_info->states; + struct ref_states *states = &show_info->states; struct branch_info *branch_info = branch_item->util; struct string_list_item *item; int n; @@ -1029,7 +1039,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb if (branch_info->rebase >= REBASE_TRUE) show_info->any_rebase = 1; - item = string_list_insert(show_info->list, branch_item->string); + item = string_list_insert(&show_info->list, branch_item->string); item->util = branch_info; return 0; @@ -1084,7 +1094,7 @@ static int add_push_to_show_info(struct string_list_item *push_item, void *cb_da show_info->width = n; if ((n = strlen(push_info->dest)) > show_info->width2) show_info->width2 = n; - item = string_list_append(show_info->list, push_item->string); + item = string_list_append(&show_info->list, push_item->string); item->util = push_item->util; return 0; } @@ -1212,9 +1222,7 @@ static int show(int argc, const char **argv) OPT_BOOL('n', NULL, &no_query, N_("do not query remotes")), OPT_END() }; - struct ref_states states; - struct string_list info_list = STRING_LIST_INIT_NODUP; - struct show_info info; + struct show_info info = SHOW_INFO_INIT; argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage, 0); @@ -1225,26 +1233,22 @@ static int show(int argc, const char **argv) if (!no_query) query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES); - memset(&states, 0, sizeof(states)); - memset(&info, 0, sizeof(info)); - info.states = &states; - info.list = &info_list; for (; argc; argc--, argv++) { int i; const char **url; int url_nr; - get_remote_ref_states(*argv, &states, query_flag); + get_remote_ref_states(*argv, &info.states, query_flag); printf_ln(_("* remote %s"), *argv); - printf_ln(_(" Fetch URL: %s"), states.remote->url_nr > 0 ? - states.remote->url[0] : _("(no URL)")); - if (states.remote->pushurl_nr) { - url = states.remote->pushurl; - url_nr = states.remote->pushurl_nr; + printf_ln(_(" Fetch URL: %s"), info.states.remote->url_nr > 0 ? + info.states.remote->url[0] : _("(no URL)")); + if (info.states.remote->pushurl_nr) { + url = info.states.remote->pushurl; + url_nr = info.states.remote->pushurl_nr; } else { - url = states.remote->url; - url_nr = states.remote->url_nr; + url = info.states.remote->url; + url_nr = info.states.remote->url_nr; } for (i = 0; i < url_nr; i++) /* @@ -1257,57 +1261,57 @@ static int show(int argc, const char **argv) printf_ln(_(" Push URL: %s"), _("(no URL)")); if (no_query) printf_ln(_(" HEAD branch: %s"), _("(not queried)")); - else if (!states.heads.nr) + else if (!info.states.heads.nr) printf_ln(_(" HEAD branch: %s"), _("(unknown)")); - else if (states.heads.nr == 1) - printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string); + else if (info.states.heads.nr == 1) + printf_ln(_(" HEAD branch: %s"), info.states.heads.items[0].string); else { printf(_(" HEAD branch (remote HEAD is ambiguous," " may be one of the following):\n")); - for (i = 0; i < states.heads.nr; i++) - printf(" %s\n", states.heads.items[i].string); + for (i = 0; i < info.states.heads.nr; i++) + printf(" %s\n", info.states.heads.items[i].string); } /* remote branch info */ info.width = 0; - for_each_string_list(&states.new_refs, add_remote_to_show_info, &info); - for_each_string_list(&states.tracked, add_remote_to_show_info, &info); - for_each_string_list(&states.stale, add_remote_to_show_info, &info); - if (info.list->nr) + for_each_string_list(&info.states.new_refs, 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) printf_ln(Q_(" Remote branch:%s", " Remote branches:%s", - info.list->nr), + info.list.nr), no_query ? _(" (status not queried)") : ""); - for_each_string_list(info.list, show_remote_info_item, &info); - string_list_clear(info.list, 0); + for_each_string_list(&info.list, show_remote_info_item, &info); + string_list_clear(&info.list, 0); /* git pull info */ info.width = 0; info.any_rebase = 0; for_each_string_list(&branch_list, add_local_to_show_info, &info); - if (info.list->nr) + if (info.list.nr) printf_ln(Q_(" Local branch configured for 'git pull':", " Local branches configured for 'git pull':", - info.list->nr)); - for_each_string_list(info.list, show_local_info_item, &info); - string_list_clear(info.list, 0); + info.list.nr)); + for_each_string_list(&info.list, show_local_info_item, &info); + string_list_clear(&info.list, 0); /* git push info */ - if (states.remote->mirror) + if (info.states.remote->mirror) printf_ln(_(" Local refs will be mirrored by 'git push'")); info.width = info.width2 = 0; - for_each_string_list(&states.push, add_push_to_show_info, &info); - QSORT(info.list->items, info.list->nr, cmp_string_with_push); - if (info.list->nr) + for_each_string_list(&info.states.push, add_push_to_show_info, &info); + QSORT(info.list.items, info.list.nr, cmp_string_with_push); + if (info.list.nr) printf_ln(Q_(" Local ref configured for 'git push'%s:", " Local refs configured for 'git push'%s:", - info.list->nr), + info.list.nr), no_query ? _(" (status not queried)") : ""); - for_each_string_list(info.list, show_push_info_item, &info); - string_list_clear(info.list, 0); + for_each_string_list(&info.list, show_push_info_item, &info); + string_list_clear(&info.list, 0); - free_remote_ref_states(&states); + free_remote_ref_states(&info.states); } return result; @@ -1334,8 +1338,7 @@ static int set_head(int argc, const char **argv) if (!opt_a && !opt_d && argc == 2) { head_name = xstrdup(argv[1]); } else if (opt_a && !opt_d && argc == 1) { - struct ref_states states; - memset(&states, 0, sizeof(states)); + struct ref_states states = REF_STATES_INIT; get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES); if (!states.heads.nr) result |= error(_("Cannot determine remote HEAD")); @@ -1374,14 +1377,13 @@ static int set_head(int argc, const char **argv) static int prune_remote(const char *remote, int dry_run) { int result = 0; - struct ref_states states; + struct ref_states states = REF_STATES_INIT; struct string_list refs_to_prune = STRING_LIST_INIT_NODUP; struct string_list_item *item; const char *dangling_msg = dry_run ? _(" %s will become dangling!") : _(" %s has become dangling!"); - memset(&states, 0, sizeof(states)); get_remote_ref_states(remote, &states, GET_REF_STATES); if (!states.stale.nr) { diff --git a/builtin/repack.c b/builtin/repack.c index 5f9bc74adc..0b2d1e5d82 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -15,6 +15,8 @@ #include "promisor-remote.h" #include "shallow.h" #include "pack.h" +#include "pack-bitmap.h" +#include "refs.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; @@ -94,12 +96,14 @@ static void remove_pack_on_signal(int signo) } /* - * Adds all packs hex strings to the fname list, which do not - * have a corresponding .keep file. These packs are not to - * be kept if we are going to pack everything into one file. + * Adds all packs hex strings to either fname_nonkept_list or + * fname_kept_list based on whether each pack has a corresponding + * .keep file or not. Packs without a .keep file are not to be kept + * if we are going to pack everything into one file. */ -static void get_non_kept_pack_filenames(struct string_list *fname_list, - const struct string_list *extra_keep) +static void collect_pack_filenames(struct string_list *fname_nonkept_list, + struct string_list *fname_kept_list, + const struct string_list *extra_keep) { DIR *dir; struct dirent *e; @@ -112,21 +116,20 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list, size_t len; int i; + if (!strip_suffix(e->d_name, ".pack", &len)) + continue; + for (i = 0; i < extra_keep->nr; i++) if (!fspathcmp(e->d_name, extra_keep->items[i].string)) break; - if (extra_keep->nr > 0 && i < extra_keep->nr) - continue; - - if (!strip_suffix(e->d_name, ".pack", &len)) - continue; fname = xmemdupz(e->d_name, len); - if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) - string_list_append_nodup(fname_list, fname); + if ((extra_keep->nr > 0 && i < extra_keep->nr) || + (file_exists(mkpath("%s/%s.keep", packdir, fname)))) + string_list_append_nodup(fname_kept_list, fname); else - free(fname); + string_list_append_nodup(fname_nonkept_list, fname); } closedir(dir); } @@ -208,10 +211,10 @@ static struct { unsigned optional:1; } exts[] = { {".pack"}, - {".idx"}, {".rev", 1}, {".bitmap", 1}, {".promisor", 1}, + {".idx"}, }; static unsigned populate_pack_exts(char *name) @@ -422,6 +425,25 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor) geometry->split = split; } +static struct packed_git *get_largest_active_pack(struct pack_geometry *geometry) +{ + if (!geometry) { + /* + * No geometry means either an all-into-one repack (in which + * case there is only one pack left and it is the largest) or an + * incremental one. + * + * If repacking incrementally, then we could check the size of + * all packs to determine which should be preferred, but leave + * this for later. + */ + return NULL; + } + if (geometry->split == geometry->pack_nr) + return NULL; + return geometry->pack[geometry->pack_nr - 1]; +} + static void clear_pack_geometry(struct pack_geometry *geometry) { if (!geometry) @@ -433,17 +455,162 @@ static void clear_pack_geometry(struct pack_geometry *geometry) geometry->split = 0; } +struct midx_snapshot_ref_data { + struct tempfile *f; + struct oidset seen; + int preferred; +}; + +static int midx_snapshot_ref_one(const char *refname, + const struct object_id *oid, + int flag, void *_data) +{ + struct midx_snapshot_ref_data *data = _data; + struct object_id peeled; + + if (!peel_iterated_oid(oid, &peeled)) + oid = &peeled; + + if (oidset_insert(&data->seen, oid)) + return 0; /* already seen */ + + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "", + oid_to_hex(oid)); + + return 0; +} + +static void midx_snapshot_refs(struct tempfile *f) +{ + struct midx_snapshot_ref_data data; + const struct string_list *preferred = bitmap_preferred_tips(the_repository); + + data.f = f; + data.preferred = 0; + oidset_init(&data.seen, 0); + + if (!fdopen_tempfile(f, "w")) + die(_("could not open tempfile %s for writing"), + get_tempfile_path(f)); + + if (preferred) { + struct string_list_item *item; + + data.preferred = 1; + for_each_string_list_item(item, preferred) + for_each_ref_in(item->string, midx_snapshot_ref_one, &data); + data.preferred = 0; + } + + for_each_ref(midx_snapshot_ref_one, &data); + + if (close_tempfile_gently(f)) { + int save_errno = errno; + delete_tempfile(&f); + errno = save_errno; + die_errno(_("could not close refs snapshot tempfile")); + } + + oidset_clear(&data.seen); +} + +static void midx_included_packs(struct string_list *include, + struct string_list *existing_nonkept_packs, + struct string_list *existing_kept_packs, + struct string_list *names, + struct pack_geometry *geometry) +{ + struct string_list_item *item; + + for_each_string_list_item(item, existing_kept_packs) + string_list_insert(include, xstrfmt("%s.idx", item->string)); + for_each_string_list_item(item, names) + string_list_insert(include, xstrfmt("pack-%s.idx", item->string)); + if (geometry) { + struct strbuf buf = STRBUF_INIT; + uint32_t i; + for (i = geometry->split; i < geometry->pack_nr; i++) { + struct packed_git *p = geometry->pack[i]; + + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_addstr(&buf, ".idx"); + + string_list_insert(include, strbuf_detach(&buf, NULL)); + } + } else { + for_each_string_list_item(item, existing_nonkept_packs) { + if (item->util) + continue; + string_list_insert(include, xstrfmt("%s.idx", item->string)); + } + } +} + +static int write_midx_included_packs(struct string_list *include, + struct pack_geometry *geometry, + const char *refs_snapshot, + int show_progress, int write_bitmaps) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct string_list_item *item; + struct packed_git *largest = get_largest_active_pack(geometry); + FILE *in; + int ret; + + if (!include->nr) + return 0; + + cmd.in = -1; + cmd.git_cmd = 1; + + strvec_push(&cmd.args, "multi-pack-index"); + strvec_pushl(&cmd.args, "write", "--stdin-packs", NULL); + + if (show_progress) + strvec_push(&cmd.args, "--progress"); + else + strvec_push(&cmd.args, "--no-progress"); + + if (write_bitmaps) + strvec_push(&cmd.args, "--bitmap"); + + if (largest) + strvec_pushf(&cmd.args, "--preferred-pack=%s", + pack_basename(largest)); + + if (refs_snapshot) + strvec_pushf(&cmd.args, "--refs-snapshot=%s", refs_snapshot); + + ret = start_command(&cmd); + if (ret) + return ret; + + in = xfdopen(cmd.in, "w"); + for_each_string_list_item(item, include) + fprintf(in, "%s\n", item->string); + fclose(in); + + return finish_command(&cmd); +} + 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_packs = STRING_LIST_INIT_DUP; + 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; struct strbuf line = STRBUF_INIT; + struct tempfile *refs_snapshot = NULL; int i, ext, ret; FILE *out; + int show_progress = isatty(2); /* variables to be filled by option parsing */ int pack_everything = 0; @@ -454,6 +621,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int no_update_server_info = 0; struct pack_objects_args po_args = {NULL}; int geometric_factor = 0; + int write_midx = 0; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -496,6 +664,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("do not repack this pack")), OPT_INTEGER('g', "geometric", &geometric_factor, N_("find a geometric progression with factor <N>")), + OPT_BOOL('m', "write-midx", &write_midx, + N_("write a multi-pack index of the resulting packs")), OPT_END() }; @@ -512,16 +682,32 @@ int cmd_repack(int argc, const char **argv, const char *prefix) die(_("--keep-unreachable and -A are incompatible")); if (write_bitmaps < 0) { - if (!(pack_everything & ALL_INTO_ONE) || - !is_bare_repository()) + if (!write_midx && + (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository())) write_bitmaps = 0; + } else if (write_bitmaps && + git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0) && + git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0)) { + write_bitmaps = 0; } if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps > 0; - if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) + if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx) die(_(incremental_bitmap_conflict_error)); + if (write_midx && write_bitmaps) { + struct strbuf path = STRBUF_INIT; + + strbuf_addf(&path, "%s/%s_XXXXXX", get_object_directory(), + "bitmap-ref-tips"); + + refs_snapshot = xmks_tempfile(path.buf); + midx_snapshot_refs(refs_snapshot); + + strbuf_release(&path); + } + if (geometric_factor) { if (pack_everything) die(_("--geometric is incompatible with -A, -a")); @@ -561,19 +747,22 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } if (has_promisor_remote()) strvec_push(&cmd.args, "--exclude-promisor-objects"); - if (write_bitmaps > 0) - strvec_push(&cmd.args, "--write-bitmap-index"); - else if (write_bitmaps < 0) - strvec_push(&cmd.args, "--write-bitmap-index-quiet"); + if (!write_midx) { + if (write_bitmaps > 0) + strvec_push(&cmd.args, "--write-bitmap-index"); + else if (write_bitmaps < 0) + strvec_push(&cmd.args, "--write-bitmap-index-quiet"); + } if (use_delta_islands) strvec_push(&cmd.args, "--delta-islands"); - if (pack_everything & ALL_INTO_ONE) { - get_non_kept_pack_filenames(&existing_packs, &keep_pack_list); + collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs, + &keep_pack_list); + if (pack_everything & ALL_INTO_ONE) { repack_promisor_objects(&po_args, &names); - if (existing_packs.nr && delete_redundant) { + if (existing_nonkept_packs.nr && delete_redundant) { for_each_string_list_item(item, &names) { strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack", packtmp_name, item->string); @@ -582,15 +771,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strvec_pushf(&cmd.args, "--unpack-unreachable=%s", unpack_unreachable); - strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); } else if (pack_everything & LOOSEN_UNREACHABLE) { strvec_push(&cmd.args, "--unpack-unreachable"); } else if (keep_unreachable) { strvec_push(&cmd.args, "--keep-unreachable"); strvec_push(&cmd.args, "--pack-loose-unreachable"); - } else { - strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); } } } else if (geometry) { @@ -676,20 +862,48 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } /* End of pack replacement. */ - reprepare_packed_git(the_repository); - - if (delete_redundant) { + if (delete_redundant && pack_everything & ALL_INTO_ONE) { const int hexsz = the_hash_algo->hexsz; - int opts = 0; string_list_sort(&names); - for_each_string_list_item(item, &existing_packs) { + for_each_string_list_item(item, &existing_nonkept_packs) { char *sha1; size_t len = strlen(item->string); if (len < hexsz) continue; sha1 = item->string + len - hexsz; - if (!string_list_has_string(&names, sha1)) - remove_redundant_pack(packdir, item->string); + /* + * Mark this pack for deletion, which ensures that this + * pack won't be included in a MIDX (if `--write-midx` + * was given) and that we will actually delete this pack + * (if `-d` was given). + */ + item->util = (void*)(intptr_t)!string_list_has_string(&names, sha1); + } + } + + if (write_midx) { + struct string_list include = STRING_LIST_INIT_NODUP; + midx_included_packs(&include, &existing_nonkept_packs, + &existing_kept_packs, &names, geometry); + + ret = write_midx_included_packs(&include, geometry, + refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL, + show_progress, write_bitmaps > 0); + + string_list_clear(&include, 0); + + if (ret) + return ret; + } + + reprepare_packed_git(the_repository); + + if (delete_redundant) { + int opts = 0; + for_each_string_list_item(item, &existing_nonkept_packs) { + if (!item->util) + continue; + remove_redundant_pack(packdir, item->string); } if (geometry) { @@ -710,7 +924,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); } - if (!po_args.quiet && isatty(2)) + if (!po_args.quiet && show_progress) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); @@ -725,12 +939,17 @@ int cmd_repack(int argc, const char **argv, const char *prefix) update_server_info(0); remove_temporary_files(); - if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) - write_midx_file(get_object_directory(), NULL, 0); + if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) { + unsigned flags = 0; + if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0)) + flags |= MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX; + write_midx_file(get_object_directory(), NULL, NULL, flags); + } string_list_clear(&names, 0); string_list_clear(&rollback, 0); - string_list_clear(&existing_packs, 0); + string_list_clear(&existing_nonkept_packs, 0); + string_list_clear(&existing_kept_packs, 0); clear_pack_geometry(geometry); strbuf_release(&line); diff --git a/builtin/replace.c b/builtin/replace.c index cd48765911..946938d011 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -507,7 +507,7 @@ static int convert_graft_file(int force) if (!fp) return -1; - advice_graft_file_deprecated = 0; + no_graft_file_deprecated_advice = 1; while (strbuf_getline(&buf, fp) != EOF) { if (*buf.buf == '#') continue; diff --git a/builtin/reset.c b/builtin/reset.c index 43e855cb88..7393595349 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -67,12 +67,18 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t case KEEP: case MERGE: opts.update = 1; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ break; case HARD: opts.update = 1; - /* fallthrough */ + opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED; + break; + case MIXED: + opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; + /* but opts.update=0, so working tree not updated */ + break; default: - opts.reset = 1; + BUG("invalid reset_type passed to reset_index"); } read_cache_unmerged(); @@ -412,7 +418,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); t_delta_in_ms = (getnanotime() - t_begin) / 1000000; - if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { + if (advice_enabled(ADVICE_RESET_QUIET_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n" "use '--quiet' to avoid this. Set the config setting reset.quiet to true\n" "to make this the default.\n"), t_delta_in_ms / 1000.0); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 7677b1af5a..36cb909eba 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -127,13 +127,15 @@ static void show_commit(struct commit *commit, void *data) if (info->header_prefix) fputs(info->header_prefix, stdout); - if (!revs->graph) - fputs(get_revision_mark(revs, commit), stdout); - if (revs->abbrev_commit && revs->abbrev) - fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev), - stdout); - else - fputs(oid_to_hex(&commit->object.oid), stdout); + if (revs->include_header) { + if (!revs->graph) + fputs(get_revision_mark(revs, commit), stdout); + if (revs->abbrev_commit && revs->abbrev) + fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev), + stdout); + else + fputs(oid_to_hex(&commit->object.oid), stdout); + } if (revs->print_parents) { struct commit_list *parents = commit->parents; while (parents) { @@ -153,7 +155,7 @@ static void show_commit(struct commit *commit, void *data) show_decorations(revs, commit); if (revs->commit_format == CMIT_FMT_ONELINE) putchar(' '); - else + else if (revs->include_header) putchar('\n'); if (revs->verbose_header) { @@ -512,6 +514,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &revs, prefix); revs.abbrev = DEFAULT_ABBREV; revs.commit_format = CMIT_FMT_UNSPECIFIED; + revs.include_header = 1; /* * Scan the argument list before invoking setup_revisions(), so that we @@ -627,6 +630,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } + if (!strcmp(arg, ("--commit-header"))) { + revs.include_header = 1; + continue; + } + + if (!strcmp(arg, ("--no-commit-header"))) { + revs.include_header = 0; + continue; + } + if (!strcmp(arg, "--disk-usage")) { show_disk_usage = 1; info.flags |= REV_LIST_QUIET; @@ -636,10 +649,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) usage(rev_list_usage); } + if (revs.commit_format != CMIT_FMT_USERFORMAT) + revs.include_header = 1; if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { /* The command line has a --pretty */ info.hdr_termination = '\n'; - if (revs.commit_format == CMIT_FMT_ONELINE) + if (revs.commit_format == CMIT_FMT_ONELINE || !revs.include_header) info.header_prefix = ""; else info.header_prefix = "commit "; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 22c4e1a4ff..8480a59f57 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -863,8 +863,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--bisect")) { - for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0); - for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0); + for_each_fullref_in("refs/bisect/bad", show_reference, NULL); + for_each_fullref_in("refs/bisect/good", anti_reference, NULL); continue; } if (opt_with_value(arg, "--branches", &arg)) { diff --git a/builtin/revert.c b/builtin/revert.c index 237f2f18d4..51776abea6 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -136,6 +136,9 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + /* implies allow_empty */ if (opts->keep_redundant_commits) opts->allow_empty = 1; @@ -191,7 +194,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) struct setup_revision_opt s_r_opt; opts->revs = xmalloc(sizeof(*opts->revs)); repo_init_revisions(the_repository, opts->revs, NULL); - opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; + opts->revs->no_walk = 1; + opts->revs->unsorted_input = 1; if (argc < 2) usage_with_options(usage_str, options); if (!strcmp(argv[1], "-")) diff --git a/builtin/rm.c b/builtin/rm.c index 8a24c715e0..3d0967cdc1 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -55,7 +55,7 @@ static void print_error_files(struct string_list *files_list, strbuf_addf(&err_msg, "\n %s", files_list->items[i].string); - if (advice_rm_hints) + if (advice_enabled(ADVICE_RM_HINTS)) strbuf_addstr(&err_msg, hints_msg); *errs = error("%s", err_msg.buf); strbuf_release(&err_msg); @@ -237,6 +237,7 @@ static int check_local_mod(struct object_id *head, int index_only) static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; static int ignore_unmatch = 0, pathspec_file_nul; +static int include_sparse; static char *pathspec_from_file; static struct option builtin_rm_options[] = { @@ -247,6 +248,7 @@ static struct option builtin_rm_options[] = { OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, N_("exit with a zero status even if nothing matched")), + OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), @@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; - if (ce_skip_worktree(ce)) + + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) continue; if (!ce_path_match(&the_index, ce, &pathspec, seen)) continue; @@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen_any = 1; else if (ignore_unmatch) continue; - else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) + else if (!include_sparse && + matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) string_list_append(&only_match_skip_worktree, original); else die(_("pathspec '%s' did not match any files"), original); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index a7e01667b0..8932142312 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -17,10 +17,10 @@ #include "protocol.h" static const char * const send_pack_usage[] = { - N_("git send-pack [--all | --mirror] [--dry-run] [--force] " - "[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] " - "[<host>:]<directory> [<ref>...]\n" - " --all and explicit <ref> specification are mutually exclusive."), + N_("git send-pack [--mirror] [--dry-run] [--force]\n" + " [--receive-pack=<git-receive-pack>]\n" + " [--verbose] [--thin] [--atomic]\n" + " [<host>:]<directory> (--all | <ref>...)"), NULL, }; @@ -230,6 +230,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.atomic = atomic; args.stateless_rpc = stateless_rpc; args.push_options = push_options.nr ? &push_options : NULL; + args.url = dest; if (from_stdin) { if (args.stateless_rpc) { diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d77ce7aeb3..082449293b 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -11,9 +11,9 @@ static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" - " [--current] [--color[=<when>] | --no-color] [--sparse]\n" - " [--more=<n> | --list | --independent | --merge-base]\n" - " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), + " [--current] [--color[=<when>] | --no-color] [--sparse]\n" + " [--more=<n> | --list | --independent | --merge-base]\n" + " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"), NULL }; @@ -482,10 +482,9 @@ static void snarf_refs(int head, int remotes) } } -static int rev_is_head(const char *head, const char *name, - unsigned char *head_sha1, unsigned char *sha1) +static int rev_is_head(const char *head, const char *name) { - if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1))) + if (!head) return 0; skip_prefix(head, "refs/heads/", &head); if (!skip_prefix(name, "refs/heads/", &name)) @@ -806,9 +805,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) /* We are only interested in adding the branch * HEAD points at. */ - if (rev_is_head(head, - ref_name[i], - head_oid.hash, NULL)) + if (rev_is_head(head, ref_name[i])) has_head++; } if (!has_head) { @@ -867,10 +864,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (1 < num_rev || extra < 0) { for (i = 0; i < num_rev; i++) { int j; - int is_head = rev_is_head(head, - ref_name[i], - head_oid.hash, - rev[i]->object.oid.hash); + int is_head = rev_is_head(head, ref_name[i]) && + oideq(&head_oid, &rev[i]->object.oid); if (extra < 0) printf("%c [%s] ", is_head ? '*' : ' ', ref_name[i]); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index a4bdd7c494..d0f5c4702b 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -100,6 +100,98 @@ static int sparse_checkout_list(int argc, const char **argv) return 0; } +static void clean_tracked_sparse_directories(struct repository *r) +{ + int i, was_full = 0; + struct strbuf path = STRBUF_INIT; + size_t pathlen; + struct string_list_item *item; + struct string_list sparse_dirs = STRING_LIST_INIT_DUP; + + /* + * If we are not using cone mode patterns, then we cannot + * delete directories outside of the sparse cone. + */ + if (!r || !r->index || !r->worktree) + return; + if (init_sparse_checkout_patterns(r->index) || + !r->index->sparse_checkout_patterns->use_cone_patterns) + return; + + /* + * Use the sparse index as a data structure to assist finding + * directories that are safe to delete. This conversion to a + * sparse index will not delete directories that contain + * conflicted entries or submodules. + */ + if (!r->index->sparse_index) { + /* + * If something, such as a merge conflict or other concern, + * prevents us from converting to a sparse index, then do + * not try deleting files. + */ + if (convert_to_sparse(r->index, SPARSE_INDEX_MEMORY_ONLY)) + return; + was_full = 1; + } + + strbuf_addstr(&path, r->worktree); + strbuf_complete(&path, '/'); + pathlen = path.len; + + /* + * Collect directories that have gone out of scope but also + * exist on disk, so there is some work to be done. We need to + * store the entries in a list before exploring, since that might + * expand the sparse-index again. + */ + for (i = 0; i < r->index->cache_nr; i++) { + struct cache_entry *ce = r->index->cache[i]; + + if (S_ISSPARSEDIR(ce->ce_mode) && + repo_file_exists(r, ce->name)) + string_list_append(&sparse_dirs, ce->name); + } + + for_each_string_list_item(item, &sparse_dirs) { + struct dir_struct dir = DIR_INIT; + struct pathspec p = { 0 }; + struct strvec s = STRVEC_INIT; + + strbuf_setlen(&path, pathlen); + strbuf_addstr(&path, item->string); + + dir.flags |= DIR_SHOW_IGNORED_TOO; + + setup_standard_excludes(&dir); + strvec_push(&s, path.buf); + + parse_pathspec(&p, PATHSPEC_GLOB, 0, NULL, s.v); + fill_directory(&dir, r->index, &p); + + if (dir.nr) { + warning(_("directory '%s' contains untracked files," + " but is not in the sparse-checkout cone"), + item->string); + } else if (remove_dir_recursively(&path, 0)) { + /* + * Removal is "best effort". If something blocks + * the deletion, then continue with a warning. + */ + warning(_("failed to remove directory '%s'"), + item->string); + } + + dir_clear(&dir); + } + + string_list_clear(&sparse_dirs, 0); + strbuf_release(&path); + + if (was_full) + ensure_full_index(r->index); +} + static int update_working_directory(struct pattern_list *pl) { enum update_sparsity_result result; @@ -141,6 +233,8 @@ static int update_working_directory(struct pattern_list *pl) else rollback_lock_file(&lock_file); + clean_tracked_sparse_directories(r); + r->index->sparse_checkout_patterns = NULL; return result; } @@ -380,10 +474,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat struct pattern_entry *e = xmalloc(sizeof(*e)); e->patternlen = path->len; e->pattern = strbuf_detach(path, NULL); - hashmap_entry_init(&e->ent, - ignore_case ? - strihash(e->pattern) : - strhash(e->pattern)); + hashmap_entry_init(&e->ent, fspathhash(e->pattern)); hashmap_add(&pl->recursive_hashmap, &e->ent); @@ -399,10 +490,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat e = xmalloc(sizeof(struct pattern_entry)); e->patternlen = newlen; e->pattern = xstrndup(oldpattern, newlen); - hashmap_entry_init(&e->ent, - ignore_case ? - strihash(e->pattern) : - strhash(e->pattern)); + hashmap_entry_init(&e->ent, fspathhash(e->pattern)); if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL)) hashmap_add(&pl->parent_hashmap, &e->ent); diff --git a/builtin/stash.c b/builtin/stash.c index 8f42360ca9..a0ccc8654d 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -85,7 +85,7 @@ static const char * const git_stash_push_usage[] = { static const char * const git_stash_save_usage[] = { N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [<message>]"), + " [-u|--include-untracked] [-a|--all] [<message>]"), NULL }; @@ -256,8 +256,10 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) opts.src_index = &the_index; opts.dst_index = &the_index; opts.merge = 1; - opts.reset = reset; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; opts.update = update; + if (update) + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = oneway_merge; if (unpack_trees(nr_trees, t, &opts)) @@ -313,6 +315,17 @@ static int reset_head(void) return run_command(&cp); } +static int is_path_a_directory(const char *path) +{ + /* + * This function differs from abspath.c:is_directory() in that + * here we use lstat() instead of stat(); we do not want to + * follow symbolic links here. + */ + struct stat st; + return (!lstat(path, &st) && S_ISDIR(st.st_mode)); +} + static void add_diff_to_buf(struct diff_queue_struct *q, struct diff_options *options, void *data) @@ -320,6 +333,9 @@ static void add_diff_to_buf(struct diff_queue_struct *q, int i; for (i = 0; i < q->nr; i++) { + if (is_path_a_directory(q->queue[i]->one->path)) + continue; + strbuf_addstr(data, q->queue[i]->one->path); /* NUL-terminate: will be fed to update-index -z */ @@ -521,9 +537,6 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, } } - if (info->has_u && restore_untracked(&info->u_tree)) - return error(_("could not restore untracked files from stash")); - init_merge_options(&o, the_repository); o.branch1 = "Updated upstream"; @@ -558,6 +571,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, unstage_changes_unless_new(&c_tree); } + if (info->has_u && restore_untracked(&info->u_tree)) + return error(_("could not restore untracked files from stash")); + if (!quiet) { struct child_process cp = CHILD_PROCESS_INIT; @@ -1519,6 +1535,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } else { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + /* BUG: this nukes untracked files in the way */ strvec_pushl(&cp.args, "reset", "--hard", "-q", "--no-recurse-submodules", NULL); if (run_command(&cp)) { diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index f73963ad67..6298cbdd4e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -187,44 +187,40 @@ static char *relative_url(const char *remote_url, out = xstrdup(sb.buf + 2); else out = xstrdup(sb.buf); - strbuf_reset(&sb); - if (!up_path || !is_relative) + if (!up_path || !is_relative) { + strbuf_release(&sb); return out; + } + strbuf_reset(&sb); strbuf_addf(&sb, "%s%s", up_path, out); free(out); return strbuf_detach(&sb, NULL); } -static int resolve_relative_url(int argc, const char **argv, const char *prefix) +static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet) { - char *remoteurl = NULL; + char *remoteurl, *resolved_url; char *remote = get_default_remote(); - const char *up_path = NULL; - char *res; - const char *url; - struct strbuf sb = STRBUF_INIT; - - if (argc != 2 && argc != 3) - die("resolve-relative-url only accepts one or two arguments"); - - url = argv[1]; - strbuf_addf(&sb, "remote.%s.url", remote); - free(remote); + struct strbuf remotesb = STRBUF_INIT; - if (git_config_get_string(sb.buf, &remoteurl)) - /* the repository is its own authoritative upstream */ + strbuf_addf(&remotesb, "remote.%s.url", remote); + if (git_config_get_string(remotesb.buf, &remoteurl)) { + if (!quiet) + warning(_("could not look up configuration '%s'. " + "Assuming this repository is its own " + "authoritative upstream."), + remotesb.buf); remoteurl = xgetcwd(); + } + resolved_url = relative_url(remoteurl, rel_url, up_path); - if (argc == 3) - up_path = argv[2]; - - res = relative_url(remoteurl, url, up_path); - puts(res); - free(res); + free(remote); free(remoteurl); - return 0; + strbuf_release(&remotesb); + + return resolved_url; } static int resolve_relative_url_test(int argc, const char **argv, const char *prefix) @@ -311,7 +307,7 @@ struct module_list { const struct cache_entry **entries; int alloc, nr; }; -#define MODULE_LIST_INIT { NULL, 0, 0 } +#define MODULE_LIST_INIT { 0 } static int module_list_compute(int argc, const char **argv, const char *prefix, @@ -588,31 +584,11 @@ static int module_foreach(int argc, const char **argv, const char *prefix) return 0; } -static char *compute_submodule_clone_url(const char *rel_url) -{ - char *remoteurl, *relurl; - char *remote = get_default_remote(); - struct strbuf remotesb = STRBUF_INIT; - - strbuf_addf(&remotesb, "remote.%s.url", remote); - if (git_config_get_string(remotesb.buf, &remoteurl)) { - warning(_("could not look up configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf); - remoteurl = xgetcwd(); - } - relurl = relative_url(remoteurl, rel_url, NULL); - - free(remote); - free(remoteurl); - strbuf_release(&remotesb); - - return relurl; -} - struct init_cb { const char *prefix; unsigned int flags; }; -#define INIT_CB_INIT { NULL, 0 } +#define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, unsigned int flags) @@ -658,7 +634,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 = compute_submodule_clone_url(oldurl); + url = resolve_relative_url(oldurl, NULL, 0); free(oldurl); } @@ -741,7 +717,7 @@ struct status_cb { const char *prefix; unsigned int flags; }; -#define STATUS_CB_INIT { NULL, 0 } +#define STATUS_CB_INIT { 0 } static void print_status(unsigned int flags, char state, const char *path, const struct object_id *oid, const char *displaypath) @@ -935,13 +911,13 @@ struct module_cb { char status; const char *sm_path; }; -#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL } +#define MODULE_CB_INIT { 0 } struct module_cb_list { struct module_cb **entries; int alloc, nr; }; -#define MODULE_CB_LIST_INIT { NULL, 0, 0 } +#define MODULE_CB_LIST_INIT { 0 } struct summary_cb { int argc; @@ -952,7 +928,7 @@ struct summary_cb { unsigned int files: 1; int summary_limit; }; -#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 } +#define SUMMARY_CB_INIT { 0 } enum diff_cmd { DIFF_INDEX, @@ -1358,7 +1334,7 @@ struct sync_cb { const char *prefix; unsigned int flags; }; -#define SYNC_CB_INIT { NULL, 0 } +#define SYNC_CB_INIT { 0 } static void sync_submodule(const char *path, const char *prefix, unsigned int flags) @@ -1378,20 +1354,10 @@ static void sync_submodule(const char *path, const char *prefix, if (sub && sub->url) { if (starts_with_dot_dot_slash(sub->url) || starts_with_dot_slash(sub->url)) { - char *remote_url, *up_path; - char *remote = get_default_remote(); - strbuf_addf(&sb, "remote.%s.url", remote); - - if (git_config_get_string(sb.buf, &remote_url)) - remote_url = xgetcwd(); - - up_path = get_up_path(path); - sub_origin_url = relative_url(remote_url, sub->url, up_path); - super_config_url = relative_url(remote_url, sub->url, NULL); - - free(remote); + 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); - free(remote_url); } else { sub_origin_url = xstrdup(sub->url); super_config_url = xstrdup(sub->url); @@ -1514,7 +1480,7 @@ struct deinit_cb { const char *prefix; unsigned int flags; }; -#define DEINIT_CB_INIT { NULL, 0 } +#define DEINIT_CB_INIT { 0 } static void deinit_submodule(const char *path, const char *prefix, unsigned int flags) @@ -1657,45 +1623,20 @@ static int module_deinit(int argc, const char **argv, const char *prefix) return 0; } -static int clone_submodule(const char *path, const char *gitdir, const char *url, - const char *depth, struct string_list *reference, int dissociate, - int quiet, int progress, int single_branch) -{ - struct child_process cp = CHILD_PROCESS_INIT; - - strvec_push(&cp.args, "clone"); - strvec_push(&cp.args, "--no-checkout"); - if (quiet) - strvec_push(&cp.args, "--quiet"); - if (progress) - strvec_push(&cp.args, "--progress"); - if (depth && *depth) - strvec_pushl(&cp.args, "--depth", depth, NULL); - if (reference->nr) { - struct string_list_item *item; - for_each_string_list_item(item, reference) - strvec_pushl(&cp.args, "--reference", - item->string, NULL); - } - if (dissociate) - strvec_push(&cp.args, "--dissociate"); - if (gitdir && *gitdir) - strvec_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); - if (single_branch >= 0) - strvec_push(&cp.args, single_branch ? - "--single-branch" : - "--no-single-branch"); - - strvec_push(&cp.args, "--"); - strvec_push(&cp.args, url); - strvec_push(&cp.args, path); - - cp.git_cmd = 1; - prepare_submodule_repo_env(&cp.env_array); - cp.no_stdin = 1; - - return run_command(&cp); -} +struct module_clone_data { + const char *prefix; + const char *path; + const char *name; + const char *url; + const char *depth; + struct string_list reference; + unsigned int quiet: 1; + unsigned int progress: 1; + unsigned int dissociate: 1; + unsigned int require_init: 1; + int single_branch; +}; +#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 } struct submodule_alternate_setup { const char *submodule_name; @@ -1706,8 +1647,9 @@ struct submodule_alternate_setup { } error_mode; struct string_list *reference; }; -#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ - SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } +#define SUBMODULE_ALTERNATE_SETUP_INIT { \ + .error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE, \ +} static const char alternate_error_advice[] = N_( "An alternate computed from a superproject's alternate is invalid.\n" @@ -1727,18 +1669,24 @@ static int add_possible_reference_from_superproject( * standard layout with .git/(modules/<name>)+/objects */ if (strip_suffix(odb->path, "/objects", &len)) { + struct repository alternate; char *sm_alternate; struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; strbuf_add(&sb, odb->path, len); + repo_init(&alternate, sb.buf, NULL); + /* * We need to end the new path with '/' to mark it as a dir, * otherwise a submodule name containing '/' will be broken * as the last part of a missing submodule reference would * be taken as a file name. */ - strbuf_addf(&sb, "/modules/%s/", sas->submodule_name); + strbuf_reset(&sb); + submodule_name_to_gitdir(&sb, &alternate, sas->submodule_name); + strbuf_addch(&sb, '/'); + repo_clear(&alternate); sm_alternate = compute_alternate_path(sb.buf, &err); if (sm_alternate) { @@ -1747,7 +1695,7 @@ static int add_possible_reference_from_superproject( } else { switch (sas->error_mode) { case SUBMODULE_ALTERNATE_ERROR_DIE: - if (advice_submodule_alternate_error_strategy_die) + if (advice_enabled(ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE)) advise(_(alternate_error_advice)); die(_("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); @@ -1801,37 +1749,128 @@ static void prepare_possible_alternates(const char *sm_name, free(error_strategy); } -static int module_clone(int argc, const char **argv, const char *prefix) +static int clone_submodule(struct module_clone_data *clone_data) { - const char *name = NULL, *url = NULL, *depth = NULL; - int quiet = 0; - int progress = 0; - char *p, *path = NULL, *sm_gitdir; - struct strbuf sb = STRBUF_INIT; - struct string_list reference = STRING_LIST_INIT_NODUP; - int dissociate = 0, require_init = 0; + char *p, *sm_gitdir; char *sm_alternate = NULL, *error_strategy = NULL; - int single_branch = -1; + struct strbuf sb = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + submodule_name_to_gitdir(&sb, the_repository, clone_data->name); + sm_gitdir = absolute_pathdup(sb.buf); + strbuf_reset(&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); + } + + if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) + die(_("refusing to create/use '%s' in another submodule's " + "git dir"), sm_gitdir); + + if (!file_exists(sm_gitdir)) { + 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); + + strvec_push(&cp.args, "clone"); + strvec_push(&cp.args, "--no-checkout"); + if (clone_data->quiet) + strvec_push(&cp.args, "--quiet"); + if (clone_data->progress) + 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) { + struct string_list_item *item; + for_each_string_list_item(item, &clone_data->reference) + strvec_pushl(&cp.args, "--reference", + item->string, NULL); + } + if (clone_data->dissociate) + strvec_push(&cp.args, "--dissociate"); + if (sm_gitdir && *sm_gitdir) + strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL); + if (clone_data->single_branch >= 0) + strvec_push(&cp.args, clone_data->single_branch ? + "--single-branch" : + "--no-single-branch"); + + strvec_push(&cp.args, "--"); + strvec_push(&cp.args, clone_data->url); + strvec_push(&cp.args, clone_data->path); + + cp.git_cmd = 1; + prepare_submodule_repo_env(&cp.env_array); + cp.no_stdin = 1; + + if(run_command(&cp)) + die(_("clone of '%s' into submodule path '%s' failed"), + 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); + } + + connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0); + + p = git_pathdup_submodule(clone_data->path, "config"); + if (!p) + 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); + if (sm_alternate) + git_config_set_in_file(p, "submodule.alternateLocation", + sm_alternate); + git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); + if (error_strategy) + git_config_set_in_file(p, "submodule.alternateErrorStrategy", + error_strategy); + + free(sm_alternate); + free(error_strategy); + + strbuf_release(&sb); + free(sm_gitdir); + free(p); + return 0; +} + +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 option module_clone_options[] = { - OPT_STRING(0, "prefix", &prefix, + OPT_STRING(0, "prefix", &clone_data.prefix, N_("path"), N_("alternative anchor for relative paths")), - OPT_STRING(0, "path", &path, + OPT_STRING(0, "path", &clone_data.path, N_("path"), N_("where the new submodule will be cloned to")), - OPT_STRING(0, "name", &name, + OPT_STRING(0, "name", &clone_data.name, N_("string"), N_("name of the new submodule")), - OPT_STRING(0, "url", &url, + OPT_STRING(0, "url", &clone_data.url, N_("string"), N_("url where to clone the submodule from")), - OPT_STRING_LIST(0, "reference", &reference, + OPT_STRING_LIST(0, "reference", &clone_data.reference, N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &dissociate, N_("use --reference only while cloning")), - OPT_STRING(0, "depth", &depth, + OPT_STRING(0, "depth", &clone_data.depth, N_("string"), N_("depth for shallow clones")), OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), @@ -1839,7 +1878,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) N_("force cloning progress")), OPT_BOOL(0, "require-init", &require_init, N_("disallow cloning into non-empty directory")), - OPT_BOOL(0, "single-branch", &single_branch, + OPT_BOOL(0, "single-branch", &clone_data.single_branch, N_("clone only one branch, HEAD or --branch")), OPT_END() }; @@ -1855,67 +1894,16 @@ static int module_clone(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); - if (argc || !url || !path || !*path) + clone_data.dissociate = !!dissociate; + clone_data.quiet = !!quiet; + clone_data.progress = !!progress; + clone_data.require_init = !!require_init; + + if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)) usage_with_options(git_submodule_helper_usage, module_clone_options); - strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); - sm_gitdir = absolute_pathdup(sb.buf); - strbuf_reset(&sb); - - if (!is_absolute_path(path)) { - strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path); - path = strbuf_detach(&sb, NULL); - } else - path = xstrdup(path); - - if (validate_submodule_git_dir(sm_gitdir, name) < 0) - die(_("refusing to create/use '%s' in another submodule's " - "git dir"), sm_gitdir); - - if (!file_exists(sm_gitdir)) { - if (safe_create_leading_directories_const(sm_gitdir) < 0) - die(_("could not create directory '%s'"), sm_gitdir); - - prepare_possible_alternates(name, &reference); - - if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate, - quiet, progress, single_branch)) - die(_("clone of '%s' into submodule path '%s' failed"), - url, path); - } else { - if (require_init && !access(path, X_OK) && !is_empty_dir(path)) - die(_("directory not empty: '%s'"), path); - if (safe_create_leading_directories_const(path) < 0) - die(_("could not create directory '%s'"), path); - strbuf_addf(&sb, "%s/index", sm_gitdir); - unlink_or_warn(sb.buf); - strbuf_reset(&sb); - } - - connect_work_tree_and_git_dir(path, sm_gitdir, 0); - - p = git_pathdup_submodule(path, "config"); - if (!p) - die(_("could not get submodule directory for '%s'"), path); - - /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ - git_config_get_string("submodule.alternateLocation", &sm_alternate); - if (sm_alternate) - git_config_set_in_file(p, "submodule.alternateLocation", - sm_alternate); - git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); - if (error_strategy) - git_config_set_in_file(p, "submodule.alternateErrorStrategy", - error_strategy); - - free(sm_alternate); - free(error_strategy); - - strbuf_release(&sb); - free(sm_gitdir); - free(path); - free(p); + clone_submodule(&clone_data); return 0; } @@ -2028,6 +2016,20 @@ struct submodule_update_clone { .max_jobs = 1, \ } +struct update_data { + const char *recursive_prefix; + const char *sm_path; + const char *displaypath; + struct object_id oid; + struct object_id suboid; + struct submodule_update_strategy update_strategy; + int depth; + unsigned int force: 1; + unsigned int quiet: 1; + unsigned int nofetch: 1; + unsigned int just_cloned: 1; +}; +#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT } static void next_submodule_warn_missing(struct submodule_update_clone *suc, struct strbuf *out, const char *displaypath) @@ -2117,7 +2119,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, if (repo_config_get_string_tmp(the_repository, sb.buf, &url)) { if (starts_with_dot_slash(sub->url) || starts_with_dot_dot_slash(sub->url)) { - url = compute_submodule_clone_url(sub->url); + url = resolve_relative_url(sub->url, NULL, 0); need_free_url = 1; } else url = sub->url; @@ -2281,6 +2283,181 @@ static int git_update_clone_config(const char *var, const char *value, return 0; } +static int is_tip_reachable(const char *path, 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.no_stderr = 1; + strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL); + + prepare_submodule_repo_env(&cp.env_array); + + if (capture_command(&cp, &rev, GIT_MAX_HEXSZ + 1) || rev.len) + return 0; + + return 1; +} + +static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + prepare_submodule_repo_env(&cp.env_array); + cp.git_cmd = 1; + cp.dir = xstrdup(module_path); + + strvec_push(&cp.args, "fetch"); + if (quiet) + strvec_push(&cp.args, "--quiet"); + if (depth) + strvec_pushf(&cp.args, "--depth=%d", depth); + if (oid) { + char *hex = oid_to_hex(oid); + char *remote = get_default_remote(); + strvec_pushl(&cp.args, remote, hex, NULL); + } + + return run_command(&cp); +} + +static int run_update_command(struct update_data *ud, int subforce) +{ + struct strvec args = STRVEC_INIT; + struct strvec child_env = STRVEC_INIT; + char *oid = oid_to_hex(&ud->oid); + int must_die_on_failure = 0; + int git_cmd; + + switch (ud->update_strategy.type) { + case SM_UPDATE_CHECKOUT: + git_cmd = 1; + strvec_pushl(&args, "checkout", "-q", NULL); + if (subforce) + strvec_push(&args, "-f"); + break; + case SM_UPDATE_REBASE: + git_cmd = 1; + strvec_push(&args, "rebase"); + if (ud->quiet) + strvec_push(&args, "--quiet"); + must_die_on_failure = 1; + break; + case SM_UPDATE_MERGE: + git_cmd = 1; + strvec_push(&args, "merge"); + if (ud->quiet) + strvec_push(&args, "--quiet"); + must_die_on_failure = 1; + break; + case SM_UPDATE_COMMAND: + git_cmd = 0; + strvec_push(&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)); + } + strvec_push(&args, oid); + + prepare_submodule_repo_env(&child_env); + if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL, + ud->sm_path, child_env.v)) { + switch (ud->update_strategy.type) { + case SM_UPDATE_CHECKOUT: + printf(_("Unable to checkout '%s' in submodule path '%s'"), + oid, ud->displaypath); + break; + case SM_UPDATE_REBASE: + printf(_("Unable to rebase '%s' in submodule path '%s'"), + oid, ud->displaypath); + break; + case SM_UPDATE_MERGE: + printf(_("Unable to merge '%s' in submodule path '%s'"), + oid, ud->displaypath); + break; + case SM_UPDATE_COMMAND: + printf(_("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)); + } + /* + * NEEDSWORK: We are currently printing to stdout with error + * return so that the shell caller handles the error output + * properly. Once we start handling the error messages within + * C, we should use die() instead. + */ + if (must_die_on_failure) + return 2; + /* + * This signifies to the caller in shell that the command + * failed without dying + */ + return 1; + } + + switch (ud->update_strategy.type) { + case SM_UPDATE_CHECKOUT: + printf(_("Submodule path '%s': checked out '%s'\n"), + ud->displaypath, oid); + break; + case SM_UPDATE_REBASE: + printf(_("Submodule path '%s': rebased into '%s'\n"), + ud->displaypath, oid); + break; + case SM_UPDATE_MERGE: + printf(_("Submodule path '%s': merged in '%s'\n"), + ud->displaypath, oid); + break; + case SM_UPDATE_COMMAND: + printf(_("Submodule path '%s': '%s %s'\n"), + ud->displaypath, ud->update_strategy.command, oid); + break; + default: + BUG("unexpected update strategy type: %s", + submodule_strategy_to_string(&ud->update_strategy)); + } + + return 0; +} + +static int do_run_update_procedure(struct update_data *ud) +{ + int subforce = is_null_oid(&ud->suboid) || ud->force; + + if (!ud->nofetch) { + /* + * Run fetch only if `oid` isn't present or it + * is not reachable from a ref. + */ + if (!is_tip_reachable(ud->sm_path, &ud->oid) && + fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, NULL) && + !ud->quiet) + fprintf_ln(stderr, + _("Unable to fetch in submodule path '%s'; " + "trying to directly fetch %s:"), + ud->displaypath, oid_to_hex(&ud->oid)); + /* + * Now we tried the usual fetch, but `oid` may + * not be reachable from any of the refs. + */ + 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 run_update_command(ud, subforce); +} + static void update_submodule(struct update_clone_data *ucd) { fprintf(stdout, "dummy %s %d\t%s\n", @@ -2378,6 +2555,73 @@ static int update_clone(int argc, const char **argv, const char *prefix) return update_submodules(&suc); } +static int run_update_procedure(int argc, const char **argv, const char *prefix) +{ + int force = 0, quiet = 0, nofetch = 0, just_cloned = 0; + char *prefixed_path, *update = NULL; + struct update_data update_data = UPDATE_DATA_INIT; + + struct option options[] = { + OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")), + OPT__FORCE(&force, N_("force checkout updates"), 0), + OPT_BOOL('N', "no-fetch", &nofetch, + N_("don't fetch new objects from the remote site")), + OPT_BOOL(0, "just-cloned", &just_cloned, + N_("overrides update mode in case the repository is a fresh clone")), + OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")), + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_STRING(0, "update", &update, + N_("string"), + N_("rebase, merge, checkout or none")), + OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"), + N_("path into the working tree, across nested " + "submodule boundaries")), + OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"), + N_("SHA1 expected by superproject"), PARSE_OPT_NONEG, + parse_opt_object_id), + OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"), + N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG, + parse_opt_object_id), + OPT_END() + }; + + const char *const usage[] = { + N_("git submodule--helper run-update-procedure [<options>] <path>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 1) + usage_with_options(usage, options); + + update_data.force = !!force; + update_data.quiet = !!quiet; + update_data.nofetch = !!nofetch; + update_data.just_cloned = !!just_cloned; + update_data.sm_path = argv[0]; + + 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, prefix); + + determine_submodule_update_strategy(the_repository, update_data.just_cloned, + update_data.sm_path, update, + &update_data.update_strategy); + + free(prefixed_path); + + if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force) + return do_run_update_procedure(&update_data); + + return 3; +} + static int resolve_relative_path(int argc, const char **argv, const char *prefix) { struct strbuf sb = STRBUF_INIT; @@ -2523,7 +2767,6 @@ static int push_check(int argc, const char **argv, const char *prefix) static int ensure_core_worktree(int argc, const char **argv, const char *prefix) { - const struct submodule *sub; const char *path; const char *cw; struct repository subrepo; @@ -2533,11 +2776,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) path = argv[1]; - sub = submodule_from_path(the_repository, null_oid(), path); - if (!sub) - BUG("We could get the submodule handle before?"); - - if (repo_submodule_init(&subrepo, the_repository, sub)) + if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) die(_("could not get a repository handle for submodule '%s'"), path); if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) { @@ -2744,6 +2983,368 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) return !!ret; } +struct add_data { + const char *prefix; + const char *branch; + const char *reference_path; + char *sm_path; + const char *sm_name; + const char *repo; + const char *realrepo; + int depth; + unsigned int force: 1; + unsigned int quiet: 1; + unsigned int progress: 1; + unsigned int dissociate: 1; +}; +#define ADD_DATA_INIT { .depth = -1 } + +static void show_fetch_remotes(FILE *output, const char *git_dir_path) +{ + struct child_process cp_remote = CHILD_PROCESS_INIT; + struct strbuf sb_remote_out = STRBUF_INIT; + + cp_remote.git_cmd = 1; + strvec_pushf(&cp_remote.env_array, + "GIT_DIR=%s", git_dir_path); + strvec_push(&cp_remote.env_array, "GIT_WORK_TREE=."); + strvec_pushl(&cp_remote.args, "remote", "-v", NULL); + 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)")) + fprintf(output, " %.*s\n", (int)len, line); + line = next_line + 1; + } + } + + strbuf_release(&sb_remote_out); +} + +static int add_submodule(const struct add_data *add_data) +{ + char *submod_gitdir_path; + struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; + + /* perhaps the path already exists and is already a git repo, else clone it */ + if (is_directory(add_data->sm_path)) { + struct strbuf sm_path = STRBUF_INIT; + strbuf_addstr(&sm_path, add_data->sm_path); + submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path); + if (is_nonbare_repository_dir(&sm_path)) + printf(_("Adding existing repo at '%s' to the index\n"), + add_data->sm_path); + else + die(_("'%s' already exists and is not a valid git repo"), + add_data->sm_path); + strbuf_release(&sm_path); + 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)) { + if (!add_data->force) { + fprintf(stderr, _("A git directory for '%s' is found " + "locally with remote(s):"), + add_data->sm_name); + show_fetch_remotes(stderr, submod_gitdir_path); + free(submod_gitdir_path); + die(_("If you want to reuse this local git " + "directory instead of cloning again from\n" + " %s\n" + "use the '--force' option. If the local git " + "directory is not the correct repo\n" + "or if you are unsure what this means, choose " + "another name with the '--name' option.\n"), + add_data->realrepo); + } else { + printf(_("Reactivating local git directory for " + "submodule '%s'\n"), add_data->sm_name); + } + } + free(submod_gitdir_path); + + clone_data.prefix = add_data->prefix; + clone_data.path = add_data->sm_path; + clone_data.name = add_data->sm_name; + 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)); + 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; + + prepare_submodule_repo_env(&cp.env_array); + cp.git_cmd = 1; + cp.dir = add_data->sm_path; + /* + * NOTE: we only get here if add_data->force is true, so + * passing --force to checkout is reasonable. + */ + strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL); + + if (add_data->branch) { + strvec_pushl(&cp.args, "-B", add_data->branch, NULL); + strvec_pushf(&cp.args, "origin/%s", add_data->branch); + } + + if (run_command(&cp)) + die(_("unable to checkout submodule '%s'"), add_data->sm_path); + } + return 0; +} + +static int config_submodule_in_gitmodules(const char *name, const char *var, const char *value) +{ + char *key; + int ret; + + if (!is_writing_gitmodules_ok()) + die(_("please make sure that the .gitmodules file is in the working tree")); + + key = xstrfmt("submodule.%s.%s", name, var); + ret = config_set_in_gitmodules_file_gently(key, value); + free(key); + + return ret; +} + +static void configure_added_submodule(struct add_data *add_data) +{ + char *key; + char *val = NULL; + struct child_process add_submod = CHILD_PROCESS_INIT; + struct child_process add_gitmodules = CHILD_PROCESS_INIT; + + key = xstrfmt("submodule.%s.url", add_data->sm_name); + git_config_set_gently(key, add_data->realrepo); + free(key); + + add_submod.git_cmd = 1; + strvec_pushl(&add_submod.args, "add", + "--no-warn-embedded-repo", NULL); + if (add_data->force) + strvec_push(&add_submod.args, "--force"); + strvec_pushl(&add_submod.args, "--", add_data->sm_path, NULL); + + if (run_command(&add_submod)) + die(_("Failed to add submodule '%s'"), add_data->sm_path); + + if (config_submodule_in_gitmodules(add_data->sm_name, "path", add_data->sm_path) || + config_submodule_in_gitmodules(add_data->sm_name, "url", add_data->repo)) + die(_("Failed to register submodule '%s'"), add_data->sm_path); + + if (add_data->branch) { + if (config_submodule_in_gitmodules(add_data->sm_name, + "branch", add_data->branch)) + die(_("Failed to register submodule '%s'"), add_data->sm_path); + } + + add_gitmodules.git_cmd = 1; + strvec_pushl(&add_gitmodules.args, + "add", "--force", "--", ".gitmodules", NULL); + + if (run_command(&add_gitmodules)) + die(_("Failed to register submodule '%s'"), add_data->sm_path); + + /* + * NEEDSWORK: In a multi-working-tree world this needs to be + * set in the per-worktree config. + */ + /* + * NEEDSWORK: In the longer run, we need to get rid of this + * pattern of querying "submodule.active" before calling + * 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 the submodule being added isn't already covered by the + * current configured pathspec, set the submodule's active flag + */ + if (!is_submodule_active(the_repository, add_data->sm_path)) { + key = xstrfmt("submodule.%s.active", add_data->sm_name); + git_config_set_gently(key, "true"); + free(key); + } + } else { + key = xstrfmt("submodule.%s.active", add_data->sm_name); + git_config_set_gently(key, "true"); + free(key); + } +} + +static void die_on_index_match(const char *path, int force) +{ + struct pathspec ps; + const char *args[] = { path, NULL }; + parse_pathspec(&ps, 0, PATHSPEC_PREFER_CWD, NULL, args); + + if (read_cache_preload(NULL) < 0) + die(_("index file corrupt")); + + if (ps.nr) { + int i; + char *ps_matched = xcalloc(ps.nr, 1); + + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); + + /* + * Since there is only one pathspec, we just need + * need to check ps_matched[0] to know if a cache + * entry matched. + */ + for (i = 0; i < active_nr; i++) { + ce_path_match(&the_index, active_cache[i], &ps, + ps_matched); + + if (ps_matched[0]) { + if (!force) + die(_("'%s' already exists in the index"), + path); + if (!S_ISGITLINK(active_cache[i]->ce_mode)) + die(_("'%s' already exists in the index " + "and is not a submodule"), path); + break; + } + } + free(ps_matched); + } +} + +static void die_on_repo_without_commits(const char *path) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, path); + if (is_nonbare_repository_dir(&sb)) { + struct object_id oid; + if (resolve_gitlink_ref(path, "HEAD", &oid) < 0) + die(_("'%s' does not have a commit checked out"), path); + } +} + +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; + + struct option options[] = { + OPT_STRING('b', "branch", &add_data.branch, N_("branch"), + N_("branch of repository to add as submodule")), + OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"), + PARSE_OPT_NOCOMPLETE), + OPT__QUIET(&quiet, N_("print only error messages")), + OPT_BOOL(0, "progress", &progress, N_("force cloning progress")), + OPT_STRING(0, "reference", &add_data.reference_path, N_("repository"), + N_("reference repository")), + OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")), + OPT_STRING(0, "name", &add_data.sm_name, N_("name"), + N_("sets the submodule’s name to the given string " + "instead of defaulting to its path")), + 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>]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (!is_writing_gitmodules_ok()) + die(_("please make sure that the .gitmodules file is in the working tree")); + + if (prefix && *prefix && + add_data.reference_path && !is_absolute_path(add_data.reference_path)) + add_data.reference_path = xstrfmt("%s%s", prefix, add_data.reference_path); + + if (argc == 0 || argc > 2) + usage_with_options(usage, options); + + add_data.repo = argv[0]; + if (argc == 1) + add_data.sm_path = git_url_basename(add_data.repo, 0, 0); + 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 (starts_with_dot_dot_slash(add_data.repo) || + starts_with_dot_slash(add_data.repo)) { + if (prefix) + die(_("Relative path can only be used from the toplevel " + "of the working tree")); + + /* dereference source url relative to parent's url */ + add_data.realrepo = resolve_relative_url(add_data.repo, NULL, 1); + } else if (is_dir_sep(add_data.repo[0]) || strchr(add_data.repo, ':')) { + add_data.realrepo = add_data.repo; + } else { + die(_("repo URL: '%s' must be absolute or begin with ./|../"), + add_data.repo); + } + + /* + * normalize path: + * multiple //; leading ./; /./; /../; + */ + normalize_path_copy(add_data.sm_path, add_data.sm_path); + strip_dir_trailing_slashes(add_data.sm_path); + + die_on_index_match(add_data.sm_path, force); + 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))) { + strbuf_complete_line(&sb); + fputs(sb.buf, stderr); + free(add_data.sm_path); + return exit_code; + } + strbuf_release(&sb); + } + + if(!add_data.sm_name) + add_data.sm_name = add_data.sm_path; + + if (check_submodule_name(add_data.sm_name)) + die(_("'%s' is not a valid submodule name"), add_data.sm_name); + + add_data.prefix = prefix; + add_data.force = !!force; + add_data.quiet = !!quiet; + add_data.progress = !!progress; + add_data.dissociate = !!dissociate; + + if (add_submodule(&add_data)) { + free(add_data.sm_path); + return 1; + } + configure_added_submodule(&add_data); + free(add_data.sm_path); + + return 0; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -2756,11 +3357,12 @@ 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-mode", module_update_module_mode, 0}, {"update-clone", update_clone, 0}, + {"run-update-procedure", run_update_procedure, 0}, {"ensure-core-worktree", ensure_core_worktree, 0}, {"relative-path", resolve_relative_path, 0}, - {"resolve-relative-url", resolve_relative_url, 0}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, {"init", module_init, SUPPORT_SUPER_PREFIX}, diff --git a/builtin/tag.c b/builtin/tag.c index 82fcfc0982..6535ed27ee 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -23,10 +23,10 @@ static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n" - "\t\t<tagname> [<head>]"), + " <tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n" - "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"), + " [--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"), N_("git tag -v [--format=<format>] <tagname>..."), NULL }; @@ -146,7 +146,7 @@ static int verify_tag(const char *name, const char *ref, const struct object_id *oid, void *cb_data) { int flags; - const struct ref_format *format = cb_data; + struct ref_format *format = cb_data; flags = GPG_VERIFY_VERBOSE; if (format->format) @@ -293,9 +293,7 @@ static void create_tag(const struct object_id *object, const char *object_ref, /* write the template message before editing: */ path = git_pathdup("TAG_EDITMSG"); - fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); - if (fd < 0) - die_errno(_("could not create file '%s'"), path); + fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (opt->message_given) { write_or_die(fd, buf->buf, buf->len); diff --git a/builtin/update-index.c b/builtin/update-index.c index f1f16f2de5..187203e8bb 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -95,9 +95,7 @@ static int create_file(const char *path) { int fd; path = get_mtime_path(path); - fd = open(path, O_CREAT | O_RDWR, 0644); - if (fd < 0) - die_errno(_("failed to create file %s"), path); + fd = xopen(path, O_CREAT | O_RDWR, 0644); return fd; } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 6029a80544..a84e7b47a2 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -302,6 +302,12 @@ static void parse_cmd_verify(struct ref_transaction *transaction, strbuf_release(&err); } +static void report_ok(const char *command) +{ + fprintf(stdout, "%s: ok\n", command); + fflush(stdout); +} + static void parse_cmd_option(struct ref_transaction *transaction, const char *next, const char *end) { @@ -317,7 +323,7 @@ static void parse_cmd_start(struct ref_transaction *transaction, { if (*next != line_termination) die("start: extra input: %s", next); - puts("start: ok"); + report_ok("start"); } static void parse_cmd_prepare(struct ref_transaction *transaction, @@ -328,7 +334,7 @@ static void parse_cmd_prepare(struct ref_transaction *transaction, die("prepare: extra input: %s", next); if (ref_transaction_prepare(transaction, &error)) die("prepare: %s", error.buf); - puts("prepare: ok"); + report_ok("prepare"); } static void parse_cmd_abort(struct ref_transaction *transaction, @@ -339,7 +345,7 @@ static void parse_cmd_abort(struct ref_transaction *transaction, die("abort: extra input: %s", next); if (ref_transaction_abort(transaction, &error)) die("abort: %s", error.buf); - puts("abort: ok"); + report_ok("abort"); } static void parse_cmd_commit(struct ref_transaction *transaction, @@ -350,7 +356,7 @@ static void parse_cmd_commit(struct ref_transaction *transaction, die("commit: extra input: %s", next); if (ref_transaction_commit(transaction, &error)) die("commit: %s", error.buf); - puts("commit: ok"); + report_ok("commit"); ref_transaction_free(transaction); } diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c index 6da8fa2607..125af53885 100644 --- a/builtin/upload-pack.c +++ b/builtin/upload-pack.c @@ -16,16 +16,18 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix) { const char *dir; int strict = 0; - struct upload_pack_options opts = { 0 }; - struct serve_options serve_opts = SERVE_OPTIONS_INIT; + int advertise_refs = 0; + int stateless_rpc = 0; + int timeout = 0; struct option options[] = { - OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc, + OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("quit after a single request/response exchange")), - OPT_BOOL(0, "advertise-refs", &opts.advertise_refs, - N_("exit immediately after initial ref advertisement")), + OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs, + N_("serve up the info/refs for git-http-backend")), + OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"), OPT_BOOL(0, "strict", &strict, N_("do not try <directory>/.git/ if <directory> is no Git directory")), - OPT_INTEGER(0, "timeout", &opts.timeout, + OPT_INTEGER(0, "timeout", &timeout, N_("interrupt transfer after <n> seconds of inactivity")), OPT_END() }; @@ -38,9 +40,6 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix) if (argc != 1) usage_with_options(upload_pack_usage, options); - if (opts.timeout) - opts.daemon_mode = 1; - setup_path(); dir = argv[0]; @@ -50,21 +49,22 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix) switch (determine_protocol_version_server()) { case protocol_v2: - serve_opts.advertise_capabilities = opts.advertise_refs; - serve_opts.stateless_rpc = opts.stateless_rpc; - serve(&serve_opts); + if (advertise_refs) + protocol_v2_advertise_capabilities(); + else + protocol_v2_serve_loop(stateless_rpc); break; case protocol_v1: /* * v1 is just the original protocol with a version string, * so just fall through after writing the version string. */ - if (opts.advertise_refs || !opts.stateless_rpc) + if (advertise_refs || !stateless_rpc) packet_write_fmt(1, "version 1\n"); /* fallthrough */ case protocol_v0: - upload_pack(&opts); + upload_pack(advertise_refs, stateless_rpc, timeout); break; case protocol_unknown_version: BUG("unknown protocol version"); diff --git a/builtin/worktree.c b/builtin/worktree.c index 976bf8ed06..d22ece93e1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -8,6 +8,7 @@ #include "branch.h" #include "refs.h" #include "run-command.h" +#include "hook.h" #include "sigchain.h" #include "submodule.h" #include "utf8.h" @@ -30,7 +31,7 @@ struct add_opts { int detach; int quiet; int checkout; - int keep_locked; + const char *keep_locked; }; static int show_only; @@ -302,10 +303,10 @@ static int add_worktree(const char *path, const char *refname, * after the preparation is over. */ strbuf_addf(&sb, "%s/locked", sb_repo.buf); - if (!opts->keep_locked) - write_file(sb.buf, "initializing"); + if (opts->keep_locked) + write_file(sb.buf, "%s", opts->keep_locked); else - write_file(sb.buf, "added with --lock"); + write_file(sb.buf, _("initializing")); strbuf_addf(&sb_git, "%s/.git", path); if (safe_create_leading_directories_const(sb_git.buf)) @@ -475,6 +476,8 @@ static int add(int ac, const char **av, const char *prefix) const char *branch; const char *new_branch = NULL; const char *opt_track = NULL; + const char *lock_reason = NULL; + int keep_locked = 0; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree"), @@ -485,7 +488,9 @@ static int add(int ac, const char **av, const char *prefix) N_("create or reset a branch")), OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), - OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")), + OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")), + OPT_STRING(0, "reason", &lock_reason, N_("string"), + N_("reason for locking")), OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), OPT_PASSTHRU(0, "track", &opt_track, NULL, N_("set up tracking mode (see git-branch(1))"), @@ -500,6 +505,13 @@ static int add(int ac, const char **av, const char *prefix) ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (!!opts.detach + !!new_branch + !!new_branch_force > 1) die(_("-b, -B, and --detach are mutually exclusive")); + if (lock_reason && !keep_locked) + die(_("--reason requires --lock")); + if (lock_reason) + opts.keep_locked = lock_reason; + else if (keep_locked) + opts.keep_locked = _("added with --lock"); + if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); |