diff --git a/Documentation/RelNotes/2.53.0.adoc b/Documentation/RelNotes/2.53.0.adoc index 34216a59fe5fe6..e94a79516c2138 100644 --- a/Documentation/RelNotes/2.53.0.adoc +++ b/Documentation/RelNotes/2.53.0.adoc @@ -188,3 +188,6 @@ Fixes since v2.52 (merge 8cbbdc92f7 kh/doc-pre-commit-fix later to maint). (merge d4bc39a4d9 mh/doc-config-gui-gcwarning later to maint). (merge 41d425008a kh/doc-send-email-paragraph-fix later to maint). + (merge d4b732899e jc/macports-darwinports later to maint). + (merge bab391761d kj/pull-options-decl-cleanup later to maint). + (merge 007b8994d4 rs/t4014-git-version-string-fix later to maint). diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc index d03235cca0c668..4c61f3aa1f1c70 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc @@ -19,7 +19,7 @@ the working tree and the index untouched. By default, updates the relevant references using an atomic transaction (all refs update or none). Use `--ref-action=print` to avoid automatic ref updates and instead get update commands that can be piped to `git update-ref --stdin` -(see the OUTPUT section below). +(see the <> section below). THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. @@ -42,6 +42,10 @@ The history is replayed on top of the and is updated to point at the tip of the resulting history. This is different from `--onto`, which uses the target only as a starting point without updating it. +--contained:: + Update all branches that point at commits in + . Requires `--onto`. + --ref-action[=]:: Control how references are updated. The mode can be: + @@ -62,6 +66,7 @@ The default mode can be configured via the `replay.refAction` configuration vari include::rev-list-options.adoc[] +[[output]] OUTPUT ------ @@ -80,6 +85,10 @@ the shape of the history being replayed. When using `--advance`, the number of refs updated is always one, but for `--onto`, it can be one or more (rebasing multiple branches simultaneously is supported). +There is no stderr output on conflicts; see the <> section below. + +[[exit-status]] EXIT STATUS ----------- diff --git a/Makefile b/Makefile index cf3f4b585fd494..809fd6b29a6b2b 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,8 @@ include shared.mak # and LDFLAGS appropriately. # # Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, -# have DarwinPorts installed in /opt/local, but don't want GIT to +# have DarwinPorts (which is an old name for MacPorts) installed +# in /opt/local, but don't want GIT to # link against any libraries installed there. If defined you may # specify your own (or DarwinPort's) include directories and # library directories by defining CFLAGS and LDFLAGS appropriately. diff --git a/builtin/pull.c b/builtin/pull.c index 5ebd5296207061..3ff748e0b3ea60 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -119,148 +119,6 @@ static int opt_show_forced_updates = -1; static const char *set_upstream; static struct strvec opt_fetch = STRVEC_INIT; -static struct option pull_options[] = { - /* Shared options */ - OPT__VERBOSITY(&opt_verbosity), - OPT_PASSTHRU(0, "progress", &opt_progress, NULL, - N_("force progress reporting"), - PARSE_OPT_NOARG), - OPT_CALLBACK_F(0, "recurse-submodules", - &recurse_submodules_cli, N_("on-demand"), - N_("control for recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), - - /* 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|interactive)", - N_("incorporate changes by rebasing rather than merging"), - PARSE_OPT_OPTARG, parse_opt_rebase), - OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, - N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG), - OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, - N_("show a diffstat at the end of the merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, - N_("(synonym to --stat)"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), - OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL, - N_("show a compact-summary at the end of the merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "log", &opt_log, N_("n"), - N_("add (at most ) entries from shortlog to merge commit message"), - PARSE_OPT_OPTARG), - OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, - N_("add a Signed-off-by trailer"), - PARSE_OPT_OPTARG), - OPT_PASSTHRU(0, "squash", &opt_squash, NULL, - N_("create a single commit instead of doing a merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "commit", &opt_commit, NULL, - N_("perform a commit if the merge succeeds (default)"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "edit", &opt_edit, NULL, - N_("edit message before committing"), - PARSE_OPT_NOARG), - OPT_CLEANUP(&cleanup_arg), - OPT_PASSTHRU(0, "ff", &opt_ff, NULL, - N_("allow fast-forward"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, - N_("abort if fast-forward is not possible"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG), - OPT_PASSTHRU(0, "verify", &opt_verify, NULL, - N_("control use of pre-merge-commit and commit-msg hooks"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, - N_("verify that the named commit has a valid GPG signature"), - PARSE_OPT_NOARG), - OPT_BOOL(0, "autostash", &opt_autostash, - N_("automatically stash/stash pop before and after")), - OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), - N_("merge strategy to use"), - 0), - OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, - N_("option=value"), - N_("option for selected merge strategy"), - 0), - OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), - N_("GPG sign commit"), - PARSE_OPT_OPTARG), - OPT_SET_INT(0, "allow-unrelated-histories", - &opt_allow_unrelated_histories, - N_("allow merging unrelated histories"), 1), - - /* Options passed to git-fetch */ - OPT_GROUP(N_("Options related to fetching")), - OPT_PASSTHRU(0, "all", &opt_all, NULL, - N_("fetch from all remotes"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('a', "append", &opt_append, NULL, - N_("append to .git/FETCH_HEAD instead of overwriting"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), - N_("path to upload pack on remote end"), - 0), - OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), - OPT_PASSTHRU('t', "tags", &opt_tags, NULL, - N_("fetch all tags and associated objects"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('p', "prune", &opt_prune, NULL, - N_("prune remote-tracking branches no longer on remote"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), - N_("number of submodules pulled in parallel"), - PARSE_OPT_OPTARG), - OPT_BOOL(0, "dry-run", &opt_dry_run, - N_("dry run")), - OPT_PASSTHRU('k', "keep", &opt_keep, NULL, - N_("keep downloaded pack"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), - N_("deepen history of shallow clone"), - 0), - OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), - N_("deepen history of shallow repository based on time"), - 0), - OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"), - N_("deepen history of shallow clone, excluding ref"), - 0), - OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), - N_("deepen history of shallow clone"), - 0), - OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, - N_("convert to a complete repository"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, - N_("accept refs that update .git/shallow"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), - N_("specify fetch refmap"), - PARSE_OPT_NONEG), - OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, - N_("server-specific"), - N_("option to transmit"), - 0), - OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, - N_("use IPv4 addresses only"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, - N_("use IPv6 addresses only"), - PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), - N_("report that we have only objects reachable from this object"), - 0), - OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, - N_("check for forced-updates on all updated branches")), - OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, - N_("set upstream for git pull/fetch"), - PARSE_OPT_NOARG), - - OPT_END() -}; - /** * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level. */ @@ -1008,6 +866,147 @@ int cmd_pull(int argc, int can_ff; int divergent; int ret; + static struct option pull_options[] = { + /* Shared options */ + OPT__VERBOSITY(&opt_verbosity), + OPT_PASSTHRU(0, "progress", &opt_progress, NULL, + N_("force progress reporting"), + PARSE_OPT_NOARG), + OPT_CALLBACK_F(0, "recurse-submodules", + &recurse_submodules_cli, N_("on-demand"), + N_("control for recursive fetching of submodules"), + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), + + /* 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|interactive)", + N_("incorporate changes by rebasing rather than merging"), + PARSE_OPT_OPTARG, parse_opt_rebase), + OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, + N_("do not show a diffstat at the end of the merge"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, + N_("show a diffstat at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, + N_("(synonym to --stat)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), + OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL, + N_("show a compact-summary at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "log", &opt_log, N_("n"), + N_("add (at most ) entries from shortlog to merge commit message"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, + N_("add a Signed-off-by trailer"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "squash", &opt_squash, NULL, + N_("create a single commit instead of doing a merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "commit", &opt_commit, NULL, + N_("perform a commit if the merge succeeds (default)"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "edit", &opt_edit, NULL, + N_("edit message before committing"), + PARSE_OPT_NOARG), + OPT_CLEANUP(&cleanup_arg), + OPT_PASSTHRU(0, "ff", &opt_ff, NULL, + N_("allow fast-forward"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "verify", &opt_verify, NULL, + N_("control use of pre-merge-commit and commit-msg hooks"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, + N_("verify that the named commit has a valid GPG signature"), + PARSE_OPT_NOARG), + OPT_BOOL(0, "autostash", &opt_autostash, + N_("automatically stash/stash pop before and after")), + OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), + N_("merge strategy to use"), + 0), + OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, + N_("option=value"), + N_("option for selected merge strategy"), + 0), + OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), + N_("GPG sign commit"), + PARSE_OPT_OPTARG), + OPT_SET_INT(0, "allow-unrelated-histories", + &opt_allow_unrelated_histories, + N_("allow merging unrelated histories"), 1), + + /* Options passed to git-fetch */ + OPT_GROUP(N_("Options related to fetching")), + OPT_PASSTHRU(0, "all", &opt_all, NULL, + N_("fetch from all remotes"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('a', "append", &opt_append, NULL, + N_("append to .git/FETCH_HEAD instead of overwriting"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), + N_("path to upload pack on remote end"), + 0), + OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), + OPT_PASSTHRU('t', "tags", &opt_tags, NULL, + N_("fetch all tags and associated objects"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('p', "prune", &opt_prune, NULL, + N_("prune remote-tracking branches no longer on remote"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), + N_("number of submodules pulled in parallel"), + PARSE_OPT_OPTARG), + OPT_BOOL(0, "dry-run", &opt_dry_run, + N_("dry run")), + OPT_PASSTHRU('k', "keep", &opt_keep, NULL, + N_("keep downloaded pack"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), + N_("deepen history of shallow repository based on time"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"), + N_("deepen history of shallow clone, excluding ref"), + 0), + OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, + N_("convert to a complete repository"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, + N_("accept refs that update .git/shallow"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), + N_("specify fetch refmap"), + PARSE_OPT_NONEG), + OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, + N_("server-specific"), + N_("option to transmit"), + 0), + OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, + N_("use IPv4 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, + N_("use IPv6 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + N_("report that we have only objects reachable from this object"), + 0), + OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, + N_("check for forced-updates on all updated branches")), + OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, + N_("set upstream for git pull/fetch"), + PARSE_OPT_NOARG), + + OPT_END() + }; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); diff --git a/builtin/replay.c b/builtin/replay.c index 507b909df7dc40..69c4c551297c03 100644 --- a/builtin/replay.c +++ b/builtin/replay.c @@ -377,7 +377,7 @@ int cmd_replay(int argc, N_("revision"), N_("replay onto given commit")), OPT_BOOL(0, "contained", &contained, - N_("advance all branches contained in revision-range")), + N_("update all branches that point at commits in ")), OPT_STRING(0, "ref-action", &ref_action, N_("mode"), N_("control ref update behavior (update|print)")), @@ -454,6 +454,9 @@ int cmd_replay(int argc, determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name, &onto, &update_refs); + if (!onto) /* FIXME: Should handle replaying down to root commit */ + die("Replaying down to root commit is not supported yet!"); + /* Build reflog message */ if (advance_name_opt) strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt); @@ -472,9 +475,6 @@ int cmd_replay(int argc, } } - if (!onto) /* FIXME: Should handle replaying down to root commit */ - die("Replaying down to root commit is not supported yet!"); - if (prepare_revision_walk(&revs) < 0) { ret = error(_("error preparing revisions")); goto cleanup; diff --git a/odb.c b/odb.c index af1317442564b5..7b5da2de32190c 100644 --- a/odb.c +++ b/odb.c @@ -89,17 +89,20 @@ int odb_mkstemp(struct object_database *odb, /* * Return non-zero iff the path is usable as an alternate object database. */ -static int alt_odb_usable(struct object_database *o, const char *path, - const char *normalized_objdir) +static bool odb_is_source_usable(struct object_database *o, const char *path) { int r; + struct strbuf normalized_objdir = STRBUF_INIT; + bool usable = false; + + strbuf_realpath(&normalized_objdir, o->sources->path, 1); /* Detect cases where alternate disappeared */ if (!is_directory(path)) { error(_("object directory %s does not exist; " "check .git/objects/info/alternates"), path); - return 0; + goto out; } /* @@ -116,33 +119,104 @@ static int alt_odb_usable(struct object_database *o, const char *path, kh_value(o->source_by_path, p) = o->sources; } - if (fspatheq(path, normalized_objdir)) - return 0; + if (fspatheq(path, normalized_objdir.buf)) + goto out; if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path)) - return 0; + goto out; + + usable = true; - return 1; +out: + strbuf_release(&normalized_objdir); + return usable; +} + +static void parse_alternates(const char *string, + int sep, + const char *relative_base, + struct strvec *out) +{ + struct strbuf pathbuf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + + if (!string || !*string) + return; + + while (*string) { + const char *end; + + strbuf_reset(&buf); + strbuf_reset(&pathbuf); + + if (*string == '#') { + /* comment; consume up to next separator */ + end = strchrnul(string, sep); + } else if (*string == '"' && !unquote_c_style(&buf, string, &end)) { + /* + * quoted path; unquote_c_style has copied the + * data for us and set "end". Broken quoting (e.g., + * an entry that doesn't end with a quote) falls + * back to the unquoted case below. + */ + } else { + /* normal, unquoted path */ + end = strchrnul(string, sep); + strbuf_add(&buf, string, end - string); + } + + if (*end) + end++; + string = end; + + if (!buf.len) + continue; + + if (!is_absolute_path(buf.buf) && relative_base) { + strbuf_realpath(&pathbuf, relative_base, 1); + strbuf_addch(&pathbuf, '/'); + } + strbuf_addbuf(&pathbuf, &buf); + + strbuf_reset(&buf); + if (!strbuf_realpath(&buf, pathbuf.buf, 0)) { + error(_("unable to normalize alternate object path: %s"), + pathbuf.buf); + continue; + } + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (buf.len && buf.buf[buf.len - 1] == '/') + strbuf_setlen(&buf, buf.len - 1); + + strvec_push(out, buf.buf); + } + + strbuf_release(&pathbuf); + strbuf_release(&buf); +} + +static void odb_source_read_alternates(struct odb_source *source, + struct strvec *out) +{ + struct strbuf buf = STRBUF_INIT; + char *path; + + path = xstrfmt("%s/info/alternates", source->path); + if (strbuf_read_file(&buf, path, 1024) < 0) { + warn_on_fopen_errors(path); + free(path); + return; + } + parse_alternates(buf.buf, '\n', source->path, out); + + strbuf_release(&buf); + free(path); } -/* - * Prepare alternate object database registry. - * - * The variable alt_odb_list points at the list of struct - * odb_source. The elements on this list come from - * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT - * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates, - * whose contents is similar to that environment variable but can be - * LF separated. Its base points at a statically allocated buffer that - * contains "/the/directory/corresponding/to/.git/objects/...", while - * its name points just after the slash at the end of ".git/objects/" - * in the example above, and has enough space to hold all hex characters - * of the object ID, an extra slash for the first level indirection, and - * the terminating NUL. - */ -static void read_info_alternates(struct object_database *odb, - const char *relative_base, - int depth); static struct odb_source *odb_source_new(struct object_database *odb, const char *path, @@ -159,44 +233,19 @@ static struct odb_source *odb_source_new(struct object_database *odb, return source; } -static struct odb_source *link_alt_odb_entry(struct object_database *odb, - const char *dir, - const char *relative_base, - int depth) +static struct odb_source *odb_add_alternate_recursively(struct object_database *odb, + const char *source, + int depth) { struct odb_source *alternate = NULL; - struct strbuf pathbuf = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; + struct strvec sources = STRVEC_INIT; khiter_t pos; int ret; - if (!is_absolute_path(dir) && relative_base) { - strbuf_realpath(&pathbuf, relative_base, 1); - strbuf_addch(&pathbuf, '/'); - } - strbuf_addstr(&pathbuf, dir); - - if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) { - error(_("unable to normalize alternate object path: %s"), - pathbuf.buf); + if (!odb_is_source_usable(odb, source)) goto error; - } - strbuf_swap(&pathbuf, &tmp); - /* - * The trailing slash after the directory name is given by - * this function at the end. Remove duplicates. - */ - while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') - strbuf_setlen(&pathbuf, pathbuf.len - 1); - - strbuf_reset(&tmp); - strbuf_realpath(&tmp, odb->sources->path, 1); - - if (!alt_odb_usable(odb, pathbuf.buf, tmp.buf)) - goto error; - - alternate = odb_source_new(odb, pathbuf.buf, false); + alternate = odb_source_new(odb, source, false); /* add the alternate entry */ *odb->sources_tail = alternate; @@ -208,104 +257,42 @@ static struct odb_source *link_alt_odb_entry(struct object_database *odb, kh_value(odb->source_by_path, pos) = alternate; /* recursively add alternates */ - read_info_alternates(odb, alternate->path, depth + 1); - - error: - strbuf_release(&tmp); - strbuf_release(&pathbuf); - return alternate; -} - -static const char *parse_alt_odb_entry(const char *string, - int sep, - struct strbuf *out) -{ - const char *end; - - strbuf_reset(out); - - if (*string == '#') { - /* comment; consume up to next separator */ - end = strchrnul(string, sep); - } else if (*string == '"' && !unquote_c_style(out, string, &end)) { - /* - * quoted path; unquote_c_style has copied the - * data for us and set "end". Broken quoting (e.g., - * an entry that doesn't end with a quote) falls - * back to the unquoted case below. - */ - } else { - /* normal, unquoted path */ - end = strchrnul(string, sep); - strbuf_add(out, string, end - string); - } - - if (*end) - end++; - return end; -} - -static void link_alt_odb_entries(struct object_database *odb, const char *alt, - int sep, const char *relative_base, int depth) -{ - struct strbuf dir = STRBUF_INIT; - - if (!alt || !*alt) - return; - - if (depth > 5) { + odb_source_read_alternates(alternate, &sources); + if (sources.nr && depth + 1 > 5) { error(_("%s: ignoring alternate object stores, nesting too deep"), - relative_base); - return; - } - - while (*alt) { - alt = parse_alt_odb_entry(alt, sep, &dir); - if (!dir.len) - continue; - link_alt_odb_entry(odb, dir.buf, relative_base, depth); - } - strbuf_release(&dir); -} - -static void read_info_alternates(struct object_database *odb, - const char *relative_base, - int depth) -{ - char *path; - struct strbuf buf = STRBUF_INIT; - - path = xstrfmt("%s/info/alternates", relative_base); - if (strbuf_read_file(&buf, path, 1024) < 0) { - warn_on_fopen_errors(path); - free(path); - return; + source); + } else { + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], depth + 1); } - link_alt_odb_entries(odb, buf.buf, '\n', relative_base, depth); - strbuf_release(&buf); - free(path); + error: + strvec_clear(&sources); + return alternate; } -void odb_add_to_alternates_file(struct object_database *odb, - const char *dir) +static int odb_source_write_alternate(struct odb_source *source, + const char *alternate) { struct lock_file lock = LOCK_INIT; - char *alts = repo_git_path(odb->repo, "objects/info/alternates"); + char *path = xstrfmt("%s/%s", source->path, "info/alternates"); FILE *in, *out; int found = 0; + int ret; - hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR); + hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR); out = fdopen_lock_file(&lock, "w"); - if (!out) - die_errno(_("unable to fdopen alternates lockfile")); + if (!out) { + ret = error_errno(_("unable to fdopen alternates lockfile")); + goto out; + } - in = fopen(alts, "r"); + in = fopen(path, "r"); if (in) { struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in) != EOF) { - if (!strcmp(dir, line.buf)) { + if (!strcmp(alternate, line.buf)) { found = 1; break; } @@ -314,20 +301,36 @@ void odb_add_to_alternates_file(struct object_database *odb, strbuf_release(&line); fclose(in); + } else if (errno != ENOENT) { + ret = error_errno(_("unable to read alternates file")); + goto out; } - else if (errno != ENOENT) - die_errno(_("unable to read alternates file")); if (found) { rollback_lock_file(&lock); } else { - fprintf_or_die(out, "%s\n", dir); - if (commit_lock_file(&lock)) - die_errno(_("unable to move new alternates file into place")); - if (odb->loaded_alternates) - link_alt_odb_entries(odb, dir, '\n', NULL, 0); + fprintf_or_die(out, "%s\n", alternate); + if (commit_lock_file(&lock)) { + ret = error_errno(_("unable to move new alternates file into place")); + goto out; + } } - free(alts); + + ret = 0; + +out: + free(path); + return ret; +} + +void odb_add_to_alternates_file(struct object_database *odb, + const char *dir) +{ + int ret = odb_source_write_alternate(odb->sources, dir); + if (ret < 0) + die(NULL); + if (odb->loaded_alternates) + odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, @@ -338,7 +341,7 @@ struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, * overwritten when they are. */ odb_prepare_alternates(odb); - return link_alt_odb_entry(odb, dir, NULL, 0); + return odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, @@ -609,13 +612,19 @@ int odb_for_each_alternate(struct object_database *odb, void odb_prepare_alternates(struct object_database *odb) { + struct strvec sources = STRVEC_INIT; + if (odb->loaded_alternates) return; - link_alt_odb_entries(odb, odb->alternate_db, PATH_SEP, NULL, 0); + parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources); + odb_source_read_alternates(odb->sources, &sources); + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], 0); - read_info_alternates(odb, odb->sources->path, 0); odb->loaded_alternates = 1; + + strvec_clear(&sources); } int odb_has_alternates(struct object_database *odb) diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 2782b1fc183e8f..21d6d0cd9ef679 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -980,7 +980,7 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' ' test_expect_success 'get git version' ' git_version=$(git --version) && - git_version=${git_version##* } + git_version=${git_version#git version } ' signature() {