diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc index 811f74bc7dbcb6..6cee8bb8e89134 100644 --- a/Documentation/RelNotes/2.56.0.adoc +++ b/Documentation/RelNotes/2.56.0.adoc @@ -56,6 +56,26 @@ UI, Workflows & Features standardized to make them consistent with each other and with other commands. + * 'git log --graph' has been modified to visually distinguish parentless + 'root' commits (and commits that become roots due to history + simplification) by indenting them, preventing them from appearing + falsely related to unrelated commits rendered immediately above them. + + * Userdiff patterns for Swift have been added, with support for + Swift-specific constructs such as attributes, modifiers, failable + initializers, and generics. + + * Configuration file locking has been updated to retry for a short + period, avoiding failures when multiple processes attempt to update + the configuration simultaneously. + + * The 'remote-object-info' command has been added to 'git cat-file + --batch-command', allowing clients to request object metadata + (currently size) from a remote server via protocol v2 without + downloading the entire object. Format placeholders are dynamically + filtered on the client based on server-advertised capabilities, + returning empty strings for inapplicable or unsupported fields. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -206,6 +226,60 @@ Performance, Internal Implementation, Development Support etc. pathspec filtering, which was lost when the streaming walk was refactored. + * The ref subsystem and the worktree API have been refactored to pass a + repository pointer down the call chain, allowing them to drop + references to the global 'the_repository' variable. As part of this, + the handling of the 'core.packedRefsTimeout' configuration has been + moved into the per-repository ref store structure. + + * 'git branch --contains' and 'git for-each-ref --contains' have been + optimized to use the memoized commit traversal previously used only by + 'git tag --contains', significantly speeding up connectivity checks + across many candidate refs with shared history. + + * The passing of push destination specifications in the 'remote-curl' + helper has been simplified by removing the explicit 'count' parameter + and relying on the NULL-termination of the array. + + * The dependency on the global 'the_repository' variable in the + 'refspec.c' API has been removed by passing the hash algorithm + explicitly to refspec-parsing functions and storing it in 'struct + refspec'. + + * The enumeration of untracked and ignored files in 'git status' has + been optimized by avoiding quadratic complexity when inserting into + string lists, reducing the construction cost from O(n^2) to O(n log + n). + + * The copy_file() and copy_file_with_time() functions have been + refactored to take a repository parameter, allowing the removal of the + implicit dependency on the global 'the_repository' variable in + 'copy.c'. + + * The tempfile and lockfile APIs have been refactored to stop depending + on the 'the_repository' global variable, and their callers have been + updated to use the repository-aware variants. + + * The 'trust_executable_bit' (coming from the 'core.filemode' + configuration) has been migrated into 'struct repo_config_values' to + tie it to a specific repository instance. + + * The 'excludes_file' and various other global configuration variables + (including 'editor_program', 'pager_program', 'askpass_program', and + 'push_default') have been migrated into the per-repository structure. + + * The 'git stash push' command has been optimized to avoid unnecessary + sparse index expansion when pathspecs are wholly inside the + sparse-checkout cone. Also, a potential out-of-bounds read in the + sparse-index expansion check helper pathspec_needs_expanded_index() + has been fixed by consistently using the parsed, prefixed path. + + * The logic to write loose objects has been refactored and moved from + 'object-file.c' to the loose backend source file 'odb/source-loose.c', + making the loose backend more self-contained. This is achieved by + first refactoring force_object_loose() to use generic ODB write + interfaces instead of loose-backend internals. + Fixes since v2.55 ----------------- @@ -350,3 +424,24 @@ Fixes since v2.55 'git submodule update' command until it was broken in a modernization of the option-parsing code, has been restored. (merge ff1da37f58 dm/submodule-update-i-shorthand later to maint). + + * An accidental use of the '%zu' format specifier in 'git + submodule--helper' has been corrected to use 'PRIuMAX' and cast the + value to 'uintmax_t' to avoid portability issues. + (merge 3279c13c00 jc/submodule-helper-avoid-zu later to maint). + + * The rebase post-rewrite notes-copying logic has been corrected. When + a commit is dropped during rebase (e.g., because its changes are + already upstream), it is no longer recorded as rewritten, preventing + its notes from being copied to an unrelated commit. + (merge 42554b78fd pw/rebase-drop-notes-with-commit later to maint). + + * A few memory problems in the Rust interface to C hash functions have + been corrected. The 'Clone' implementation of 'CryptoHasher' now + properly initializes the context before cloning, and its 'Drop' + implementation now discards the context to prevent leaks. + + * The object ID shortening and linking in the 'commitdiff' view of + 'gitweb' has been corrected to work even when the index line carries + a trailing file mode. + (merge fda513d6fe tl/gitweb-shorten-hashes-with-modes later to maint). diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc index a0ebf03e2eb050..340329edc38143 100644 --- a/Documentation/config/core.adoc +++ b/Documentation/config/core.adoc @@ -589,6 +589,14 @@ core.packedRefsTimeout:: all; -1 means to try indefinitely. Default is 1000 (i.e., retry for 1 second). +core.configLockTimeout:: + The length of time, in milliseconds, to retry when trying to + lock a configuration file for writing. Value 0 means not to + retry at all; -1 means to try indefinitely. Default is 1000 + (i.e., retry for 1 second). This is read from the configuration + that is already on disk before the lock is taken, so it can be + set persistently like any other option. + core.pager:: Text viewer for use by Git commands (e.g., 'less'). The value is meant to be interpreted by the shell. The order of preference diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc index 757a7be196ab38..f7dfce69b5f95e 100644 --- a/Documentation/config/log.adoc +++ b/Documentation/config/log.adoc @@ -59,6 +59,10 @@ This is the same as the `--decorate` option of the `git log`. A list of colors, separated by commas, that can be used to draw history lines in `git log --graph`. +`log.graphIndent`:: + If `true`, indent visual roots when rendering the graphs with `--graph`. + Set true by default. It can be overriden with `--[no-]graph-indent`. + `log.showRoot`:: If true, the initial commit will be shown as a big creation event. This is equivalent to a diff against an empty tree. diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index 86b9181599317e..ac3b528c6f00f6 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc @@ -169,6 +169,13 @@ info :: Print object info for object reference ``. This corresponds to the output of `--batch-check`. +remote-object-info ...:: + Print object info for object references `` at specified + `` without downloading objects from the remote. + Raise an error when the `object-info` capability is not supported by the remote. + Raise an error when no object references are provided. + This command may be combined with `--buffer`. + flush:: Used with `--buffer` to execute all preceding commands that were issued since the beginning or since the last flush was issued. When `--buffer` @@ -301,7 +308,8 @@ one per line, and print information based on the command given. With `--batch-command`, the `info` command followed by an object will print information about the object the same way `--batch-check` would, and the `contents` command followed by an object prints contents in the same way -`--batch` would. +`--batch` would. The `remote-object-info` command followed by a remote and +object IDs prints object info from the remote without downloading the objects. You can specify the information shown for each object by using a custom ``. The `` is copied literally to stdout for each @@ -340,8 +348,15 @@ newline. The available atoms are: after that first run of whitespace (i.e., the "rest" of the line) are output in place of the `%(rest)` atom. +The command `remote-object-info` only supports the `%(objectname)` and +`%(objectsize)` placeholders. See `CAVEATS` below for more information. + If no format is specified, the default format is `%(objectname) -%(objecttype) %(objectsize)`. +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which +use `%(objectname) %(objectsize)` because `%(objecttype)` is not supported yet. + +WARNING: When "%(objecttype)" is supported, the default format WILL be unified, +so DO NOT RELY on the current default format to stay the same!!! If `--batch` is specified, or if `--batch-command` is used with the `contents` command, the object information is followed by the object contents (consisting @@ -438,6 +453,10 @@ scripting purposes. CAVEATS ------- +Note that only `%(objectname)` and `%(objectsize)` are currently +supported by the `remote-object-info` command. Using any other placeholder in +the format string will return an empty string in its position. + Note that the sizes of objects on disk are reported accurately, but care should be taken in drawing conclusions about which refs or objects are responsible for disk usage. The size of a packed non-delta object may be diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc index bd76167a45eb71..9fea75f96f928f 100644 --- a/Documentation/gitattributes.adoc +++ b/Documentation/gitattributes.adoc @@ -914,6 +914,8 @@ patterns are available: - `scheme` suitable for source code in most Lisp dialects, including Scheme, Emacs Lisp, Common Lisp, and Clojure. +- `swift` suitable for source code in the Swift language. + - `tex` suitable for source code for LaTeX documents. diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index 2beb70595fc1e5..7bf62014c3917d 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -568,21 +568,26 @@ An `object-info` request takes the following arguments: oid Indicates to the server an object which the client wants to obtain - information for. + information for. They must be full OIDs. -The response of `object-info` is a list of the requested object ids -and associated requested information, each separated by a single space. +The response of `object-info` consists of one pkt-line per requested attribute, +echoing the attributes the server will report, followed by one pkt-line per +requested object id with its information, each field separated by a single +space. output = info flush-pkt - info = PKT-LINE(attrs) LF) - *PKT-LINE(obj-info LF) - - attrs = attr | attrs SP attrs + info = *PKT-LINE(attr LF) + *PKT-LINE(obj-info LF) attr = "size" - obj-info = obj-id SP obj-size + obj-size = 1*DIGIT + + obj-info = obj-id [SP [obj-size]] + +If the server does not recognize the OID, the response will be ` SP` +regardless of the number of attributes requested. bundle-uri ~~~~~~~~~~ diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index eaee6ee8399c57..fd831f0ec64744 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -1269,6 +1269,14 @@ This implies the `--topo-order` option by default, but the By default it is set to 0 (no limit), zero and negative values are ignored and treated as no limit. +`--no-graph-indent`:: +`--graph-indent`:: + When used with `--graph`, indent visual roots (commits with no parents + or whose parents are not shown) to differentiate them from commits that + are vertically adjacent but unrelated. Enabled by default. Use + `--no-graph-indent` to disable or set `log.graphIndent` to set a + default preference. + ifdef::git-rev-list[] `--count`:: Print a number stating how many commits would have been diff --git a/Makefile b/Makefile index 98e995e4be0967..fac3e8879c8377 100644 --- a/Makefile +++ b/Makefile @@ -1169,6 +1169,7 @@ LIB_OBJS += ewah/ewah_io.o LIB_OBJS += ewah/ewah_rlw.o LIB_OBJS += exec-cmd.o LIB_OBJS += fetch-negotiator.o +LIB_OBJS += fetch-object-info.o LIB_OBJS += fetch-pack.o LIB_OBJS += fmt-merge-msg.o LIB_OBJS += fsck.o diff --git a/apply.c b/apply.c index 5e87ba772e8531..f00b7ba4d3a7e6 100644 --- a/apply.c +++ b/apply.c @@ -47,11 +47,17 @@ struct gitdiff_data { int p_value; }; -static void git_apply_config(void) +static void git_apply_config(struct repository *repo) { - repo_config_get_string(the_repository, "apply.whitespace", &apply_default_whitespace); - repo_config_get_string(the_repository, "apply.ignorewhitespace", &apply_default_ignorewhitespace); - repo_config(the_repository, git_xmerge_config, NULL); + struct repo_config_values *cfg = repo_config_values(repo); + + FREE_AND_NULL(cfg->apply_default_whitespace); + repo_config_get_string(repo, "apply.whitespace", + &cfg->apply_default_whitespace); + FREE_AND_NULL(cfg->apply_default_ignorewhitespace); + repo_config_get_string(repo, "apply.ignorewhitespace", + &cfg->apply_default_ignorewhitespace); + repo_config(repo, git_xmerge_config, NULL); } static int parse_whitespace_option(struct apply_state *state, const char *option) @@ -109,6 +115,8 @@ int init_apply_state(struct apply_state *state, struct repository *repo, const char *prefix) { + struct repo_config_values *cfg = repo_config_values(repo); + memset(state, 0, sizeof(*state)); state->prefix = prefix; state->repo = repo; @@ -126,10 +134,13 @@ int init_apply_state(struct apply_state *state, strset_init(&state->kept_symlinks); strbuf_init(&state->root, 0); - git_apply_config(); - if (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace)) + git_apply_config(repo); + + if (cfg->apply_default_whitespace && + parse_whitespace_option(state, cfg->apply_default_whitespace)) return -1; - if (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace)) + if (cfg->apply_default_ignorewhitespace && + parse_ignorewhitespace_option(state, cfg->apply_default_ignorewhitespace)) return -1; return 0; } @@ -192,7 +203,8 @@ int check_apply_state(struct apply_state *state, int force_apply) static void set_default_whitespace_mode(struct apply_state *state) { - if (!state->whitespace_option && !apply_default_whitespace) + if (!state->whitespace_option && + !repo_config_values(state->repo)->apply_default_whitespace) state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error); } @@ -3893,8 +3905,8 @@ static int check_preimage(struct apply_state *state, if (*ce && !(*ce)->ce_mode) BUG("ce_mode == 0 for path '%s'", old_name); - if (trust_executable_bit || !S_ISREG(st->st_mode)) - st_mode = ce_mode_from_stat(*ce, st->st_mode); + if (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode)) + st_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode); else if (*ce) st_mode = (*ce)->ce_mode; else @@ -4287,7 +4299,8 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list) } } - hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(state->repo, &lock, state->fake_ancestor, + LOCK_DIE_ON_ERROR); res = write_locked_index(&result, &lock, COMMIT_LOCK); discard_index(&result); @@ -4511,7 +4524,7 @@ static int try_create_file(struct apply_state *state, const char *path, return !!mkdir(path, 0777); } - if (has_symlinks && S_ISLNK(mode)) + if (repo_has_symlinks(state->repo) && S_ISLNK(mode)) /* Although buf:size is counted string, it also is NUL * terminated. */ @@ -4945,9 +4958,10 @@ static int apply_patch(struct apply_state *state, state->update_index = (state->check_index || state->ita_only) && state->apply; if (state->update_index && !is_lock_file_locked(&state->lock_file)) { if (state->index_file) - hold_lock_file_for_update(&state->lock_file, - state->index_file, - LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(state->repo, + &state->lock_file, + state->index_file, + LOCK_DIE_ON_ERROR); else repo_hold_locked_index(state->repo, &state->lock_file, LOCK_DIE_ON_ERROR); diff --git a/branch.c b/branch.c index 243db7d0fc0226..3a12e2a9ebeeb6 100644 --- a/branch.c +++ b/branch.c @@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv) static int should_setup_rebase(const char *origin) { - switch (autorebase) { + switch (repo_config_values(the_repository)->autorebase) { case AUTOREBASE_NEVER: return 0; case AUTOREBASE_LOCAL: @@ -372,7 +372,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name) */ int validate_branchname(const char *name, struct strbuf *ref) { - if (check_branch_ref(ref, name)) { + if (check_branch_ref(the_repository, ref, name)) { int code = die_message(_("'%s' is not a valid branch name"), name); advise_if_enabled(ADVICE_REF_SYNTAX, _("See 'git help check-ref-format'")); @@ -394,7 +394,7 @@ static void prepare_checked_out_branches(void) return; initialized_checked_out_branches = 1; - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); while (worktrees[i]) { char *old, *wt_gitdir; @@ -846,7 +846,7 @@ void remove_branch_state(struct repository *r, int verbose) void die_if_checked_out(const char *branch, int ignore_current_worktree) { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(the_repository); for (int i = 0; worktrees[i]; i++) { if (worktrees[i]->is_current && ignore_current_worktree) diff --git a/builtin/branch.c b/builtin/branch.c index dede60d27b69ea..031a4a9d055558 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -259,7 +259,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, char *target = NULL; int flags = 0; - copy_branchname(&bname, argv[i], allowed_interpret); + copy_branchname(the_repository, &bname, + argv[i], allowed_interpret); free(name); name = mkpathdup(fmt, bname.buf); @@ -579,9 +580,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int const char *interpreted_oldname = NULL; const char *interpreted_newname = NULL; int recovery = 0, oldref_usage = 0; - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(the_repository); - if (check_branch_ref(&oldref, oldname)) { + if (check_branch_ref(the_repository, &oldref, oldname)) { /* * Bad name --- this could be an attempt to rename a * ref that we used to allow to be created by accident. @@ -921,7 +922,8 @@ int cmd_branch(int argc, die(_("cannot give description to detached HEAD")); branch_name = head; } else if (argc == 1) { - copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + copy_branchname(the_repository, &buf, argv[0], + INTERPRET_BRANCH_LOCAL); branch_name = buf.buf; } else { die(_("cannot edit description of more than one branch")); @@ -964,7 +966,8 @@ int cmd_branch(int argc, if (!argc) branch = branch_get(NULL); else if (argc == 1) { - copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + copy_branchname(the_repository, &buf, argv[0], + INTERPRET_BRANCH_LOCAL); branch = branch_get(buf.buf); } else die(_("too many arguments to set new upstream")); @@ -1003,7 +1006,8 @@ int cmd_branch(int argc, if (!argc) branch = branch_get(NULL); else if (argc == 1) { - copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + copy_branchname(the_repository, &buf, argv[0], + INTERPRET_BRANCH_LOCAL); branch = branch_get(buf.buf); } else die(_("too many arguments to unset upstream")); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 1458dd76d683e0..884b6d5ad348b5 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -28,6 +28,22 @@ #include "promisor-remote.h" #include "mailmap.h" #include "write-or-die.h" +#include "alias.h" +#include "remote.h" +#include "transport.h" + +/* + * Maximum length for a remote URL. While no universal standard exists, + * 8K is assumed to be a reasonable limit. + */ +#define MAX_REMOTE_URL_LEN (8 * 1024) + +/* Maximum number of objects allowed in a single remote-object-info request. */ +#define MAX_ALLOWED_OBJ_LIMIT 10000 + +/* Maximum input size permitted for the remote-object-info command. */ +#define MAX_REMOTE_OBJ_INFO_LINE \ + (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1)) enum batch_mode { BATCH_MODE_CONTENTS, @@ -316,8 +332,24 @@ struct expand_data { * optimized out. */ unsigned skip_object_info : 1; + + /* + * Flags about when an object info is being fetched from remote. + */ + unsigned is_remote:1; + + /* + * List of atoms (i.e. "objectsize") that the server supports. Built + * from the server's object-info advertised capabilities. + */ + struct string_list remote_allowed_atoms; }; -#define EXPAND_DATA_INIT { .mode = S_IFINVALID } + +#define EXPAND_DATA_INIT { \ + .mode = S_IFINVALID, \ + .type = OBJ_BAD, \ + .remote_allowed_atoms = STRING_LIST_INIT_NODUP, \ +} static int is_atom(const char *atom, const char *s, int slen) { @@ -328,14 +360,26 @@ static int is_atom(const char *atom, const char *s, int slen) static int expand_atom(struct strbuf *sb, const char *atom, int len, struct expand_data *data) { + if (data->is_remote) { + size_t i; + for (i = 0; i < data->remote_allowed_atoms.nr; i++) + if (is_atom(data->remote_allowed_atoms.items[i].string, + atom, len)) + break; + if (i == data->remote_allowed_atoms.nr) + return 1; + } + if (is_atom("objectname", atom, len)) { if (!data->mark_query) strbuf_add_oid_hex(sb, &data->oid); } else if (is_atom("objecttype", atom, len)) { - if (data->mark_query) + if (data->mark_query) { data->info.typep = &data->type; - else - strbuf_addstr(sb, type_name(data->type)); + } else { + const char *t = type_name(data->type); + strbuf_addstr(sb, t ? t : ""); + } } else if (is_atom("objectsize", atom, len)) { if (data->mark_query) data->info.sizep = &data->size; @@ -635,6 +679,62 @@ static void batch_one_object(const char *obj_name, object_context_release(&ctx); } +static int get_remote_info(int argc, + const char **argv, + struct object_info **remote_object_info, + struct oid_array *object_info_oids, + struct string_list *object_info_options) +{ + int retval = 0; + struct remote *remote = NULL; + struct object_id oid; + struct transport *gtransport; + + remote = remote_get(argv[0]); + if (!remote) + die(_("must supply valid remote when using remote-object-info")); + + oid_array_clear(object_info_oids); + for (size_t i = 1; i < argc; i++) { + if (get_oid_hex(argv[i], &oid)) { + size_t len = strlen(argv[i]); + + if (len < the_hash_algo->hexsz && len >= 4) { + size_t j; + for (j = 0; j < len; j++) + if (!isxdigit(argv[i][j])) + break; + if (j == len) + die(_("remote-object-info does not support " + "short oids, %d characters required"), + (int)the_hash_algo->hexsz); + } + die(_("not a valid object name '%s'"), argv[i]); + } + oid_array_append(object_info_oids, &oid); + } + + if (!object_info_oids->nr) + die(_("remote-object-info requires objects")); + + gtransport = transport_get(remote, NULL); + + if (!gtransport->smart_options) { + retval = -1; + goto cleanup; + } + + CALLOC_ARRAY(*remote_object_info, object_info_oids->nr); + gtransport->smart_options->object_info_oids = object_info_oids; + + gtransport->smart_options->object_info_options = object_info_options; + gtransport->smart_options->object_info_data = *remote_object_info; + retval = transport_fetch_object_info(gtransport); +cleanup: + transport_disconnect(gtransport); + return retval; +} + struct object_cb_data { struct batch_options *opt; struct expand_data *expand; @@ -716,18 +816,115 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED, load_mailmap(); } +struct protocol_placeholder_entry { + const char *option; + const char *atom; +}; + +static const struct protocol_placeholder_entry remote_atom_map[] = { + {"size", "objectsize"}, + {"type", "objecttype"}, + /* + * Add new protocol options here. Even if the server doesn't support + * them the allow_list will drop them if the server doesn't advertise + * them. + */ +}; + +static void parse_cmd_remote_object_info(struct batch_options *opt, + const char *line, struct strbuf *output, + struct expand_data *data) +{ + int count; + const char **argv; + char *line_to_split; + struct object_info *remote_object_info = NULL; + struct oid_array object_info_oids = OID_ARRAY_INIT; + struct string_list object_info_options = STRING_LIST_INIT_NODUP; + const char *saved_format = opt->format; + + if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE) + die(_("remote-object-info command too long")); + /* + * TODO: Use the default format once %(objecttype) is supported. + */ + if (!opt->format) + opt->format = "%(objectname) %(objectsize)"; + + line_to_split = xstrdup(line); + count = split_cmdline(line_to_split, &argv); + if (count < 0) + die(_("remote-object-info: failed to parse command line: %s"), + split_cmdline_strerror(count)); + if (count - 1 > MAX_ALLOWED_OBJ_LIMIT) + die(_("remote-object-info supports at most %d objects"), + MAX_ALLOWED_OBJ_LIMIT); + + if (data->info.sizep) + string_list_append(&object_info_options, "size"); + if (data->info.typep) + string_list_append(&object_info_options, "type"); + + if (get_remote_info(count, argv, &remote_object_info, + &object_info_oids, &object_info_options)) + die(_("failed to get object info from the remote: %s"), argv[0]); + + string_list_clear(&data->remote_allowed_atoms, 0); + string_list_append(&data->remote_allowed_atoms, "objectname"); + for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++) + if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option)) + string_list_append(&data->remote_allowed_atoms, + remote_atom_map[i].atom); + + data->skip_object_info = 1; + for (size_t i = 0; i < object_info_oids.nr; i++) { + data->oid = object_info_oids.oid[i]; + + if (remote_object_info[i].unrecognized) { + report_object_status(opt, oid_to_hex(&data->oid), + &data->oid, "missing"); + continue; + } + + /* + * When reaching here, it means remote-object-info can retrieve + * information from server without downloading them. + */ + if (remote_object_info[i].sizep) { + data->size = *remote_object_info[i].sizep; + } + + if (remote_object_info[i].typep) { + data->type = *remote_object_info[i].typep; + } + + opt->batch_mode = BATCH_MODE_INFO; + data->is_remote = 1; + batch_object_write(argv[i + 1], output, opt, data, NULL, 0); + data->is_remote = 0; + } + data->skip_object_info = 0; + opt->format = saved_format; + + for (size_t i = 0; i < object_info_oids.nr; i++) + free_object_info_contents(&remote_object_info[i]); + string_list_clear(&object_info_options, 0); + free(line_to_split); + free(argv); + free(remote_object_info); + oid_array_clear(&object_info_oids); +} + static void dispatch_calls(struct batch_options *opt, struct strbuf *output, struct expand_data *data, struct queued_cmd *cmd, - int nr) + size_t nr) { - int i; - if (!opt->buffer_output) die(_("flush is only for --buffer mode")); - for (i = 0; i < nr; i++) + for (size_t i = 0; i < nr; i++) cmd[i].fn(opt, cmd[i].line, output, data); fflush(stdout); @@ -735,9 +932,7 @@ static void dispatch_calls(struct batch_options *opt, static void free_cmds(struct queued_cmd *cmd, size_t *nr) { - size_t i; - - for (i = 0; i < *nr; i++) + for (size_t i = 0; i < *nr; i++) FREE_AND_NULL(cmd[i].line); *nr = 0; @@ -750,9 +945,10 @@ static const struct parse_cmd { unsigned takes_args; } commands[] = { { "contents", parse_cmd_contents, 1 }, - { "info", parse_cmd_info, 1 }, { "flush", NULL, 0 }, + { "info", parse_cmd_info, 1 }, { "mailmap", parse_cmd_mailmap, 1 }, + { "remote-object-info", parse_cmd_remote_object_info, 1 }, }; static void batch_objects_command(struct batch_options *opt, @@ -764,7 +960,6 @@ static void batch_objects_command(struct batch_options *opt, size_t alloc = 0, nr = 0; while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) { - int i; const struct parse_cmd *cmd = NULL; const char *p = NULL, *cmd_end; struct queued_cmd call = {0}; @@ -774,7 +969,7 @@ static void batch_objects_command(struct batch_options *opt, if (isspace(*input.buf)) die(_("whitespace before command: '%s'"), input.buf); - for (i = 0; i < ARRAY_SIZE(commands); i++) { + for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { if (!skip_prefix(input.buf, commands[i].name, &cmd_end)) continue; @@ -972,6 +1167,7 @@ static int batch_objects(struct batch_options *opt) cleanup: strbuf_release(&input); strbuf_release(&output); + string_list_clear(&data.remote_allowed_atoms, 0); cfg->warn_on_object_refname_ambiguity = save_warning; return retval; } diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index e42b0444ead269..fd1c9c0e0c8e60 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -45,7 +45,7 @@ static int check_ref_format_branch(const char *arg) int nongit; setup_git_directory_gently(the_repository, &nongit); - if (check_branch_ref(&sb, arg) || + if (check_branch_ref(the_repository, &sb, arg) || !skip_prefix(sb.buf, "refs/heads/", &name)) die("'%s' is not a valid branch name", arg); printf("%s\n", name); diff --git a/builtin/checkout.c b/builtin/checkout.c index aee84ca89742b0..55e3a89a852712 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -805,7 +805,7 @@ static void setup_branch_path(struct branch_info *branch) &branch->oid, &branch->refname, 0)) repo_get_oid_committish(the_repository, branch->name, &branch->oid); - copy_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); + copy_branchname(the_repository, &buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) { free(branch->name); branch->name = xstrdup(buf.buf); diff --git a/builtin/clone.c b/builtin/clone.c index 9d08cd87224304..c649a9f49e6e22 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -335,7 +335,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, die_errno(_("failed to create link '%s'"), dest->buf); option_no_hardlinks = 1; } - if (copy_file_with_time(dest->buf, src->buf, 0666)) + if (copy_file_with_time(the_repository, dest->buf, src->buf, 0666)) die_errno(_("failed to copy file to '%s'"), dest->buf); } diff --git a/builtin/config.c b/builtin/config.c index 8d8ec0beead220..0882899c3fbd2a 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -974,7 +974,7 @@ static void location_options_init(struct config_location_options *opts, opts->source.file = opts->file_to_free = repo_git_path(the_repository, "config"); opts->source.scope = CONFIG_SCOPE_LOCAL; } else if (opts->use_worktree_config) { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(the_repository); if (the_repository->repository_format_worktree_config) opts->source.file = opts->file_to_free = repo_git_path(the_repository, "config.worktree"); diff --git a/builtin/difftool.c b/builtin/difftool.c index 26778f8515deef..bc7b2ea44396bb 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -552,7 +552,7 @@ static int run_dir_diff(struct repository *repo, struct stat st; if (stat(wtdir.buf, &st)) st.st_mode = 0644; - if (copy_file(rdir.buf, wtdir.buf, + if (copy_file(repo, rdir.buf, wtdir.buf, st.st_mode)) { ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); goto finish; @@ -636,7 +636,7 @@ static int run_dir_diff(struct repository *repo, struct lock_file lock = LOCK_INIT; strbuf_reset(&buf); strbuf_addf(&buf, "%s/wtindex", tmpdir.buf); - if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + if (repo_hold_lock_file_for_update(repo, &lock, buf.buf, 0) < 0 || write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { ret = error("could not write %s", buf.buf); goto finish; @@ -658,7 +658,7 @@ static int run_dir_diff(struct repository *repo, warning("%s", ""); err = 1; } else if (unlink(wtdir.buf) || - copy_file(wtdir.buf, rdir.buf, st.st_mode)) + copy_file(repo, wtdir.buf, rdir.buf, st.st_mode)) warning_errno(_("could not copy '%s' to '%s'"), rdir.buf, wtdir.buf); } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 629d7c591a9d70..50278bef7a6a4d 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -51,7 +51,7 @@ static int show_original_ids; static int mark_tags; static struct string_list extra_refs = STRING_LIST_INIT_DUP; static struct string_list tag_refs = STRING_LIST_INIT_DUP; -static struct refspec refspecs = REFSPEC_INIT_FETCH; +static struct refspec refspecs; static int anonymize; static struct hashmap anonymized_seeds; static struct revision_sources revision_sources; @@ -1372,6 +1372,8 @@ int cmd_fast_export(int argc, /* we handle encodings */ repo_config(the_repository, git_default_config, NULL); + refspec_init_fetch(&refspecs, the_hash_algo); + repo_init_revisions(the_repository, &revs, prefix); init_revision_sources(&revision_sources); revs.topo_order = 1; diff --git a/builtin/fetch.c b/builtin/fetch.c index 775a79707472ae..ab7db2be06d14c 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -96,7 +96,7 @@ static struct string_list deepen_not = STRING_LIST_INIT_NODUP; static struct strbuf default_rla = STRBUF_INIT; static struct transport *gtransport; static struct transport *gsecondary; -static struct refspec refmap = REFSPEC_INIT_FETCH; +static struct refspec refmap; static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_restrict = STRING_LIST_INIT_NODUP; static struct string_list negotiation_include = STRING_LIST_INIT_NODUP; @@ -601,7 +601,8 @@ static struct ref *get_ref_map(struct remote *remote, struct refspec_item tag_refspec; /* also fetch all tags */ - refspec_item_init_push(&tag_refspec, TAG_REFSPEC); + refspec_item_init_push(&tag_refspec, TAG_REFSPEC, + the_hash_algo); get_fetch_map(remote_refs, &tag_refspec, &tail, 0); refspec_item_clear(&tag_refspec); } else if (tags == TAGS_DEFAULT && *autotags) { @@ -2428,7 +2429,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, const struct fetch_config *config, struct list_objects_filter_options *filter_options) { - struct refspec rs = REFSPEC_INIT_FETCH; + struct refspec rs = REFSPEC_INIT_FETCH(the_hash_algo); int i; int exit_code; int maybe_prune_tags; @@ -2630,6 +2631,8 @@ int cmd_fetch(int argc, filter_options.allow_auto_filter = 1; + refspec_init_fetch(&refmap, the_hash_algo); + packet_trace_identity("fetch"); /* Record the command line for the reflog */ diff --git a/builtin/fsck.c b/builtin/fsck.c index 76b723f36d3dca..a6c054e45bf8c0 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -632,7 +632,7 @@ static void snapshot_refs(struct repository *repo, refs_for_each_ref_ext(get_main_ref_store(repo), snapshot_ref, &data, &opts); - worktrees = get_worktrees(); + worktrees = get_worktrees(repo); for (p = worktrees; *p; p++) { struct worktree *wt = *p; struct strbuf refname = STRBUF_INIT; @@ -685,7 +685,7 @@ static void process_refs(struct repository *repo, struct snapshot *snap) } if (include_reflogs) { - worktrees = get_worktrees(); + worktrees = get_worktrees(repo); for (p = worktrees; *p; p++) { struct worktree *wt = *p; @@ -1121,7 +1121,7 @@ int cmd_fsck(int argc, verify_index_checksum = 1; verify_ce_order = 1; - worktrees = get_worktrees(); + worktrees = get_worktrees(repo); for (p = worktrees; *p; p++) { struct worktree *wt = *p; struct index_state istate = diff --git a/builtin/gc.c b/builtin/gc.c index d32af422af5e58..49c8474fade8ed 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -412,7 +412,7 @@ static int worktree_prune_condition(struct gc_config *cfg) while (limit && (d = readdir_skip_dot_and_dotdot(dir))) { char *wtpath; strbuf_reset(&buf); - if (should_prune_worktree(d->d_name, &buf, &wtpath, expiry_date)) + if (should_prune_worktree(the_repository, d->d_name, &buf, &wtpath, expiry_date)) limit--; free(wtpath); } @@ -1790,7 +1790,7 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts, struct repository *r = the_repository; char *lock_path = xstrfmt("%s/maintenance", r->objects->sources->path); - if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + if (repo_hold_lock_file_for_update(r, &lk, lock_path, LOCK_NO_DEREF) < 0) { /* * Another maintenance command is running. * diff --git a/builtin/history.c b/builtin/history.c index d28c1f08bb66ea..000155ad9c24d5 100644 --- a/builtin/history.c +++ b/builtin/history.c @@ -795,7 +795,7 @@ static int write_ondisk_index(struct repository *repo, prime_cache_tree(repo, &index, tree); - if (hold_lock_file_for_update(&lock, path, 0) < 0) { + if (repo_hold_lock_file_for_update(repo, &lock, path, 0) < 0) { ret = error_errno(_("unable to acquire index lock")); goto out; } diff --git a/builtin/merge.c b/builtin/merge.c index 5b46a596f0bdf4..58d1b7bb07d90f 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -553,7 +553,7 @@ static void merge_name(const char *remote, struct strbuf *msg) char *found_ref = NULL; int len, early; - copy_branchname(&bname, remote, 0); + copy_branchname(the_repository, &bname, remote, 0); remote = bname.buf; oidclr(&branch_head, the_repository->hash_algo); diff --git a/builtin/notes.c b/builtin/notes.c index 962df867c85843..9f1f0ec840b533 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -989,7 +989,7 @@ static int merge(int argc, const char **argv, const char *prefix, "NOTES_MERGE_PARTIAL", &result_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); wt = find_shared_symref(worktrees, "NOTES_MERGE_REF", notes_ref); if (wt) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 3673b14b89b275..1ec5b6f206366e 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -32,6 +32,7 @@ #include "list.h" #include "packfile.h" #include "object-file.h" +#include "object-file-convert.h" #include "odb.h" #include "odb/streaming.h" #include "replace-object.h" @@ -4621,6 +4622,51 @@ static int loosened_object_can_be_discarded(const struct object_id *oid, return 1; } +static int force_object_loose(struct odb_source *source, + const struct object_id *oid, + const time_t *mtime) +{ + struct odb_source_files *files = odb_source_files_downcast(source); + const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; + struct object_info oi = OBJECT_INFO_INIT; + struct object_id compat_oid, *compat_oid_p = NULL; + enum object_type type; + void *buf = NULL; + size_t len; + int ret; + + for (struct odb_source *s = source->odb->sources; s; s = s->next) { + struct odb_source_files *files = odb_source_files_downcast(s); + if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0)) + return 0; + } + + oi.typep = &type; + oi.sizep = &len; + oi.contentp = &buf; + if (odb_read_object_info_extended(source->odb, oid, &oi, 0)) { + ret = error(_("cannot read object for %s"), oid_to_hex(oid)); + goto out; + } + + if (compat) { + if (repo_oid_to_algop(source->odb->repo, oid, compat, &compat_oid)) { + ret = error(_("cannot map object %s to %s"), + oid_to_hex(oid), compat->name); + goto out; + } + + compat_oid_p = &compat_oid; + } + + ret = odb_source_write_object(&files->loose->base, buf, len, type, oid, + compat_oid_p, mtime, 0); + +out: + free(buf); + return ret; +} + static void loosen_unused_packed_objects(void) { struct packed_git *p; @@ -4641,7 +4687,7 @@ static void loosen_unused_packed_objects(void) !has_sha1_pack_kept_or_nonlocal(&oid) && !loosened_object_can_be_discarded(&oid, p->mtime)) { if (force_object_loose(the_repository->objects->sources, - &oid, p->mtime)) + &oid, &p->mtime)) die(_("unable to force loose object")); loosened_objects_nr++; } diff --git a/builtin/pull.c b/builtin/pull.c index d49b09114ae7d8..db3ee0aab3ed91 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -612,7 +612,7 @@ static const char *get_tracking_branch(const char *remote, const char *refspec) const char *spec_src; const char *merge_branch; - if (!refspec_item_init_fetch(&spec, refspec)) + if (!refspec_item_init_fetch(&spec, refspec, the_hash_algo)) die(_("invalid refspec '%s'"), refspec); spec_src = spec.src; if (!*spec_src || !strcmp(spec_src, "HEAD")) diff --git a/builtin/push.c b/builtin/push.c index 1b2ad3b8df7c55..2377b5af554bda 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -66,7 +66,7 @@ static enum transport_family family; static struct push_cas_option cas; -static struct refspec rs = REFSPEC_INIT_PUSH; +static struct refspec rs; static struct string_list push_options_config = STRING_LIST_INIT_DUP; @@ -74,6 +74,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref, struct remote *remote, struct ref *matched) { const char *branch_name; + struct repo_config_values *cfg = repo_config_values(the_repository); if (remote->push.nr) { struct refspec_item query = { @@ -89,7 +90,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref, } } - if (push_default == PUSH_DEFAULT_UPSTREAM && + if (cfg->push_default == PUSH_DEFAULT_UPSTREAM && skip_prefix(matched->name, "refs/heads/", &branch_name)) { struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { @@ -161,7 +162,7 @@ static NORETURN void die_push_simple(struct branch *branch, * Don't show advice for people who explicitly set * push.default. */ - if (push_default == PUSH_DEFAULT_UNSPECIFIED) + if (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED) advice_pushdefault_maybe = _("\n" "To choose either option permanently, " "see push.default in 'git help config'.\n"); @@ -232,8 +233,9 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote) struct branch *branch; const char *dst; int same_remote; + struct repo_config_values *cfg = repo_config_values(the_repository); - switch (push_default) { + switch (cfg->push_default) { case PUSH_DEFAULT_MATCHING: refspec_append(&rs, ":"); return; @@ -253,7 +255,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote) dst = branch->refname; same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL)); - switch (push_default) { + switch (cfg->push_default) { default: case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_SIMPLE: @@ -749,6 +751,8 @@ int cmd_push(int argc, : &push_options_config); set_push_cert_flags(&flags, push_cert); + refspec_init_push(&rs, the_hash_algo); + die_for_incompatible_opt4(deleterefs, "--delete", tags, "--tags", flags & TRANSPORT_PUSH_ALL, "--all/--branches", @@ -855,7 +859,7 @@ int cmd_push(int argc, } refspec_clear(&rs); - rs = (struct refspec) REFSPEC_INIT_PUSH; + rs = (struct refspec) REFSPEC_INIT_PUSH(the_hash_algo); if (tags) refspec_append(&rs, "refs/tags/*"); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 7190d7f43063c9..86933d8d7e4b33 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1500,7 +1500,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) struct object_id *old_oid = &cmd->old_oid; struct object_id *new_oid = &cmd->new_oid; int do_update_worktree = 0; - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(the_repository); const struct worktree *worktree = find_shared_symref(worktrees, "HEAD", name); diff --git a/builtin/reflog.c b/builtin/reflog.c index dcbfe89339f9da..1211c58fa45e3c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -250,7 +250,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, struct string_list_item *item; struct worktree **worktrees, **p; - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); for (p = worktrees; *p; p++) { if (single_worktree && !(*p)->is_current) continue; @@ -374,7 +374,7 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix, struct string_list_item *item; struct worktree **worktrees, **p; - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); for (p = worktrees; *p; p++) { if (single_worktree && !(*p)->is_current) continue; diff --git a/builtin/refs.c b/builtin/refs.c index a9ca2058eeb55c..5cd21c25fe53dc 100644 --- a/builtin/refs.c +++ b/builtin/refs.c @@ -113,7 +113,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix, repo_config(repo, git_fsck_config, &fsck_refs_options); prepare_repo_settings(repo); - worktrees = get_worktrees_without_reading_head(); + worktrees = get_worktrees_without_reading_head(repo); for (size_t i = 0; worktrees[i]; i++) ret |= refs_fsck(get_worktree_ref_store(worktrees[i]), &fsck_refs_options, worktrees[i]); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 1412b49bc845b1..d6cdbae472f725 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -153,7 +153,7 @@ int cmd_send_pack(int argc, const char *prefix, struct repository *repo) { - struct refspec rs = REFSPEC_INIT_PUSH; + struct refspec rs; const char *remote_name = NULL; struct remote *remote = NULL; const char *dest = NULL; @@ -214,6 +214,9 @@ int cmd_send_pack(int argc, repo_config(repo, send_pack_config, NULL); argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0); + + refspec_init_push(&rs, repo->hash_algo); + if (argc > 0) { dest = argv[0]; refspec_appendn(&rs, argv + 1, argc - 1); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 0863d0fb460cf8..cb4a037b770291 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -341,7 +341,8 @@ static int write_patterns_and_update(struct repository *repo, if (safe_create_leading_directories(repo, sparse_filename)) die(_("failed to create directory for sparse-checkout file")); - hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(repo, &lk, sparse_filename, + LOCK_DIE_ON_ERROR); result = update_working_directory(repo, pl); if (result) { diff --git a/builtin/stash.c b/builtin/stash.c index c4809f299a313b..72c52571f8c06c 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1702,8 +1702,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (!include_untracked && ps->nr) { char *ps_matched = xcalloc(ps->nr, 1); - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(the_repository->index); + if (pathspec_needs_expanded_index(the_repository->index, ps)) + ensure_full_index(the_repository->index); for (size_t i = 0; i < the_repository->index->cache_nr; i++) ce_path_match(the_repository->index, the_repository->index->cache[i], ps, ps_matched); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 2b3c9762afec0a..e7cd3225fa84c4 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -549,7 +549,8 @@ static void create_default_gitdir_config(const char *submodule_name) } /* Case 2.4: If all the above failed, try a hash of the name as a last resort */ - header_len = snprintf(header, sizeof(header), "blob %zu", strlen(submodule_name)); + header_len = snprintf(header, sizeof(header), + "blob %"PRIuMAX, (uintmax_t)strlen(submodule_name)); git_hash_init(&ctx, the_hash_algo); git_hash_update(&ctx, header, header_len); git_hash_update(&ctx, "\0", 1); @@ -3150,7 +3151,7 @@ static int push_check(int argc, const char **argv, const char *prefix UNUSED, if (argc > 2) { int i; struct ref *local_refs = get_local_heads(); - struct refspec refspec = REFSPEC_INIT_PUSH; + struct refspec refspec = REFSPEC_INIT_PUSH(the_hash_algo); refspec_appendn(&refspec, argv + 2, argc - 2); diff --git a/builtin/update-index.c b/builtin/update-index.c index 4c4b39a1575a38..241abd4332dcf9 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -294,7 +294,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; fill_stat_cache_info(the_repository->index, ce, st); - ce->ce_mode = ce_mode_from_stat(old, st->st_mode); + ce->ce_mode = ce_mode_from_stat(the_repository, old, st->st_mode); if (index_path(the_repository->index, &ce->oid, path, st, info_only ? 0 : INDEX_WRITE_OBJECT)) { diff --git a/builtin/worktree.c b/builtin/worktree.c index 4bc7b4f6e7199a..654d27c3e1ce99 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -226,7 +226,7 @@ static void prune_worktrees(void) while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) { char *path; strbuf_reset(&reason); - if (should_prune_worktree(d->d_name, &reason, &path, expire)) + if (should_prune_worktree(the_repository, d->d_name, &reason, &path, expire)) prune_worktree(d->d_name, reason.buf); else if (path) string_list_append_nodup(&kept, path)->util = xstrdup(d->d_name); @@ -349,7 +349,7 @@ static void copy_sparse_checkout(const char *worktree_git_dir) if (file_exists(from_file)) { if (safe_create_leading_directories(the_repository, to_file) || - copy_file(to_file, from_file, 0666)) + copy_file(the_repository, to_file, from_file, 0666)) error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"), from_file, to_file); } @@ -368,7 +368,7 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir) int bare; if (safe_create_leading_directories(the_repository, to_file) || - copy_file(to_file, from_file, 0666)) { + copy_file(the_repository, to_file, from_file, 0666)) { error(_("failed to copy worktree config from '%s' to '%s'"), from_file, to_file); goto worktree_copy_cleanup; @@ -475,13 +475,13 @@ static int add_worktree(const char *path, const char *refname, struct ref_store *wt_refs; struct repo_config_values *cfg = repo_config_values(the_repository); - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); check_candidate_path(path, opts->force, worktrees, "add"); free_worktrees(worktrees); worktrees = NULL; /* is 'refname' a branch or commit? */ - if (!opts->detach && !check_branch_ref(&symref, refname) && + if (!opts->detach && !check_branch_ref(the_repository, &symref, refname) && refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) { is_branch = 1; if (!opts->force) @@ -539,7 +539,8 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_worktree_linking_files(sb_git.buf, sb.buf, opts->relative_paths); + write_worktree_linking_files(the_repository, sb_git.buf, + sb.buf, opts->relative_paths); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); @@ -547,7 +548,7 @@ static int add_worktree(const char *path, const char *refname, /* * Set up the ref store of the worktree and create the HEAD reference. */ - wt = get_linked_worktree(name, 1); + wt = get_linked_worktree(the_repository, name, 1); if (!wt) { ret = error(_("could not find created worktree '%s'"), name); goto done; @@ -649,7 +650,7 @@ static void print_preparing_worktree_line(int detach, fprintf_ln(stderr, _("Preparing worktree (new branch '%s')"), new_branch); } else { struct strbuf s = STRBUF_INIT; - if (!detach && !check_branch_ref(&s, branch) && + if (!detach && !check_branch_ref(the_repository, &s, branch) && refs_ref_exists(get_main_ref_store(the_repository), s.buf)) fprintf_ln(stderr, _("Preparing worktree (checking out '%s')"), branch); @@ -771,7 +772,7 @@ static char *dwim_branch(const char *path, char **new_branch) char *branchname = xstrndup(s, n); struct strbuf ref = STRBUF_INIT; - branch_exists = !check_branch_ref(&ref, branchname) && + branch_exists = !check_branch_ref(the_repository, &ref, branchname) && refs_ref_exists(get_main_ref_store(the_repository), ref.buf); strbuf_release(&ref); @@ -868,7 +869,7 @@ static int add(int ac, const char **av, const char *prefix, new_branch = new_branch_force; if (!opts.force && - !check_branch_ref(&symref, new_branch) && + !check_branch_ref(the_repository, &symref, new_branch) && refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) die_if_checked_out(symref.buf, 0); strbuf_release(&symref); @@ -1106,7 +1107,7 @@ static int list(int ac, const char **av, const char *prefix, else if (!line_terminator && !porcelain) die(_("the option '%s' requires '%s'"), "-z", "--porcelain"); else { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(the_repository); int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i; struct worktree_display *display = NULL; @@ -1149,7 +1150,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix, if (ac != 1) usage_with_options(git_worktree_lock_usage, options); - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -1186,7 +1187,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix, if (ac != 1) usage_with_options(git_worktree_unlock_usage, options); - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -1272,7 +1273,7 @@ static int move_worktree(int ac, const char **av, const char *prefix, strbuf_addstr(&dst, path); free(path); - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -1397,7 +1398,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix, if (ac != 1) usage_with_options(git_worktree_remove_usage, options); - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -1459,8 +1460,9 @@ static int repair(int ac, const char **av, const char *prefix, ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0); p = ac > 0 ? av : self; for (; *p; p++) - repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths); - repair_worktrees(report_repair, &rc, use_relative_paths); + repair_worktree_at_path(the_repository, *p, report_repair, + &rc, use_relative_paths); + repair_worktrees(the_repository, report_repair, &rc, use_relative_paths); return rc; } diff --git a/bundle-uri.c b/bundle-uri.c index 9ca2060e9af9c1..2bb2eb99e47b58 100644 --- a/bundle-uri.c +++ b/bundle-uri.c @@ -396,7 +396,7 @@ static int copy_uri_to_file(const char *filename, const char *uri) uri = out; /* Copy as a file */ - return copy_file(filename, uri, 0); + return copy_file(the_repository, filename, uri, 0); } static int unbundle_from_file(struct repository *r, const char *file) diff --git a/bundle.c b/bundle.c index fd2db2c837df60..b64716f252b78f 100644 --- a/bundle.c +++ b/bundle.c @@ -519,8 +519,8 @@ int create_bundle(struct repository *r, const char *path, if (bundle_to_stdout) bundle_fd = 1; else - bundle_fd = hold_lock_file_for_update(&lock, path, - LOCK_DIE_ON_ERROR); + bundle_fd = repo_hold_lock_file_for_update(r, &lock, path, + LOCK_DIE_ON_ERROR); if (version == -1) version = min_version; diff --git a/combine-diff.c b/combine-diff.c index fb72174918786a..717d5376888f8e 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1080,7 +1080,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, /* if symlinks don't work, assume symlink if all parents * are symlinks */ - is_file = has_symlinks; + is_file = repo_has_symlinks(rev->repo); for (i = 0; !is_file && i < num_parent; i++) is_file = !S_ISLNK(elem->parent[i].mode); if (!is_file) diff --git a/commit-graph.c b/commit-graph.c index ce2e020759ca25..49e8f639305212 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -2127,8 +2127,8 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) if (ctx->split) { char *lock_name = get_commit_graph_chain_filename(ctx->odb_source); - hold_lock_file_for_update_mode(&lk, lock_name, - LOCK_DIE_ON_ERROR, 0444); + repo_hold_lock_file_for_update_mode(ctx->r, &lk, lock_name, + LOCK_DIE_ON_ERROR, 0444); free(lock_name); graph_layer = mks_tempfile_m(ctx->graph_name, 0444); @@ -2146,8 +2146,9 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) f = hashfd(ctx->r->hash_algo, get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer)); } else { - hold_lock_file_for_update_mode(&lk, ctx->graph_name, - LOCK_DIE_ON_ERROR, 0444); + repo_hold_lock_file_for_update_mode(ctx->r, &lk, + ctx->graph_name, + LOCK_DIE_ON_ERROR, 0444); f = hashfd(ctx->r->hash_algo, get_lock_file_fd(&lk), get_lock_file_path(&lk)); } diff --git a/commit-reach.c b/commit-reach.c index d7221fe389c046..b53c6b1cdff3f3 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -761,7 +761,8 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) /* * Test whether the candidate is contained in the list. - * Do not recurse to find out, though, but return -1 if inconclusive. + * Do not recurse to find out, though, but return CONTAINS_UNKNOWN if + * inconclusive. */ static enum contains_result contains_test(struct commit *candidate, const struct commit_list *want, @@ -818,6 +819,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate, if (result != CONTAINS_UNKNOWN) return result; + *contains_cache_at(cache, candidate) = CONTAINS_IN_PROGRESS; push_to_contains_stack(candidate, &contains_stack); while (contains_stack.nr) { struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1]; @@ -829,8 +831,8 @@ static enum contains_result contains_tag_algo(struct commit *candidate, contains_stack.nr--; } /* - * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful yes/no. + * A parent may have just been popped and marked, or may still + * be active when replacement refs create a cycle. */ else switch (contains_test(parents->item, want, cache, cutoff)) { case CONTAINS_YES: @@ -840,7 +842,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, case CONTAINS_NO: entry->parents = parents->next; break; + case CONTAINS_IN_PROGRESS: + die(_("commit ancestry contains a cycle")); case CONTAINS_UNKNOWN: + *contains_cache_at(cache, parents->item) = + CONTAINS_IN_PROGRESS; push_to_contains_stack(parents->item, &contains_stack); break; } @@ -852,9 +858,16 @@ static enum contains_result contains_tag_algo(struct commit *candidate, int commit_contains(struct ref_filter *filter, struct commit *commit, struct commit_list *list, struct contains_cache *cache) { - if (filter->with_commit_tag_algo) + int result; + + if (filter->with_commit_tag_algo || + generation_numbers_enabled(the_repository)) return contains_tag_algo(commit, list, cache) == CONTAINS_YES; - return repo_is_descendant_of(the_repository, commit, list); + + result = repo_is_descendant_of(the_repository, commit, list); + if (result < 0) + die(_("failed to check reachability")); + return result; } int can_all_from_reach_with_flag(struct object_array *from, diff --git a/commit-reach.h b/commit-reach.h index 3f3a563d8a5dd1..f908d305b16902 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -73,7 +73,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid); enum contains_result { CONTAINS_UNKNOWN = 0, CONTAINS_NO, - CONTAINS_YES + CONTAINS_YES, + CONTAINS_IN_PROGRESS }; define_commit_slab(contains_cache, enum contains_result); diff --git a/compat/mingw.c b/compat/mingw.c index 3eca3a7f2e87b2..e0fbd2c66de23d 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -7,6 +7,7 @@ #include "config.h" #include "dir.h" #include "environment.h" +#include "repository.h" #include "gettext.h" #include "run-command.h" #include "strbuf.h" @@ -1044,7 +1045,7 @@ int mingw_chdir(const char *dirname) if (xutftowcs_path(wdirname, dirname) < 0) return -1; - if (has_symlinks) { + if (repo_has_symlinks(the_repository)) { HANDLE hnd = CreateFileW(wdirname, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); @@ -2914,7 +2915,7 @@ int symlink(const char *target, const char *link) int len; /* fail if symlinks are disabled or API is not supported (WinXP) */ - if (!has_symlinks) { + if (!repo_has_symlinks(the_repository)) { errno = ENOSYS; return -1; } @@ -3184,15 +3185,23 @@ static void setup_windows_environment(void) if (!tmp && (tmp = getenv("USERPROFILE"))) setenv("HOME", tmp, 1); } +} +int mingw_platform_has_symlinks(void) +{ + static int has_symlinks = -1; /* * Change 'core.symlinks' default to false, unless native symlinks are * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can * run the test suite (which doesn't obey config files) with or without * symlink support. */ - if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict")) - has_symlinks = 0; + if (has_symlinks < 0) { + const char *tmp = getenv("MSYS"); + has_symlinks = (tmp && strstr(tmp, "winsymlinks:nativestrict")) ? 1 : 0; + } + + return has_symlinks; } static void get_current_user_sid(PSID *sid, HANDLE *linked_token) diff --git a/compat/mingw.h b/compat/mingw.h index 444daedfa52469..df02aeb632d8c3 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -208,6 +208,9 @@ void open_in_gdb(void); */ int err_win_to_posix(DWORD winerr); +int mingw_platform_has_symlinks(void); +#define platform_has_symlinks() mingw_platform_has_symlinks() + #ifndef NO_UNIX_SOCKETS int mingw_have_unix_sockets(void); #undef have_unix_sockets diff --git a/config.c b/config.c index 9d6d7872d76c10..1bdd702e7a3969 100644 --- a/config.c +++ b/config.c @@ -2954,6 +2954,24 @@ char *git_config_prepare_comment_string(const char *comment) return prepared; } +/* + * How long to retry acquiring config.lock when another process holds + * it. Default matches core.packedRefsTimeout; override via + * core.configLockTimeout. + */ +static long config_lock_timeout_ms(struct repository *r) +{ + static int configured; + static int timeout_ms = 1000; + + if (!configured) { + repo_config_get_int(r, "core.configlocktimeout", &timeout_ms); + configured = 1; + } + + return timeout_ms; +} + static void validate_comment_string(const char *comment) { size_t leading_blanks; @@ -3037,7 +3055,8 @@ int repo_config_set_multivar_in_file_gently(struct repository *r, * The lock serves a purpose in addition to locking: the new * contents of .git/config will be written into it. */ - fd = hold_lock_file_for_update(&lock, config_filename, 0); + fd = repo_hold_lock_file_for_update_timeout(r, &lock, config_filename, 0, + config_lock_timeout_ms(r)); if (fd < 0) { error_errno(_("could not lock config file %s"), config_filename); ret = CONFIG_NO_LOCK; @@ -3382,7 +3401,9 @@ static int repo_config_copy_or_rename_section_in_file( if (!config_filename) config_filename = filename_buf = repo_git_path(r, "config"); - out_fd = hold_lock_file_for_update(&lock, config_filename, 0); + out_fd = repo_hold_lock_file_for_update_timeout(r, &lock, + config_filename, 0, + config_lock_timeout_ms(r)); if (out_fd < 0) { ret = error(_("could not lock config file %s"), config_filename); goto out; diff --git a/connect.c b/connect.c index 1d74c1eda21f93..feb0420b0f7e3e 100644 --- a/connect.c +++ b/connect.c @@ -709,6 +709,40 @@ int server_supports(const char *feature) return !!server_feature_value(feature, NULL); } +void write_command_and_capabilities(struct strbuf *req_buf, const char *command, + const struct string_list *server_options) +{ + const char *hash_name; + int advertise_sid = 0; + + repo_config_get_bool(the_repository, "transfer.advertisesid", &advertise_sid); + + ensure_server_supports_v2(command); + packet_buf_write(req_buf, "command=%s", command); + if (server_supports_v2("agent")) + packet_buf_write(req_buf, "agent=%s", git_user_agent_sanitized()); + if (advertise_sid && server_supports_v2("session-id")) + packet_buf_write(req_buf, "session-id=%s", trace2_session_id()); + if (server_options && server_options->nr) { + ensure_server_supports_v2("server-option"); + for (size_t i = 0; i < server_options->nr; i++) + packet_buf_write(req_buf, "server-option=%s", + server_options->items[i].string); + } + + if (server_feature_v2("object-format", &hash_name)) { + const unsigned int hash_algo = hash_algo_by_name(hash_name); + if (hash_algo_by_ptr(the_hash_algo) != hash_algo) + die(_("mismatched algorithms: client %s; server %s"), + the_hash_algo->name, hash_name); + packet_buf_write(req_buf, "object-format=%s", the_hash_algo->name); + } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) { + die(_("the server does not support algorithm '%s'"), + the_hash_algo->name); + } + packet_buf_delim(req_buf); +} + static const char *url_scheme_name(enum url_scheme scheme) { switch (scheme) { diff --git a/connect.h b/connect.h index aa482a37fb4da4..957e5fe2b9eccb 100644 --- a/connect.h +++ b/connect.h @@ -34,4 +34,12 @@ void check_stateless_delimiter(int stateless_rpc, struct packet_reader *reader, const char *error); +struct string_list; +/* + * Write a protocol v2 command request, along with the capability + * advertisements, into req_buf. + */ +void write_command_and_capabilities(struct strbuf *req_buf, const char *command, + const struct string_list *server_options); + #endif diff --git a/copy.c b/copy.c index b668209b6c24fd..6074132050ca6f 100644 --- a/copy.c +++ b/copy.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "copy.h" #include "path.h" @@ -35,7 +33,8 @@ static int copy_times(const char *dst, const char *src) return 0; } -int copy_file(const char *dst, const char *src, int mode) +int copy_file(struct repository *repo, + const char *dst, const char *src, int mode) { int fdi, fdo, status; @@ -59,15 +58,16 @@ int copy_file(const char *dst, const char *src, int mode) if (close(fdo) != 0) return error_errno("%s: close error", dst); - if (!status && adjust_shared_perm(the_repository, dst)) + if (!status && adjust_shared_perm(repo, dst)) return -1; return status; } -int copy_file_with_time(const char *dst, const char *src, int mode) +int copy_file_with_time(struct repository *repo, + const char *dst, const char *src, int mode) { - int status = copy_file(dst, src, mode); + int status = copy_file(repo, dst, src, mode); if (!status) return copy_times(dst, src); return status; diff --git a/copy.h b/copy.h index 2af77cba8649b3..1059b118d615ec 100644 --- a/copy.h +++ b/copy.h @@ -1,10 +1,14 @@ #ifndef COPY_H #define COPY_H +struct repository; + #define COPY_READ_ERROR (-2) #define COPY_WRITE_ERROR (-3) int copy_fd(int ifd, int ofd); -int copy_file(const char *dst, const char *src, int mode); -int copy_file_with_time(const char *dst, const char *src, int mode); +int copy_file(struct repository *repo, + const char *dst, const char *src, int mode); +int copy_file_with_time(struct repository *repo, + const char *dst, const char *src, int mode); #endif /* COPY_H */ diff --git a/diff-lib.c b/diff-lib.c index ae91027a024eec..46cae637ecda83 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -160,7 +160,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) changed = check_removed(ce, &st); if (!changed) - wt_mode = ce_mode_from_stat(ce, st.st_mode); + wt_mode = ce_mode_from_stat(revs->repo, ce, st.st_mode); else { if (changed < 0) { perror(ce->name); @@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) num_compare_stages++; oidcpy(&dpath->parent[stage - 2].oid, &nce->oid); - dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode); + dpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo, nce, mode); dpath->parent[stage-2].status = DIFF_STATUS_MODIFIED; } @@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) continue; } else if (revs->diffopt.ita_invisible_in_index && ce_intent_to_add(ce)) { - newmode = ce_mode_from_stat(ce, st.st_mode); + newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode); diff_addremove(&revs->diffopt, '+', newmode, null_oid(the_hash_algo), 0, ce->name, 0); continue; @@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) changed = match_stat_with_submodule(&revs->diffopt, ce, &st, ce_option, &dirty_submodule); - newmode = ce_mode_from_stat(ce, st.st_mode); + newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode); } if (!changed && !dirty_submodule) { @@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce, changed = match_stat_with_submodule(diffopt, ce, &st, 0, dirty_submodule); if (changed) { - mode = ce_mode_from_stat(ce, st.st_mode); + mode = ce_mode_from_stat(diffopt->repo, ce, st.st_mode); oid = null_oid(the_hash_algo); } } diff --git a/dir.c b/dir.c index 1094d7be3c16ec..95d8a1cce90f77 100644 --- a/dir.c +++ b/dir.c @@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude") void setup_standard_excludes(struct dir_struct *dir) { + const char *excludes_file = repo_excludes_file(the_repository); + dir->exclude_per_dir = ".gitignore"; /* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */ - if (!excludes_file) - excludes_file = xdg_config_home("ignore"); if (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) add_patterns_from_file_1(dir, excludes_file, dir->untracked ? &dir->internal.ss_excludes_file : NULL); diff --git a/editor.c b/editor.c index fd174e6a034f1c..0d1cb8768db1a0 100644 --- a/editor.c +++ b/editor.c @@ -29,8 +29,8 @@ const char *git_editor(void) const char *editor = getenv("GIT_EDITOR"); int terminal_is_dumb = is_terminal_dumb(); - if (!editor && editor_program) - editor = editor_program; + if (!editor) + editor = repo_config_values(the_repository)->editor_program; if (!editor && !terminal_is_dumb) editor = getenv("VISUAL"); if (!editor) diff --git a/entry.c b/entry.c index 6b79884e3246d6..1c4f0f44070ea3 100644 --- a/entry.c +++ b/entry.c @@ -319,7 +319,8 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca * We can't make a real symlink; write out a regular file entry * with the symlink destination as its contents. */ - if (!has_symlinks || to_tempfile) + if (!repo_has_symlinks(state->istate && state->istate->repo ? + state->istate->repo : the_repository) || to_tempfile) goto write_file_entry; ret = symlink(new_blob, path); diff --git a/environment.c b/environment.c index c663113e8a6dcc..76ee65e62b4823 100644 --- a/environment.c +++ b/environment.c @@ -41,31 +41,21 @@ static int pack_compression_seen; static int zlib_compression_seen; -int trust_executable_bit = 1; -int has_symlinks = 1; int minimum_abbrev = 4, default_abbrev = -1; int assume_unchanged; char *git_commit_encoding; char *git_log_output_encoding; -char *apply_default_whitespace; -char *apply_default_ignorewhitespace; int fsync_object_files = -1; int use_fsync = -1; enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT; -char *editor_program; -char *askpass_program; -char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; char *check_roundtrip_encoding; -enum rebase_setup_type autorebase = AUTOREBASE_NEVER; -enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #ifndef OBJECT_CREATION_MODE #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif -enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_keep_true_parents; unsigned long pack_size_limit_cfg; @@ -148,6 +138,30 @@ int repo_ignore_case(struct repository *repo) 0; } +int repo_trust_executable_bit(struct repository *repo) +{ + return repo->initialized + ? repo_config_values(repo)->trust_executable_bit + : 1; +} + +int repo_has_symlinks(struct repository *repo) +{ + return repo->initialized + ? repo_config_values(repo)->has_symlinks + : platform_has_symlinks(); +} + +const char *repo_excludes_file(struct repository *repo) +{ + struct repo_config_values *cfg = repo_config_values(repo); + + if (!cfg->excludes_file) + cfg->excludes_file = xdg_config_home("ignore"); + + return cfg->excludes_file; +} + int have_git_dir(void) { return startup_info->have_repository @@ -311,7 +325,7 @@ int git_default_core_config(const char *var, const char *value, /* This needs a better name */ if (!strcmp(var, "core.filemode")) { - trust_executable_bit = git_config_bool(var, value); + cfg->trust_executable_bit = git_config_bool(var, value); return 0; } if (!strcmp(var, "core.trustctime")) { @@ -336,7 +350,8 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.symlinks")) { - has_symlinks = git_config_bool(var, value); + struct repo_config_values *cfg = repo_config_values(the_repository); + cfg->has_symlinks = git_config_bool(var, value); return 0; } @@ -442,8 +457,8 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.editor")) { - FREE_AND_NULL(editor_program); - return git_config_string(&editor_program, var, value); + FREE_AND_NULL(cfg->editor_program); + return git_config_string(&cfg->editor_program, var, value); } if (!strcmp(var, "core.commentchar") || @@ -470,13 +485,13 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.askpass")) { - FREE_AND_NULL(askpass_program); - return git_config_string(&askpass_program, var, value); + FREE_AND_NULL(cfg->askpass_program); + return git_config_string(&cfg->askpass_program, var, value); } if (!strcmp(var, "core.excludesfile")) { - FREE_AND_NULL(excludes_file); - return git_config_pathname(&excludes_file, var, value); + FREE_AND_NULL(cfg->excludes_file); + return git_config_pathname(&cfg->excludes_file, var, value); } if (!strcmp(var, "core.whitespace")) { @@ -523,9 +538,9 @@ int git_default_core_config(const char *var, const char *value, if (!value) return config_error_nonbool(var); if (!strcmp(value, "rename")) - object_creation_mode = OBJECT_CREATION_USES_RENAMES; + cfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES; else if (!strcmp(value, "link")) - object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; + cfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; else die(_("invalid mode for object creation: %s"), value); return 0; @@ -611,13 +626,13 @@ static int git_default_branch_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); else if (!strcmp(value, "never")) - autorebase = AUTOREBASE_NEVER; + cfg->autorebase = AUTOREBASE_NEVER; else if (!strcmp(value, "local")) - autorebase = AUTOREBASE_LOCAL; + cfg->autorebase = AUTOREBASE_LOCAL; else if (!strcmp(value, "remote")) - autorebase = AUTOREBASE_REMOTE; + cfg->autorebase = AUTOREBASE_REMOTE; else if (!strcmp(value, "always")) - autorebase = AUTOREBASE_ALWAYS; + cfg->autorebase = AUTOREBASE_ALWAYS; else return error(_("malformed value for %s"), var); return 0; @@ -629,21 +644,23 @@ static int git_default_branch_config(const char *var, const char *value) static int git_default_push_config(const char *var, const char *value) { + struct repo_config_values *cfg = repo_config_values(the_repository); + if (!strcmp(var, "push.default")) { if (!value) return config_error_nonbool(var); else if (!strcmp(value, "nothing")) - push_default = PUSH_DEFAULT_NOTHING; + cfg->push_default = PUSH_DEFAULT_NOTHING; else if (!strcmp(value, "matching")) - push_default = PUSH_DEFAULT_MATCHING; + cfg->push_default = PUSH_DEFAULT_MATCHING; else if (!strcmp(value, "simple")) - push_default = PUSH_DEFAULT_SIMPLE; + cfg->push_default = PUSH_DEFAULT_SIMPLE; else if (!strcmp(value, "upstream")) - push_default = PUSH_DEFAULT_UPSTREAM; + cfg->push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "tracking")) /* deprecated */ - push_default = PUSH_DEFAULT_UPSTREAM; + cfg->push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "current")) - push_default = PUSH_DEFAULT_CURRENT; + cfg->push_default = PUSH_DEFAULT_CURRENT; else { error(_("malformed value for %s: %s"), var, value); return error(_("must be one of nothing, matching, simple, " @@ -729,10 +746,21 @@ int git_default_config(const char *var, const char *value, void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; + cfg->excludes_file = NULL; + cfg->editor_program = NULL; + cfg->pager_program = NULL; + cfg->askpass_program = NULL; + cfg->apply_default_whitespace = NULL; + cfg->apply_default_ignorewhitespace = NULL; + cfg->push_default = PUSH_DEFAULT_UNSPECIFIED; + cfg->autorebase = AUTOREBASE_NEVER; + cfg->object_creation_mode = OBJECT_CREATION_MODE; cfg->apply_sparse_checkout = 0; cfg->protect_hfs = PROTECT_HFS_DEFAULT; cfg->protect_ntfs = PROTECT_NTFS_DEFAULT; cfg->ignore_case = 0; + cfg->trust_executable_bit = 1; + cfg->has_symlinks = platform_has_symlinks(); cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; cfg->check_stat = 1; @@ -743,3 +771,14 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->sparse_expect_files_outside_of_patterns = 0; cfg->warn_on_object_refname_ambiguity = 1; } + +void repo_config_values_clear(struct repo_config_values *cfg) +{ + FREE_AND_NULL(cfg->attributes_file); + FREE_AND_NULL(cfg->excludes_file); + FREE_AND_NULL(cfg->editor_program); + FREE_AND_NULL(cfg->pager_program); + FREE_AND_NULL(cfg->askpass_program); + FREE_AND_NULL(cfg->apply_default_whitespace); + FREE_AND_NULL(cfg->apply_default_ignorewhitespace); +} diff --git a/environment.h b/environment.h index acfb670be107a0..e7ec5b0437342d 100644 --- a/environment.h +++ b/environment.h @@ -87,9 +87,45 @@ extern const char * const local_repo_env[]; struct strvec; struct repository; + +/* + * NEEDSWORK: It would be better if these definitions could be moved to + * other more specific files, but care is needed to avoid circular + * inclusion issues. + */ +enum push_default_type { + PUSH_DEFAULT_NOTHING = 0, + PUSH_DEFAULT_MATCHING, + PUSH_DEFAULT_SIMPLE, + PUSH_DEFAULT_UPSTREAM, + PUSH_DEFAULT_CURRENT, + PUSH_DEFAULT_UNSPECIFIED +}; + +enum rebase_setup_type { + AUTOREBASE_NEVER = 0, + AUTOREBASE_LOCAL, + AUTOREBASE_REMOTE, + AUTOREBASE_ALWAYS +}; + +enum object_creation_mode { + OBJECT_CREATION_USES_HARDLINKS = 0, + OBJECT_CREATION_USES_RENAMES = 1 +}; + struct repo_config_values { /* section "core" config values */ char *attributes_file; + char *excludes_file; + char *editor_program; + char *pager_program; + char *askpass_program; + char *apply_default_whitespace; + char *apply_default_ignorewhitespace; + enum push_default_type push_default; + enum rebase_setup_type autorebase; + enum object_creation_mode object_creation_mode; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -101,6 +137,8 @@ struct repo_config_values { int protect_hfs; int protect_ntfs; int ignore_case; + int trust_executable_bit; + int has_symlinks; /* section "sparse" config values */ int sparse_expect_files_outside_of_patterns; @@ -151,10 +189,25 @@ int repo_protect_ntfs(struct repository *repo); */ int repo_ignore_case(struct repository *repo); +int repo_trust_executable_bit(struct repository *repo); + +int repo_has_symlinks(struct repository *repo); + +const char *repo_excludes_file(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); int is_bare_repository(struct repository *repo); +/* + * Frees memory allocated for dynamically loaded configuration values + * inside `repo_config_values`. + * + * As dynamically allocated variables are migrated into this struct, + * their FREE_AND_NULL() calls should be appended here. + */ +void repo_config_values_clear(struct repo_config_values *cfg); + /* * TODO: All the below state either explicitly or implicitly relies on * `the_repository`. We should eventually get rid of these and make the @@ -178,38 +231,10 @@ int is_bare_repository(struct repository *repo); int have_git_dir(void); /* Environment bits from configuration mechanism */ -extern int trust_executable_bit; -extern int has_symlinks; extern int minimum_abbrev, default_abbrev; extern int assume_unchanged; -extern char *apply_default_whitespace; -extern char *apply_default_ignorewhitespace; extern unsigned long pack_size_limit_cfg; -enum rebase_setup_type { - AUTOREBASE_NEVER = 0, - AUTOREBASE_LOCAL, - AUTOREBASE_REMOTE, - AUTOREBASE_ALWAYS -}; -extern enum rebase_setup_type autorebase; - -enum push_default_type { - PUSH_DEFAULT_NOTHING = 0, - PUSH_DEFAULT_MATCHING, - PUSH_DEFAULT_SIMPLE, - PUSH_DEFAULT_UPSTREAM, - PUSH_DEFAULT_CURRENT, - PUSH_DEFAULT_UNSPECIFIED -}; -extern enum push_default_type push_default; - -enum object_creation_mode { - OBJECT_CREATION_USES_HARDLINKS = 0, - OBJECT_CREATION_USES_RENAMES = 1 -}; -extern enum object_creation_mode object_creation_mode; - extern int grafts_keep_true_parents; const char *get_log_output_encoding(void); @@ -218,10 +243,6 @@ const char *get_commit_output_encoding(void); extern char *git_commit_encoding; extern char *git_log_output_encoding; -extern char *editor_program; -extern char *askpass_program; -extern char *excludes_file; - /* * The character that begins a commented line in user-editable file * that is subject to stripspace. diff --git a/fetch-object-info.c b/fetch-object-info.c new file mode 100644 index 00000000000000..ba7e179c44ee54 --- /dev/null +++ b/fetch-object-info.c @@ -0,0 +1,156 @@ +#include "git-compat-util.h" +#include "gettext.h" +#include "hex.h" +#include "pkt-line.h" +#include "connect.h" +#include "oid-array.h" +#include "odb.h" +#include "fetch-object-info.h" +#include "string-list.h" + +/* Sends object-info command and its arguments into the request buffer. */ +static void send_object_info_request(const int fd_out, struct object_info_args *args) +{ + struct strbuf req_buf = STRBUF_INIT; + + write_command_and_capabilities(&req_buf, "object-info", args->server_options); + + if (unsorted_string_list_has_string(args->object_info_options, "size")) + packet_buf_write(&req_buf, "size"); + else if (args->object_info_options->nr) + BUG("only size should be in object_info_options"); + + if (args->oids) + for (size_t i = 0; i < args->oids->nr; i++) + packet_buf_write(&req_buf, "oid %s", oid_to_hex(&args->oids->oid[i])); + + packet_buf_flush(&req_buf); + if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0) + die_errno(_("unable to write request to remote")); + + strbuf_release(&req_buf); +} + +static int parse_object_size(const char *s, size_t *res) +{ + uintmax_t uim; + + if (!s[0] || s[strspn(s, "0123456789")]) + return -1; + errno = 0; + uim = strtoumax(s, NULL, 10); + if (errno || uim > SIZE_MAX) + return -1; + *res = uim; + return 0; +} + +int fetch_object_info(const enum protocol_version version, struct object_info_args *args, + struct packet_reader *reader, struct object_info *object_info_data, + const int stateless_rpc, const int fd_out) +{ + int size_index = -1; + + switch (version) { + case protocol_v2: + if (!server_supports_v2("object-info")) + die(_("object-info capability is not enabled on the server")); + /* + * When removing an element from the list it gets swapped by the + * last element, iterate backwards to prevent elements skipping + * evaluation. + * + * object_info_options->nr can be safely casted without overflow + * because the number of options is a small known number (the + * supported placeholders which currently are size and type). + */ + for (int i = (int)args->object_info_options->nr - 1; i >= 0; i--) + if (!server_supports_feature("object-info", + args->object_info_options->items[i].string, 0)) + unsorted_string_list_delete_item(args->object_info_options, i, 0); + + /* + * Even if no options are left, we still send the oid so we get + * at least an existence check. + */ + send_object_info_request(fd_out, args); + break; + case protocol_v1: + case protocol_v0: + die(_("object-info requires protocol v2")); + case protocol_unknown_version: + BUG("unknown protocol version"); + } + + for (size_t i = 0; i < args->object_info_options->nr; i++) { + if (packet_reader_read(reader) != PACKET_READ_NORMAL) { + check_stateless_delimiter(stateless_rpc, reader, + "stateless delimiter expected"); + return -1; + } + + if (!unsorted_string_list_has_string(args->object_info_options, reader->line)) + return -1; + + if (!strcmp(reader->line, "size")) { + /* + * i is the number of supported options which currently + * is only size. No risk of overflow. + */ + size_index = (int)i; + for (size_t j = 0; j < args->oids->nr; j++) + object_info_data[j].sizep = + xcalloc(1, sizeof(*object_info_data[j].sizep)); + } else { + BUG("only size is supported"); + } + } + + for (size_t i = 0; + packet_reader_read(reader) == PACKET_READ_NORMAL && + i < args->oids->nr; + i++) { + struct string_list object_info_values = STRING_LIST_INIT_DUP; + + string_list_split(&object_info_values, reader->line, " ", -1); + + if (strcmp(object_info_values.items[0].string, + oid_to_hex(&args->oids->oid[i]))) + die(_("object-info: expected OID: %s, got %s"), + oid_to_hex(&args->oids->oid[i]), + object_info_values.items[0].string); + + /* + * If the response is two elements but the second one is an + * empty string, that means that the OID is unrecognized by the + * server. + */ + if (object_info_values.nr >= 2 && + !strcmp(object_info_values.items[1].string, "")) { + object_info_data[i].unrecognized = 1; + string_list_clear(&object_info_values, 0); + continue; + } + + /* + * Because we filter the options to be only the supported by + * the server we expect the server to answer with the same + * number of attributes requested. + */ + if (args->object_info_options->nr + 1 != object_info_values.nr) + die("object-info: unexpected number of attributes: %s", + reader->line); + + if (size_index >= 0 && + parse_object_size(object_info_values.items[size_index + 1].string, + object_info_data[i].sizep)) + die("object-info: ref %s has invalid size %s", + object_info_values.items[0].string, + object_info_values.items[size_index + 1].string); + + string_list_clear(&object_info_values, 0); + } + check_stateless_delimiter(stateless_rpc, reader, "stateless delimiter expected"); + + return 0; +} diff --git a/fetch-object-info.h b/fetch-object-info.h new file mode 100644 index 00000000000000..269cebb3f7df48 --- /dev/null +++ b/fetch-object-info.h @@ -0,0 +1,25 @@ +#ifndef FETCH_OBJECT_INFO_H +#define FETCH_OBJECT_INFO_H + +#include "pkt-line.h" +#include "protocol.h" + +struct object_info_args { + struct string_list *object_info_options; + const struct string_list *server_options; + struct oid_array *oids; +}; + +struct object_info; +/* + * Sends git-cat-file object-info command into the request buf and read the + * results from packets. + * + * Modifies args->object_info_options, on return it contains only the supported + * options by the server. + */ +int fetch_object_info(enum protocol_version version, struct object_info_args *args, + struct packet_reader *reader, struct object_info *object_info_data, + int stateless_rpc, int fd_out); + +#endif /* FETCH_OBJECT_INFO_H */ diff --git a/fetch-pack.c b/fetch-pack.c index 29c41132ee0495..922a9b25812c68 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1; static int transfer_fsck_objects = -1; static int agent_supported; static int server_supports_filtering; -static int advertise_sid; static struct shallow_lock shallow_lock; static const char *alternate_shallow_file; static struct strbuf fsck_msg_types = STRBUF_INIT; @@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator, size_t state_len = 0; struct packet_reader reader; struct oidset negotiation_include_oids = OIDSET_INIT; + int advertise_sid = 0; + + repo_config_get_bool(the_repository, "transfer.advertisesid", &advertise_sid); if (args->stateless_rpc && multi_ack == 1) die(_("the option '%s' requires '%s'"), "--stateless-rpc", "multi_ack_detailed"); @@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator, if (deepen_not_ok) strbuf_addstr(&c, " deepen-not"); if (agent_supported) strbuf_addf(&c, " agent=%s", git_user_agent_sanitized()); - if (advertise_sid) + if (advertise_sid && server_supports("session-id")) strbuf_addf(&c, " session-id=%s", trace2_session_id()); if (args->filter_options.choice) strbuf_addstr(&c, " filter"); @@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, (int)agent_len, agent_feature); } - if (!server_supports("session-id")) - advertise_sid = 0; - if (server_supports("shallow")) print_verbose(args, _("Server supports %s"), "shallow"); else if (args->depth > 0 || is_repository_shallow(r)) @@ -1376,38 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator, return haves_added; } -static void write_fetch_command_and_capabilities(struct strbuf *req_buf, - const struct string_list *server_options) -{ - const char *hash_name; - - ensure_server_supports_v2("fetch"); - packet_buf_write(req_buf, "command=fetch"); - if (server_supports_v2("agent")) - packet_buf_write(req_buf, "agent=%s", git_user_agent_sanitized()); - if (advertise_sid && server_supports_v2("session-id")) - packet_buf_write(req_buf, "session-id=%s", trace2_session_id()); - if (server_options && server_options->nr) { - int i; - ensure_server_supports_v2("server-option"); - for (i = 0; i < server_options->nr; i++) - packet_buf_write(req_buf, "server-option=%s", - server_options->items[i].string); - } - - if (server_feature_v2("object-format", &hash_name)) { - int hash_algo = hash_algo_by_name(hash_name); - if (hash_algo_by_ptr(the_hash_algo) != hash_algo) - die(_("mismatched algorithms: client %s; server %s"), - the_hash_algo->name, hash_name); - packet_buf_write(req_buf, "object-format=%s", the_hash_algo->name); - } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) { - die(_("the server does not support algorithm '%s'"), - the_hash_algo->name); - } - packet_buf_delim(req_buf); -} - static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, struct fetch_pack_args *args, const struct ref *wants, struct oidset *common, @@ -1419,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, int done_sent = 0; struct strbuf req_buf = STRBUF_INIT; - write_fetch_command_and_capabilities(&req_buf, args->server_options); + write_command_and_capabilities(&req_buf, "fetch", args->server_options); if (args->use_thin_pack) packet_buf_write(&req_buf, "thin-pack"); @@ -1768,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, reader.me = "fetch-pack"; } + /* v2 supports these by default */ + allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1; + use_sideband = 2; + if (args->depth > 0 || args->deepen_since || args->deepen_not) + args->deepen = 1; + while (state != FETCH_DONE) { switch (state) { case FETCH_CHECK_LOCAL: sort_ref_list(&ref, ref_compare_name); QSORT(sought, nr_sought, cmp_ref_by_name); - /* v2 supports these by default */ - allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1; - use_sideband = 2; - if (args->depth > 0 || args->deepen_since || args->deepen_not) - args->deepen = 1; - /* Filter 'ref' by 'sought' and those that aren't local */ mark_complete_and_common_ref(negotiator, args, &ref); filter_refs(args, &ref, sought, nr_sought); @@ -1999,7 +1966,6 @@ static void fetch_pack_config(void) repo_config_get_bool(the_repository, "repack.usedeltabaseoffset", &prefer_ofs_delta); repo_config_get_bool(the_repository, "fetch.fsckobjects", &fetch_fsck_objects); repo_config_get_bool(the_repository, "transfer.fsckobjects", &transfer_fsck_objects); - repo_config_get_bool(the_repository, "transfer.advertisesid", &advertise_sid); if (!uri_protocols.nr) { char *str; @@ -2287,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips, the_repository, "%d", negotiation_round); strbuf_reset(&req_buf); - write_fetch_command_and_capabilities(&req_buf, server_options); + write_command_and_capabilities(&req_buf, "fetch", server_options); packet_buf_write(&req_buf, "wait-for-done"); diff --git a/git-compat-util.h b/git-compat-util.h index 88097764078538..a0f901ce793ed6 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c) #define is_dir_sep git_is_dir_sep #endif +#ifndef platform_has_symlinks +#define platform_has_symlinks() 1 +#endif + #ifndef offset_1st_component static inline int git_offset_1st_component(const char *path) { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fde804593b6ff3..8c2d9b8eefe4db 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2339,12 +2339,14 @@ sub format_extended_diff_header_line { $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"}, esc_path($to->{'file'})); } - # match single - if ($line =~ m/\s(\d{6})$/) { - $line .= ' (' . - file_type_long($1) . - ')'; + + # Temporarily remove a trailing so an index line ends with its + # object IDs and can be shortened below. + my $mode; + if ($line =~ s/\s(\d{6})$//) { + $mode = $1; } + # match if ($line =~ oid_nlen_prefix_infix_regex($sha1_len, "index ", ",") | $line =~ oid_nlen_prefix_infix_regex($sha256_len, "index ", ",")) { @@ -2388,6 +2390,12 @@ sub format_extended_diff_header_line { my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; } + if (defined $mode) { + $line .= " $mode" . + ' (' . + file_type_long($mode) . + ')'; + } return $line . "
\n"; } diff --git a/graph.c b/graph.c index 842282685f6cef..28bef1b88f5a7a 100644 --- a/graph.c +++ b/graph.c @@ -60,12 +60,23 @@ struct column { * index into column_colors. */ unsigned short color; + /* + * Marks if a commit is a non-first parent of a merge. These columns are + * already visually connected to the merge commit and do not need + * indentation. + * + * The first parent is the one that inherits the column and it can need + * indentation if turns out to be a visual root and there's still + * commits to render. + */ + unsigned int is_merge_parent:1; }; enum graph_state { GRAPH_PADDING, GRAPH_SKIP, GRAPH_PRE_COMMIT, + GRAPH_PRE_ROOT, GRAPH_COMMIT, GRAPH_POST_MERGE, GRAPH_COLLAPSING @@ -315,6 +326,59 @@ struct git_graph { * diff_output_prefix_callback(). */ struct strbuf prefix_buf; + + /* + * Lookahead buffer: up to 2 pre-fetched commits that will be shown. + * Populated by get_revision() so graph_peek_next_visible() can use + * actual walk results instead of peeking at rev_info internals. + */ + struct commit *lookahead[2]; + int lookahead_nr; + + /* + * If a commit is a visual root, we need to indent it to prevent + * unrelated commits from being vertically adjacent to it. + */ + unsigned int is_visual_root:1; + + /* + * Indentation increases for each visual root adjacent to another visual + * root, making visual root commits indentation cascade. + */ + unsigned int visual_root_depth; + + /* + * When a visual root is adjacent to other visual roots, the first one + * can avoid indentation and the rest cascades, increasing the indentation + * for each one. + */ + unsigned int visual_root_cascade:1; + + /* + * Set when the current commit was already present in graph->columns + * before being processed. + */ + unsigned int commit_in_columns:1; +}; + +struct graph_lookahead_flags { + + /* + * Set when there will be a commit after the current one that will be + * rendered. + */ + unsigned int is_next_visible:1; + + /* + * Set when the next visible commit is candidate to be a visual root. + */ + unsigned int is_next_visual_root:1; + + /* + * Set when the next visible commit will be rendered under the current + * commit. + */ + unsigned int next_has_column:1; }; static inline int graph_needs_truncation(struct git_graph *graph, int lane) @@ -353,13 +417,13 @@ void graph_setup_line_prefix(struct diff_options *diffopt) diffopt->output_prefix = diff_output_prefix_callback; } -struct git_graph *graph_init(struct rev_info *opt) +static void graph_read_config(struct rev_info *revs) { - struct git_graph *graph = xmalloc(sizeof(struct git_graph)); + int val; if (!column_colors) { char *string; - if (repo_config_get_string(opt->repo, "log.graphcolors", &string)) { + if (repo_config_get_string(revs->repo, "log.graphcolors", &string)) { /* not configured -- use default */ graph_set_column_colors(column_colors_ansi, column_colors_ansi_max); @@ -374,6 +438,16 @@ struct git_graph *graph_init(struct rev_info *opt) } } + if (!repo_config_get_bool(revs->repo, "log.graphIndent", &val)) + revs->no_graph_indent = !val; +} + +struct git_graph *graph_init(struct rev_info *opt) +{ + struct git_graph *graph = xmalloc(sizeof(struct git_graph)); + + graph_read_config(opt); + graph->commit = NULL; graph->revs = opt; graph->num_parents = 0; @@ -388,6 +462,11 @@ struct git_graph *graph_init(struct rev_info *opt) graph->num_columns = 0; graph->num_new_columns = 0; graph->mapping_size = 0; + graph->lookahead[0] = NULL; + graph->lookahead[1] = NULL; + graph->lookahead_nr = 0; + graph->visual_root_depth = 0; + graph->visual_root_cascade = 0; /* * Start the column color at the maximum value, since we'll * always increment it for the first commit we output. @@ -456,6 +535,15 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns) */ static int graph_is_interesting(struct git_graph *graph, struct commit *commit) { + /* + * Commits in the lookahead buffer have been pre-fetched by + * get_revision() and will be shown in the future. They already have + * the SHOWN flag set when they were pre-fetched but the graph still + * needs to treat them as interesting parents. + */ + for (int i = 0; i < graph->lookahead_nr; i++) + if (graph->lookahead[i] == commit) + return 1; /* * If revs->boundary is set, commits whose children have * been shown are always interesting, even if they have the @@ -561,6 +649,11 @@ static void graph_insert_into_new_columns(struct git_graph *graph, struct commit *commit, int idx) { + /* + * Get the initial merge_layout before it's modified to know if this + * is a merge. + */ + int initial_merge_layout = graph->merge_layout; int i = graph_find_new_column_by_commit(graph, commit); int mapping_idx; @@ -572,6 +665,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph, i = graph->num_new_columns++; graph->new_columns[i].commit = commit; graph->new_columns[i].color = graph_find_commit_color(graph, commit); + graph->new_columns[i].is_merge_parent = 0; } if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) { @@ -610,6 +704,12 @@ static void graph_insert_into_new_columns(struct git_graph *graph, } graph->mapping[mapping_idx] = i; + + /* + * Mark non-first parents of a merge. + */ + if (graph->num_parents > 1 && initial_merge_layout >= 0 && idx > -1) + graph->new_columns[i].is_merge_parent = 1; } static void graph_update_columns(struct git_graph *graph) @@ -701,10 +801,20 @@ static void graph_update_columns(struct git_graph *graph) if (graph->num_parents == 0) graph->width += 2; } else { + int j; graph_insert_into_new_columns(graph, col_commit, -1); + /* + * This column is not the current commit, but we need to + * propagate the flag until the commit is processed. + */ + j = graph_find_new_column_by_commit(graph, col_commit); + if (j >= 0 && graph->columns[i].is_merge_parent) + graph->new_columns[j].is_merge_parent = 1; } } + graph->commit_in_columns = is_commit_in_columns; + /* * If graph_max_lanes is set, cap the width */ @@ -763,9 +873,145 @@ static int graph_needs_pre_commit_line(struct git_graph *graph) graph->expansion_row < graph_num_expansion_rows(graph); } +struct commit *graph_pop_lookahead(struct git_graph *graph) +{ + struct commit *c; + + if (!graph->lookahead_nr) + return NULL; + + c = graph->lookahead[0]; + if (!c) + BUG("lookahead buffer has %d entries but the first one is NULL", + graph->lookahead_nr); + + graph->lookahead[0] = graph->lookahead[1]; + graph->lookahead[1] = NULL; + graph->lookahead_nr--; + return c; +} + +int graph_get_lookahead_room(struct git_graph *graph) +{ + return (int)ARRAY_SIZE(graph->lookahead) - graph->lookahead_nr; +} + +void graph_push_lookahead(struct git_graph *graph, struct commit *c) +{ + if (!graph_get_lookahead_room(graph)) + BUG("pushing into lookahead buffer when it is already full"); + + graph->lookahead[graph->lookahead_nr++] = c; +} + +/* + * A commit can be a visual root when: + * + * - It has no parents. + * + * - It has parents but they are all filtered out and + * commit->parents arrives NULL. + * + * - Its parents are uninteresting. + * + * - It is not a boundary commit. Boundary commits also have no visible + * parents, but they are not selected as visual roots because they cannot + * cause the ambiguity of being vertically adjacent because: + * + * 1. A boundary only appears because an included commit is its child. + * Children are always above, and the renderer draws an edge down to + * the boundary from that child. Rather than starting a column like a + * visual root would do, it inherits its child column. + * + * 2. Included commits cannot appear below a boundary. Boundaries are + * ancestors of the exclusion point; if an included commit were an + * ancestor of the boundary it would be excluded and not rendered. + * Boundaries therefore always sink to the bottom. + */ +static int graph_is_visual_root_candidate(struct commit *c, struct git_graph *graph) +{ + struct commit_list *p; + + if (c->object.flags & BOUNDARY) + return 0; + for (p = c->parents; p; p = p->next) + if (graph_is_interesting(graph, p->item)) + return 0; + return 1; +} + +static int graph_is_visual_root(struct git_graph *graph, + struct graph_lookahead_flags *flags) +{ + /* + * This must be only called for the current commit as graph contains + * the state for the current commit only. + * + * To check if a commit is a visual root, call graph_is_visual_root_candidate() + * but we won't know if it is really a visual root until we get to the + * next commit state. + * + * The current commit is an actual visual root if it is a candidate and + * the commit is not a non-first parent of a merge. + * + * * + * |\ + * | * <- it is a visual root candidate but it shouldn't be indented + * * because it is already connected by an edge. + * ^ if commit_in_columns && is_merge_parent means the commit + * | was put by a merge and is connected. + * | + * `-------- if !is_next_visible means we're on the last commit, avoid + * indentation unless the one before is a visual root, then + * we need to differentiate from the one above. + * + * If next_has_columns means that the next commit has + * already a column, so it will not be rendered below, the + * current commit has to act as the last commit and omit + * indentation. + */ + return graph_is_visual_root_candidate(graph->commit, graph) && + !(graph->commit_in_columns && + graph->columns[graph->commit_index].is_merge_parent) && + flags->is_next_visible && + (!flags->next_has_column || graph->visual_root_depth > 0); +} + +/* + * Peeks the next commits via the lookahead buffer and sets the lookahead flags. + */ +static void graph_peek_next_visible(struct git_graph *graph, + struct graph_lookahead_flags *flags) +{ + flags->is_next_visible = 0; + flags->is_next_visual_root = 0; + flags->next_has_column = 0; + + if (!graph->lookahead_nr) + return; + + flags->is_next_visible = 1; + flags->next_has_column = + graph_find_new_column_by_commit(graph, graph->lookahead[0]) >= 0; + + if (!graph_is_visual_root_candidate(graph->lookahead[0], graph)) + return; + + if (graph->lookahead_nr >= 2) + flags->is_next_visual_root = 1; +} + +static int graph_needs_pre_root_line(struct git_graph *graph) +{ + return graph->commit_in_columns && graph->is_visual_root && + graph->num_columns > 0 && !graph->visual_root_cascade && + !graph->revs->no_graph_indent; +} + void graph_update(struct git_graph *graph, struct commit *commit) { struct commit_list *parent; + struct graph_lookahead_flags flags; /* * Set the new commit @@ -796,6 +1042,40 @@ void graph_update(struct git_graph *graph, struct commit *commit) */ graph_update_columns(graph); + graph_peek_next_visible(graph, &flags); + + graph->is_visual_root = graph_is_visual_root(graph, &flags); + + if (graph->is_visual_root) { + /* + * If next is a visual root we can omit the indent for the first + * visual root and start cascading. + */ + if (!graph->visual_root_depth && flags.is_next_visual_root) + graph->visual_root_cascade = 1; + + /* + * We wrap the cascading at a max of four columns at most, after + * that we wrap it back to the initial column. + * + * This could cause ambiguity in case of the next commit not + * being a visual root and be at the initial column after the + * first wrap. + * + * In case of being a non-visual-root the next, stop the + * cascading to get the commit indented. + */ + if (!flags.is_next_visual_root && + graph->visual_root_depth && + !(graph->visual_root_depth % 4)) + graph->visual_root_cascade = 0; + + graph->visual_root_depth++; + } else { + graph->visual_root_depth = 0; + graph->visual_root_cascade = 0; + } + graph->expansion_row = 0; /* @@ -813,11 +1093,16 @@ void graph_update(struct git_graph *graph, struct commit *commit) * room for it. We need to do this only if there is a branch row * (or more) to the right of this commit. * + * If it is a visual root, we need to print an extra row to + * connect the indentation. + * * If there are less than 3 parents, we can immediately print the * commit line. */ if (graph->state != GRAPH_PADDING) graph->state = GRAPH_SKIP; + else if (graph_needs_pre_root_line(graph)) + graph->state = GRAPH_PRE_ROOT; else if (graph_needs_pre_commit_line(graph)) graph->state = GRAPH_PRE_COMMIT; else @@ -1065,6 +1350,20 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line if (col_commit == graph->commit) { seen_this = 1; + if (graph->is_visual_root && !graph->revs->no_graph_indent) { + int depth = graph->visual_root_depth; + /* + * Each visual column is 2 characters wide. + * Omit the indentation for the first visual + * root in cascade mode. + * + * Have a max of 4 columns when cascading, after + * that wrap it and repeat. + */ + int padding = ((depth - graph->visual_root_cascade) % 4) * 2; + graph_line_addchars(line, ' ', padding); + graph->width += padding; + } graph_output_commit_char(graph, line); if (graph_needs_truncation(graph, i)) { @@ -1436,6 +1735,30 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l graph_update_state(graph, GRAPH_PADDING); } +static void graph_output_pre_root_line(struct git_graph *graph, struct graph_line *line) +{ + /* + * This function adds a row before a visual root, to connect the + * branch to the indented commit. It must only be called on a + * visual root. + */ + if (!graph->is_visual_root) + BUG("commit must be a visual root to call pre_root_line"); + + for (int i = 0; i < graph->num_columns; i++) { + struct column *col = &graph->columns[i]; + if (col->commit == graph->commit) { + graph_line_addch(line, ' '); + graph_line_write_column(line, col, '\\'); + } else { + graph_line_write_column(line, col, '|'); + } + graph_line_addch(line, ' '); + } + + graph_update_state(graph, GRAPH_COMMIT); +} + int graph_next_line(struct git_graph *graph, struct strbuf *sb) { int shown_commit_line = 0; @@ -1461,6 +1784,9 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) case GRAPH_PRE_COMMIT: graph_output_pre_commit_line(graph, &line); break; + case GRAPH_PRE_ROOT: + graph_output_pre_root_line(graph, &line); + break; case GRAPH_COMMIT: graph_output_commit_line(graph, &line); shown_commit_line = 1; diff --git a/graph.h b/graph.h index 3fd1dcb2e94d43..1193711fb8892c 100644 --- a/graph.h +++ b/graph.h @@ -262,4 +262,21 @@ void graph_show_commit_msg(struct git_graph *graph, FILE *file, struct strbuf const *sb); +/* + * Pop the first commit from the graph's lookahead buffer. + * Returns NULL if the buffer is empty. + */ +struct commit *graph_pop_lookahead(struct git_graph *graph); + +/* + * Returns how many more commits can be added to the lookahead buffer. + */ +int graph_get_lookahead_room(struct git_graph *graph); + +/* + * Push a commit into the lookahead buffer. Must only be called when + * graph_get_lookahead_room() returns > 0. + */ +void graph_push_lookahead(struct git_graph *graph, struct commit *c); + #endif /* GRAPH_H */ diff --git a/http-push.c b/http-push.c index 60f6f8f0546cf4..94a1fac9ab0fcd 100644 --- a/http-push.c +++ b/http-push.c @@ -1716,7 +1716,7 @@ int cmd_main(int argc, const char **argv) { struct transfer_request *request; struct transfer_request *next_request; - struct refspec rs = REFSPEC_INIT_PUSH; + struct refspec rs = REFSPEC_INIT_PUSH(the_hash_algo); struct remote_lock *ref_lock = NULL; struct remote_lock *info_ref_lock = NULL; int delete_branch = 0; diff --git a/lockfile.c b/lockfile.c index 7add2f136abd85..100f60377174ef 100644 --- a/lockfile.c +++ b/lockfile.c @@ -2,11 +2,14 @@ * Copyright (c) 2005, Junio C Hamano */ +#define USE_THE_REPOSITORY_VARIABLE + #include "git-compat-util.h" #include "abspath.h" #include "gettext.h" #include "lockfile.h" #include "parse.h" +#include "repository.h" #include "strbuf.h" #include "wrapper.h" @@ -162,8 +165,8 @@ static int read_lock_pid(const char *pid_path, uintmax_t *pid_out) } /* Make sure errno contains a meaningful value on error */ -static int lock_file(struct lock_file *lk, const char *path, int flags, - int mode) +static int lock_file(struct repository *r, struct lock_file *lk, + const char *path, int flags, int mode) { struct strbuf base_path = STRBUF_INIT; struct strbuf lock_path = STRBUF_INIT; @@ -176,7 +179,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags, get_lock_path(&lock_path, base_path.buf); get_pid_path(&pid_path, base_path.buf); - lk->tempfile = create_tempfile_mode(lock_path.buf, mode); + lk->tempfile = repo_create_tempfile_mode(r, lock_path.buf, mode); if (lk->tempfile) lk->pid_tempfile = create_lock_pid_file(pid_path.buf, mode); @@ -200,8 +203,9 @@ static int lock_file(struct lock_file *lk, const char *path, int flags, * timeout_ms milliseconds. If timeout_ms is 0, try locking the file * exactly once. If timeout_ms is -1, try indefinitely. */ -static int lock_file_timeout(struct lock_file *lk, const char *path, - int flags, long timeout_ms, int mode) +static int lock_file_timeout(struct repository *r, struct lock_file *lk, + const char *path, int flags, long timeout_ms, + int mode) { int n = 1; int multiplier = 1; @@ -209,7 +213,7 @@ static int lock_file_timeout(struct lock_file *lk, const char *path, static int random_initialized = 0; if (timeout_ms == 0) - return lock_file(lk, path, flags, mode); + return lock_file(r, lk, path, flags, mode); if (!random_initialized) { srand((unsigned int)getpid()); @@ -223,7 +227,7 @@ static int lock_file_timeout(struct lock_file *lk, const char *path, long backoff_ms, wait_ms; int fd; - fd = lock_file(lk, path, flags, mode); + fd = lock_file(r, lk, path, flags, mode); if (fd >= 0) return fd; /* success */ @@ -308,7 +312,17 @@ int hold_lock_file_for_update_timeout_mode(struct lock_file *lk, const char *path, int flags, long timeout_ms, int mode) { - int fd = lock_file_timeout(lk, path, flags, timeout_ms, mode); + return repo_hold_lock_file_for_update_timeout_mode(the_repository, + lk, path, flags, + timeout_ms, mode); +} + +int repo_hold_lock_file_for_update_timeout_mode(struct repository *r, + struct lock_file *lk, + const char *path, int flags, + long timeout_ms, int mode) +{ + int fd = lock_file_timeout(r, lk, path, flags, timeout_ms, mode); if (fd < 0) { if (flags & LOCK_DIE_ON_ERROR) unable_to_lock_die(path, errno); diff --git a/lockfile.h b/lockfile.h index e7233f28dea5c7..1667612674b52a 100644 --- a/lockfile.h +++ b/lockfile.h @@ -189,6 +189,11 @@ int hold_lock_file_for_update_timeout_mode( struct lock_file *lk, const char *path, int flags, long timeout_ms, int mode); +int repo_hold_lock_file_for_update_timeout_mode(struct repository *r, + struct lock_file *lk, + const char *path, int flags, + long timeout_ms, int mode); + static inline int hold_lock_file_for_update_timeout( struct lock_file *lk, const char *path, int flags, long timeout_ms) @@ -197,6 +202,16 @@ static inline int hold_lock_file_for_update_timeout( timeout_ms, 0666); } +static inline int repo_hold_lock_file_for_update_timeout(struct repository *r, + struct lock_file *lk, + const char *path, + int flags, + long timeout_ms) +{ + return repo_hold_lock_file_for_update_timeout_mode(r, lk, path, flags, + timeout_ms, 0666); +} + /* * Attempt to create a lockfile for the file at `path` and return a * file descriptor for writing to it, or -1 on error. The flags @@ -208,6 +223,13 @@ static inline int hold_lock_file_for_update( return hold_lock_file_for_update_timeout(lk, path, flags, 0); } +static inline int repo_hold_lock_file_for_update(struct repository *r, + struct lock_file *lk, + const char *path, int flags) +{ + return repo_hold_lock_file_for_update_timeout(r, lk, path, flags, 0); +} + static inline int hold_lock_file_for_update_mode( struct lock_file *lk, const char *path, int flags, int mode) @@ -215,6 +237,15 @@ static inline int hold_lock_file_for_update_mode( return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode); } +static inline int repo_hold_lock_file_for_update_mode(struct repository *r, + struct lock_file *lk, + const char *path, + int flags, int mode) +{ + return repo_hold_lock_file_for_update_timeout_mode(r, lk, path, flags, + 0, mode); +} + /* * Return a nonzero value iff `lk` is currently locked. */ diff --git a/loose.c b/loose.c index bf01d3e42def34..e97c3bb50af867 100644 --- a/loose.c +++ b/loose.c @@ -137,7 +137,8 @@ int repo_write_loose_object_map(struct repository *repo) return 0; repo_common_path_replace(repo, &path, "objects/loose-object-idx"); - fd = hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1); + fd = repo_hold_lock_file_for_update_timeout(repo, &lock, path.buf, + LOCK_DIE_ON_ERROR, -1); iter = kh_begin(map); if (write_in_full(fd, loose_object_header, strlen(loose_object_header)) < 0) goto errout; @@ -179,7 +180,8 @@ static int write_one_object(struct odb_source_loose *loose, struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT; strbuf_addf(&path, "%s/loose-object-idx", loose->base.path); - hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1); + repo_hold_lock_file_for_update_timeout(loose->base.odb->repo, &lock, + path.buf, LOCK_DIE_ON_ERROR, -1); fd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666); if (fd < 0) diff --git a/meson.build b/meson.build index f7c40ea079b797..7073d5844d2531 100644 --- a/meson.build +++ b/meson.build @@ -374,6 +374,7 @@ libgit_sources = [ 'ewah/ewah_rlw.c', 'exec-cmd.c', 'fetch-negotiator.c', + 'fetch-object-info.c', 'fetch-pack.c', 'fmt-merge-msg.c', 'fsck.c', diff --git a/midx-write.c b/midx-write.c index 8c1837f6df4671..580724d21a677f 100644 --- a/midx-write.c +++ b/midx-write.c @@ -1627,8 +1627,8 @@ static int write_midx_internal(struct write_midx_opts *opts) struct strbuf lock_name = STRBUF_INIT; get_midx_chain_filename(opts->source, &lock_name); - hold_lock_file_for_update(&lk, lock_name.buf, - LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(r, &lk, lock_name.buf, + LOCK_DIE_ON_ERROR); strbuf_release(&lock_name); } @@ -1647,7 +1647,8 @@ static int write_midx_internal(struct write_midx_opts *opts) f = hashfd(r->hash_algo, get_tempfile_fd(incr), get_tempfile_path(incr)); } else { - hold_lock_file_for_update(&lk, midx_name.buf, LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(r, &lk, midx_name.buf, + LOCK_DIE_ON_ERROR); f = hashfd(r->hash_algo, get_lock_file_fd(&lk), get_lock_file_path(&lk)); } diff --git a/object-file.c b/object-file.c index 7ff2b730ac0ead..ec35c318bc9fe7 100644 --- a/object-file.c +++ b/object-file.c @@ -67,9 +67,17 @@ const char *odb_loose_path(struct odb_source_loose *loose, } /* Returns 1 if we have successfully freshened the file, 0 otherwise. */ -static int freshen_file(const char *fn) +static int freshen_file(const char *fn, const time_t *mtime) { - return !utime(fn, NULL); + struct utimbuf times, *timesp = NULL; + + if (mtime) { + times.actime = *mtime; + times.modtime = *mtime; + timesp = × + } + + return !utime(fn, timesp); } /* @@ -79,11 +87,12 @@ static int freshen_file(const char *fn) * either does not exist on disk, or has a stale mtime and may be subject to * pruning). */ -int check_and_freshen_file(const char *fn, int freshen) +int check_and_freshen_file(const char *fn, int freshen, + const time_t *mtime) { if (access(fn, F_OK)) return 0; - if (freshen && !freshen_file(fn)) + if (freshen && !freshen_file(fn, mtime)) return 0; return 1; } @@ -313,31 +322,6 @@ int parse_loose_header(const char *hdr, struct object_info *oi) return 0; } -static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c, - const void *buf, size_t len, - struct object_id *oid, - char *hdr, size_t *hdrlen) -{ - git_hash_init(c, algo); - git_hash_update(c, hdr, *hdrlen); - git_hash_update(c, buf, len); - git_hash_final_oid(oid, c); -} - -void write_object_file_prepare(const struct git_hash_algo *algo, - const void *buf, size_t len, - enum object_type type, struct object_id *oid, - char *hdr, size_t *hdrlen) -{ - struct git_hash_ctx c; - - /* Generate the header */ - *hdrlen = format_object_header(hdr, *hdrlen, type, len); - - /* Hash (function pointers) computation */ - hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen); -} - #define CHECK_COLLISION_DEST_VANISHED -2 static int check_collision(const char *source, const char *dest) @@ -409,11 +393,12 @@ int finalize_object_file_flags(struct repository *repo, { unsigned retries = 0; int ret; + struct repo_config_values *cfg = repo_config_values(repo); retry: ret = 0; - if (object_creation_mode == OBJECT_CREATION_USES_RENAMES) + if (cfg->object_creation_mode == OBJECT_CREATION_USES_RENAMES) goto try_rename; else if (link(tmpfile, filename)) ret = errno; @@ -473,10 +458,16 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf, size_t len, enum object_type type, struct object_id *oid) { + struct git_hash_ctx c; char hdr[MAX_HEADER_LEN]; - size_t hdrlen = sizeof(hdr); + int hdrlen; + + hdrlen = format_object_header(hdr, sizeof(hdr), type, len); - write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); + git_hash_init(&c, algo); + git_hash_update(&c, hdr, hdrlen); + git_hash_update(&c, buf, len); + git_hash_final_oid(oid, &c); } struct transaction_packfile { @@ -498,7 +489,7 @@ struct odb_transaction_files { const char *prefix; }; -static int odb_transaction_files_prepare(struct odb_transaction *base) +int odb_transaction_files_prepare(struct odb_transaction *base) { struct odb_transaction_files *transaction = container_of_or_null(base, struct odb_transaction_files, base); @@ -521,8 +512,8 @@ static int odb_transaction_files_prepare(struct odb_transaction *base) return 0; } -static void odb_transaction_files_fsync(struct odb_transaction *base, - int fd, const char *filename) +void odb_transaction_files_fsync(struct odb_transaction *base, + int fd, const char *filename) { struct odb_transaction_files *transaction = container_of_or_null(base, struct odb_transaction_files, base); @@ -546,397 +537,6 @@ static void odb_transaction_files_fsync(struct odb_transaction *base, } } -/* Finalize a file on disk, and close it. */ -static void close_loose_object(struct odb_source_loose *loose, - int fd, const char *filename) -{ - if (loose->base.will_destroy) - goto out; - - if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - odb_transaction_files_fsync(loose->base.odb->transaction, fd, filename); - else if (fsync_object_files > 0) - fsync_or_die(fd, filename); - else - fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd, - filename); - -out: - if (close(fd) != 0) - die_errno(_("error when closing loose object file")); -} - -/* Size of directory component, including the ending '/' */ -static inline int directory_size(const char *filename) -{ - const char *s = strrchr(filename, '/'); - if (!s) - return 0; - return s - filename + 1; -} - -/* - * This creates a temporary file in the same directory as the final - * 'filename' - * - * We want to avoid cross-directory filename renames, because those - * can have problems on various filesystems (FAT, NFS, Coda). - */ -static int create_tmpfile(struct repository *repo, - struct strbuf *tmp, const char *filename) -{ - int fd, dirlen = directory_size(filename); - - strbuf_reset(tmp); - strbuf_add(tmp, filename, dirlen); - strbuf_addstr(tmp, "tmp_obj_XXXXXX"); - fd = git_mkstemp_mode(tmp->buf, 0444); - if (fd < 0 && dirlen && errno == ENOENT) { - /* - * Make sure the directory exists; note that the contents - * of the buffer are undefined after mkstemp returns an - * error, so we have to rewrite the whole buffer from - * scratch. - */ - strbuf_reset(tmp); - strbuf_add(tmp, filename, dirlen - 1); - if (mkdir(tmp->buf, 0777) && errno != EEXIST) - return -1; - if (adjust_shared_perm(repo, tmp->buf)) - return -1; - - /* Try again */ - strbuf_addstr(tmp, "/tmp_obj_XXXXXX"); - fd = git_mkstemp_mode(tmp->buf, 0444); - } - return fd; -} - -/** - * Common steps for loose object writers to start writing loose - * objects: - * - * - Create tmpfile for the loose object. - * - Setup zlib stream for compression. - * - Start to feed header to zlib stream. - * - * Returns a "fd", which should later be provided to - * end_loose_object_common(). - */ -static int start_loose_object_common(struct odb_source_loose *loose, - struct strbuf *tmp_file, - const char *filename, unsigned flags, - git_zstream *stream, - unsigned char *buf, size_t buflen, - struct git_hash_ctx *c, struct git_hash_ctx *compat_c, - char *hdr, int hdrlen) -{ - const struct git_hash_algo *algo = loose->base.odb->repo->hash_algo; - const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; - int fd; - struct repo_config_values *cfg = repo_config_values(the_repository); - - fd = create_tmpfile(loose->base.odb->repo, tmp_file, filename); - if (fd < 0) { - if (flags & ODB_WRITE_OBJECT_SILENT) - return -1; - else if (errno == EACCES) - return error(_("insufficient permission for adding " - "an object to repository database %s"), - loose->base.path); - else - return error_errno( - _("unable to create temporary file")); - } - - /* Setup zlib stream for compression */ - git_deflate_init(stream, cfg->zlib_compression_level); - stream->next_out = buf; - stream->avail_out = buflen; - git_hash_init(c, algo); - if (compat && compat_c) - git_hash_init(compat_c, compat); - - /* Start to feed header to zlib stream */ - stream->next_in = (unsigned char *)hdr; - stream->avail_in = hdrlen; - while (git_deflate(stream, 0) == Z_OK) - ; /* nothing */ - git_hash_update(c, hdr, hdrlen); - if (compat && compat_c) - git_hash_update(compat_c, hdr, hdrlen); - - return fd; -} - -/** - * Common steps for the inner git_deflate() loop for writing loose - * objects. Returns what git_deflate() returns. - */ -static int write_loose_object_common(struct odb_source_loose *loose, - struct git_hash_ctx *c, struct git_hash_ctx *compat_c, - git_zstream *stream, const int flush, - unsigned char *in0, const int fd, - unsigned char *compressed, - const size_t compressed_len) -{ - const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; - int ret; - - ret = git_deflate(stream, flush ? Z_FINISH : 0); - git_hash_update(c, in0, stream->next_in - in0); - if (compat && compat_c) - git_hash_update(compat_c, in0, stream->next_in - in0); - if (write_in_full(fd, compressed, stream->next_out - compressed) < 0) - die_errno(_("unable to write loose object file")); - stream->next_out = compressed; - stream->avail_out = compressed_len; - - return ret; -} - -/** - * Common steps for loose object writers to end writing loose objects: - * - * - End the compression of zlib stream. - * - Get the calculated oid to "oid". - */ -static int end_loose_object_common(struct odb_source_loose *loose, - struct git_hash_ctx *c, struct git_hash_ctx *compat_c, - git_zstream *stream, struct object_id *oid, - struct object_id *compat_oid) -{ - const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; - int ret; - - ret = git_deflate_end_gently(stream); - if (ret != Z_OK) - return ret; - git_hash_final_oid(oid, c); - if (compat && compat_c) - git_hash_final_oid(compat_oid, compat_c); - - return Z_OK; -} - -int write_loose_object(struct odb_source_loose *loose, - const struct object_id *oid, char *hdr, - int hdrlen, const void *buf, unsigned long len, - time_t mtime, unsigned flags) -{ - int fd, ret; - unsigned char compressed[4096]; - git_zstream stream; - struct git_hash_ctx c; - struct object_id parano_oid; - static struct strbuf tmp_file = STRBUF_INIT; - static struct strbuf filename = STRBUF_INIT; - - if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - odb_transaction_files_prepare(loose->base.odb->transaction); - - odb_loose_path(loose, &filename, oid); - - fd = start_loose_object_common(loose, &tmp_file, filename.buf, flags, - &stream, compressed, sizeof(compressed), - &c, NULL, hdr, hdrlen); - if (fd < 0) - return -1; - - /* Then the data itself.. */ - stream.next_in = (void *)buf; - stream.avail_in = len; - do { - unsigned char *in0 = stream.next_in; - - ret = write_loose_object_common(loose, &c, NULL, &stream, 1, in0, fd, - compressed, sizeof(compressed)); - } while (ret == Z_OK); - - if (ret != Z_STREAM_END) - die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid), - ret); - ret = end_loose_object_common(loose, &c, NULL, &stream, ¶no_oid, NULL); - if (ret != Z_OK) - die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid), - ret); - if (!oideq(oid, ¶no_oid)) - die(_("confused by unstable object source data for %s"), - oid_to_hex(oid)); - - close_loose_object(loose, fd, tmp_file.buf); - - if (mtime) { - struct utimbuf utb; - utb.actime = mtime; - utb.modtime = mtime; - if (utime(tmp_file.buf, &utb) < 0 && - !(flags & ODB_WRITE_OBJECT_SILENT)) - warning_errno(_("failed utime() on %s"), tmp_file.buf); - } - - return finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, - FOF_SKIP_COLLISION_CHECK); -} - -int odb_source_loose_write_stream(struct odb_source_loose *loose, - struct odb_write_stream *in_stream, size_t len, - struct object_id *oid) -{ - const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; - struct object_id compat_oid; - int fd, ret, err = 0, flush = 0; - unsigned char compressed[4096]; - git_zstream stream; - struct git_hash_ctx c, compat_c; - struct strbuf tmp_file = STRBUF_INIT; - struct strbuf filename = STRBUF_INIT; - unsigned char buf[8192]; - int dirlen; - char hdr[MAX_HEADER_LEN]; - int hdrlen; - - if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - odb_transaction_files_prepare(loose->base.odb->transaction); - - /* Since oid is not determined, save tmp file to odb path. */ - strbuf_addf(&filename, "%s/", loose->base.path); - hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len); - - /* - * Common steps for write_loose_object and stream_loose_object to - * start writing loose objects: - * - * - Create tmpfile for the loose object. - * - Setup zlib stream for compression. - * - Start to feed header to zlib stream. - */ - fd = start_loose_object_common(loose, &tmp_file, filename.buf, 0, - &stream, compressed, sizeof(compressed), - &c, &compat_c, hdr, hdrlen); - if (fd < 0) { - err = -1; - goto cleanup; - } - - /* Then the data itself.. */ - do { - unsigned char *in0 = stream.next_in; - - if (!stream.avail_in && !in_stream->is_finished) { - ssize_t read_len = odb_write_stream_read(in_stream, buf, - sizeof(buf)); - if (read_len < 0) { - close(fd); - err = -1; - goto cleanup; - } - - stream.avail_in = read_len; - stream.next_in = buf; - in0 = buf; - /* All data has been read. */ - if (in_stream->is_finished) - flush = 1; - } - ret = write_loose_object_common(loose, &c, &compat_c, &stream, flush, in0, fd, - compressed, sizeof(compressed)); - /* - * Unlike write_loose_object(), we do not have the entire - * buffer. If we get Z_BUF_ERROR due to too few input bytes, - * then we'll replenish them in the next input_stream->read() - * call when we loop. - */ - } while (ret == Z_OK || ret == Z_BUF_ERROR); - - if (stream.total_in != len + hdrlen) - die(_("write stream object %"PRIuMAX" != %"PRIuMAX), (uintmax_t)stream.total_in, - (uintmax_t)len + hdrlen); - - /* - * Common steps for write_loose_object and stream_loose_object to - * end writing loose object: - * - * - End the compression of zlib stream. - * - Get the calculated oid. - */ - if (ret != Z_STREAM_END) - die(_("unable to stream deflate new object (%d)"), ret); - ret = end_loose_object_common(loose, &c, &compat_c, &stream, oid, &compat_oid); - if (ret != Z_OK) - die(_("deflateEnd on stream object failed (%d)"), ret); - close_loose_object(loose, fd, tmp_file.buf); - - if (odb_freshen_object(loose->base.odb, oid)) { - unlink_or_warn(tmp_file.buf); - goto cleanup; - } - odb_loose_path(loose, &filename, oid); - - /* We finally know the object path, and create the missing dir. */ - dirlen = directory_size(filename.buf); - if (dirlen) { - struct strbuf dir = STRBUF_INIT; - strbuf_add(&dir, filename.buf, dirlen); - - if (safe_create_dir_in_gitdir(loose->base.odb->repo, dir.buf) && - errno != EEXIST) { - err = error_errno(_("unable to create directory %s"), dir.buf); - strbuf_release(&dir); - goto cleanup; - } - strbuf_release(&dir); - } - - err = finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, - FOF_SKIP_COLLISION_CHECK); - if (!err && compat) - err = repo_add_loose_object_map(loose, oid, &compat_oid); -cleanup: - strbuf_release(&tmp_file); - strbuf_release(&filename); - return err; -} - -int force_object_loose(struct odb_source *source, - const struct object_id *oid, time_t mtime) -{ - struct odb_source_files *files = odb_source_files_downcast(source); - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; - void *buf; - size_t len; - struct object_info oi = OBJECT_INFO_INIT; - struct object_id compat_oid; - enum object_type type; - char hdr[MAX_HEADER_LEN]; - int hdrlen; - int ret; - - for (struct odb_source *s = source->odb->sources; s; s = s->next) { - struct odb_source_files *files = odb_source_files_downcast(s); - if (!odb_source_read_object_info(&files->loose->base, oid, NULL, 0)) - return 0; - } - - oi.typep = &type; - oi.sizep = &len; - oi.contentp = &buf; - if (odb_read_object_info_extended(source->odb, oid, &oi, 0)) - return error(_("cannot read object for %s"), oid_to_hex(oid)); - if (compat) { - if (repo_oid_to_algop(source->odb->repo, oid, compat, &compat_oid)) - return error(_("cannot map object %s to %s"), - oid_to_hex(oid), compat->name); - } - hdrlen = format_object_header(hdr, sizeof(hdr), type, len); - ret = write_loose_object(files->loose, oid, hdr, hdrlen, buf, len, mtime, 0); - if (!ret && compat) - ret = repo_add_loose_object_map(files->loose, oid, &compat_oid); - free(buf); - - return ret; -} - /* * We can't use the normal fsck_error_function() for index_mem(), * because we don't yet have a valid oid for it to report. Instead, @@ -1740,3 +1340,13 @@ int odb_transaction_files_begin(struct odb_source *source, return 0; } + +void free_object_info_contents(struct object_info *object_info) +{ + if (!object_info) + return; + free(object_info->typep); + free(object_info->sizep); + free(object_info->disk_sizep); + free(object_info->delta_base_oid); +} diff --git a/object-file.h b/object-file.h index d04ffa6493ff17..805f2cfa289661 100644 --- a/object-file.h +++ b/object-file.h @@ -24,20 +24,6 @@ int index_path(struct index_state *istate, struct object_id *oid, const char *pa struct object_info; struct odb_source; -/* - * Write the given stream into the loose object source. The only difference - * from the generic implementation of this function is that we don't perform an - * object existence check here. - * - * TODO: We should stop exposing this function altogether and move it into - * "odb/source-loose.c". This requires a couple of refactorings though to make - * `force_object_loose()` generic and is thus postponed to a later point in - * time. - */ -int odb_source_loose_write_stream(struct odb_source_loose *source, - struct odb_write_stream *stream, size_t len, - struct object_id *oid); - /* * Put in `buf` the name of the file in the local object database that * would be used to store a loose object with the specified oid. @@ -98,9 +84,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr, int format_object_header(char *str, size_t size, enum object_type type, size_t objsize); -int force_object_loose(struct odb_source *source, - const struct object_id *oid, time_t mtime); - /** * With in-core object data in "buf", rehash it to make sure the * object name actually matches "oid" to detect object corruption. @@ -134,17 +117,10 @@ int finalize_object_file_flags(struct repository *repo, void hash_object_file(const struct git_hash_algo *algo, const void *buf, size_t len, enum object_type type, struct object_id *oid); -void write_object_file_prepare(const struct git_hash_algo *algo, - const void *buf, size_t len, - enum object_type type, struct object_id *oid, - char *hdr, size_t *hdrlen); -int write_loose_object(struct odb_source_loose *loose, - const struct object_id *oid, char *hdr, - int hdrlen, const void *buf, unsigned long len, - time_t mtime, unsigned flags); /* Helper to check and "touch" a file */ -int check_and_freshen_file(const char *fn, int freshen); +int check_and_freshen_file(const char *fn, int freshen, + const time_t *mtime); /* * Open the loose object at path, check its hash, and return the contents, @@ -201,4 +177,8 @@ int odb_transaction_files_begin(struct odb_source *source, struct odb_transaction **out, enum odb_transaction_flags flags); +int odb_transaction_files_prepare(struct odb_transaction *base); +void odb_transaction_files_fsync(struct odb_transaction *base, + int fd, const char *filename); + #endif /* OBJECT_FILE_H */ diff --git a/odb.c b/odb.c index cf6e7938c01e56..dabd481f57dbc4 100644 --- a/odb.c +++ b/odb.c @@ -738,7 +738,7 @@ int odb_pretend_object(struct object_database *odb, return 0; return odb_source_write_object(odb->inmemory_objects, - buf, len, type, oid, NULL, 0); + buf, len, type, oid, NULL, NULL, 0); } void *odb_read_object(struct object_database *odb, @@ -829,7 +829,7 @@ int odb_freshen_object(struct object_database *odb, struct odb_source *source; odb_prepare_alternates(odb); for (source = odb->sources; source; source = source->next) - if (odb_source_freshen_object(source, oid)) + if (odb_source_freshen_object(source, oid, NULL)) return 1; return 0; } @@ -989,11 +989,42 @@ int odb_write_object_ext(struct object_database *odb, const void *buf, unsigned long len, enum object_type type, struct object_id *oid, - struct object_id *compat_oid, + const struct object_id *compat_oid_in, enum odb_write_object_flags flags) { + const struct git_hash_algo *compat = odb->repo->compat_hash_algo; + struct object_id compat_oid, *compat_oid_p = NULL; + + hash_object_file(odb->repo->hash_algo, buf, len, type, oid); + + /* + * We can skip the write in case we already have the object available. + * In that case, we only freshen its mtime. + */ + if (odb_freshen_object(odb, oid)) + return 0; + + if (compat) { + const struct git_hash_algo *algo = odb->repo->hash_algo; + + if (compat_oid_in) { + oidcpy(&compat_oid, compat_oid_in); + } else if (type == OBJ_BLOB) { + hash_object_file(compat, buf, len, type, &compat_oid); + } else { + struct strbuf converted = STRBUF_INIT; + convert_object_file(odb->repo, &converted, algo, compat, + buf, len, type, 0); + hash_object_file(compat, converted.buf, converted.len, + type, &compat_oid); + strbuf_release(&converted); + } + + compat_oid_p = &compat_oid; + } + return odb_source_write_object(odb->sources, buf, len, type, - oid, compat_oid, flags); + oid, compat_oid_p, NULL, flags); } int odb_write_object_stream(struct object_database *odb, diff --git a/odb.h b/odb.h index 7995bed97bc36e..cbc2f9ced42338 100644 --- a/odb.h +++ b/odb.h @@ -340,6 +340,12 @@ struct object_info { * or multiple times in the same source. */ struct odb_source_info *source_infop; + + /* + * object-info protocol specific. Set by the protocol when the remote + * does not recognize the requested object. + */ + unsigned int unrecognized:1; }; /* @@ -597,9 +603,11 @@ enum odb_write_object_flags { /* * Write an object into the object database. The object is being written into - * the local alternate of the repository. If provided, the converted object ID - * as well as the compatibility object ID are written to the respective - * pointers. + * the local alternate of the repository. If provided, the object ID of the + * final object is written into `oid`. + * + * If the caller provides a `compat_oid`, then this compatibility object hash + * will be stored instead of computing the compatibility hash ad-hoc. * * Returns 0 on success, a negative error code otherwise. */ @@ -607,7 +615,7 @@ int odb_write_object_ext(struct object_database *odb, const void *buf, unsigned long len, enum object_type type, struct object_id *oid, - struct object_id *compat_oid, + const struct object_id *compat_oid, enum odb_write_object_flags flags); static inline int odb_write_object(struct object_database *odb, @@ -629,4 +637,7 @@ void parse_alternates(const char *string, const char *relative_base, struct strvec *out); +/* Free pointers inside of object_info, but not object_info itself */ +void free_object_info_contents(struct object_info *object_info); + #endif /* ODB_H */ diff --git a/odb/source-files.c b/odb/source-files.c index 413875851135a5..5e086d266fac4f 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -150,11 +150,12 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source, } static int odb_source_files_freshen_object(struct odb_source *source, - const struct object_id *oid) + const struct object_id *oid, + const time_t *mtime) { struct odb_source_files *files = odb_source_files_downcast(source); - if (odb_source_freshen_object(&files->packed->base, oid) || - odb_source_freshen_object(&files->loose->base, oid)) + if (odb_source_freshen_object(&files->packed->base, oid, mtime) || + odb_source_freshen_object(&files->loose->base, oid, mtime)) return 1; return 0; } @@ -162,13 +163,14 @@ static int odb_source_files_freshen_object(struct odb_source *source, static int odb_source_files_write_object(struct odb_source *source, const void *buf, size_t len, enum object_type type, - struct object_id *oid, - struct object_id *compat_oid, + const struct object_id *oid, + const struct object_id *compat_oid, + const time_t *mtime, enum odb_write_object_flags flags) { struct odb_source_files *files = odb_source_files_downcast(source); return odb_source_write_object(&files->loose->base, buf, len, type, - oid, compat_oid, flags); + oid, compat_oid, mtime, flags); } static int odb_source_files_write_object_stream(struct odb_source *source, @@ -215,7 +217,8 @@ static int odb_source_files_write_alternate(struct odb_source *source, int found = 0; int ret; - hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(source->odb->repo, &lock, path, + LOCK_DIE_ON_ERROR); out = fdopen_lock_file(&lock, "w"); if (!out) { ret = error_errno(_("unable to fdopen alternates lockfile")); diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c index e47bfd8fccabf0..3e71611b8e0071 100644 --- a/odb/source-inmemory.c +++ b/odb/source-inmemory.c @@ -230,15 +230,14 @@ static int odb_source_inmemory_count_objects(struct odb_source *source, static int odb_source_inmemory_write_object(struct odb_source *source, const void *buf, size_t len, enum object_type type, - struct object_id *oid, - struct object_id *compat_oid UNUSED, + const struct object_id *oid, + const struct object_id *compat_oid UNUSED, + const time_t *mtime UNUSED, enum odb_write_object_flags flags UNUSED) { struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); struct inmemory_object *object; - hash_object_file(source->odb->repo->hash_algo, buf, len, type, oid); - if (!inmemory->objects) { CALLOC_ARRAY(inmemory->objects, 1); oidtree_init(inmemory->objects); @@ -285,8 +284,10 @@ static int odb_source_inmemory_write_object_stream(struct odb_source *source, goto out; } + hash_object_file(source->odb->repo->hash_algo, data, total_read, OBJ_BLOB, oid); + ret = odb_source_inmemory_write_object(source, data, len, OBJ_BLOB, oid, - NULL, 0); + NULL, NULL, 0); if (ret < 0) goto out; @@ -296,7 +297,8 @@ static int odb_source_inmemory_write_object_stream(struct odb_source *source, } static int odb_source_inmemory_freshen_object(struct odb_source *source, - const struct object_id *oid) + const struct object_id *oid, + const time_t *mtime UNUSED) { struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source); if (find_cached_object(inmemory, oid)) diff --git a/odb/source-loose.c b/odb/source-loose.c index 3f7d04a56e36ce..ef0e9192777c4a 100644 --- a/odb/source-loose.c +++ b/odb/source-loose.c @@ -11,8 +11,11 @@ #include "odb/source-loose.h" #include "odb/streaming.h" #include "oidtree.h" +#include "path.h" #include "repository.h" #include "strbuf.h" +#include "tempfile.h" +#include "write-or-die.h" static int append_loose_object(const struct object_id *oid, const char *path UNUSED, @@ -574,53 +577,270 @@ static int odb_source_loose_count_objects(struct odb_source *source, } static int odb_source_loose_freshen_object(struct odb_source *source, - const struct object_id *oid) + const struct object_id *oid, + const time_t *mtime) { struct odb_source_loose *loose = odb_source_loose_downcast(source); static struct strbuf path = STRBUF_INIT; odb_loose_path(loose, &path, oid); - return !!check_and_freshen_file(path.buf, 1); + return !!check_and_freshen_file(path.buf, 1, mtime); +} + +/* Finalize a file on disk, and close it. */ +static void close_loose_object(struct odb_source_loose *loose, + int fd, const char *filename) +{ + if (loose->base.will_destroy) + goto out; + + if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) + odb_transaction_files_fsync(loose->base.odb->transaction, fd, filename); + else if (fsync_object_files > 0) + fsync_or_die(fd, filename); + else + fsync_component_or_die(FSYNC_COMPONENT_LOOSE_OBJECT, fd, + filename); + +out: + if (close(fd) != 0) + die_errno(_("error when closing loose object file")); +} + +/* Size of directory component, including the ending '/' */ +static inline int directory_size(const char *filename) +{ + const char *s = strrchr(filename, '/'); + if (!s) + return 0; + return s - filename + 1; +} + +/* + * This creates a temporary file in the same directory as the final + * 'filename' + * + * We want to avoid cross-directory filename renames, because those + * can have problems on various filesystems (FAT, NFS, Coda). + */ +static int create_tmpfile(struct repository *repo, + struct strbuf *tmp, const char *filename) +{ + int fd, dirlen = directory_size(filename); + + strbuf_reset(tmp); + strbuf_add(tmp, filename, dirlen); + strbuf_addstr(tmp, "tmp_obj_XXXXXX"); + fd = git_mkstemp_mode(tmp->buf, 0444); + if (fd < 0 && dirlen && errno == ENOENT) { + /* + * Make sure the directory exists; note that the contents + * of the buffer are undefined after mkstemp returns an + * error, so we have to rewrite the whole buffer from + * scratch. + */ + strbuf_reset(tmp); + strbuf_add(tmp, filename, dirlen - 1); + if (mkdir(tmp->buf, 0777) && errno != EEXIST) + return -1; + if (adjust_shared_perm(repo, tmp->buf)) + return -1; + + /* Try again */ + strbuf_addstr(tmp, "/tmp_obj_XXXXXX"); + fd = git_mkstemp_mode(tmp->buf, 0444); + } + return fd; +} + +/** + * Common steps for loose object writers to start writing loose + * objects: + * + * - Create tmpfile for the loose object. + * - Setup zlib stream for compression. + * - Start to feed header to zlib stream. + * + * Returns a "fd", which should later be provided to + * end_loose_object_common(). + */ +static int start_loose_object_common(struct odb_source_loose *loose, + struct strbuf *tmp_file, + const char *filename, unsigned flags, + git_zstream *stream, + unsigned char *buf, size_t buflen, + struct git_hash_ctx *c, struct git_hash_ctx *compat_c, + char *hdr, int hdrlen) +{ + const struct git_hash_algo *algo = loose->base.odb->repo->hash_algo; + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; + int fd; + struct repo_config_values *cfg = repo_config_values(loose->base.odb->repo); + + fd = create_tmpfile(loose->base.odb->repo, tmp_file, filename); + if (fd < 0) { + if (flags & ODB_WRITE_OBJECT_SILENT) + return -1; + else if (errno == EACCES) + return error(_("insufficient permission for adding " + "an object to repository database %s"), + loose->base.path); + else + return error_errno( + _("unable to create temporary file")); + } + + /* Setup zlib stream for compression */ + git_deflate_init(stream, cfg->zlib_compression_level); + stream->next_out = buf; + stream->avail_out = buflen; + git_hash_init(c, algo); + if (compat && compat_c) + git_hash_init(compat_c, compat); + + /* Start to feed header to zlib stream */ + stream->next_in = (unsigned char *)hdr; + stream->avail_in = hdrlen; + while (git_deflate(stream, 0) == Z_OK) + ; /* nothing */ + git_hash_update(c, hdr, hdrlen); + if (compat && compat_c) + git_hash_update(compat_c, hdr, hdrlen); + + return fd; +} + +/** + * Common steps for the inner git_deflate() loop for writing loose + * objects. Returns what git_deflate() returns. + */ +static int write_loose_object_common(struct odb_source_loose *loose, + struct git_hash_ctx *c, struct git_hash_ctx *compat_c, + git_zstream *stream, const int flush, + unsigned char *in0, const int fd, + unsigned char *compressed, + const size_t compressed_len) +{ + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; + int ret; + + ret = git_deflate(stream, flush ? Z_FINISH : 0); + git_hash_update(c, in0, stream->next_in - in0); + if (compat && compat_c) + git_hash_update(compat_c, in0, stream->next_in - in0); + if (write_in_full(fd, compressed, stream->next_out - compressed) < 0) + die_errno(_("unable to write loose object file")); + stream->next_out = compressed; + stream->avail_out = compressed_len; + + return ret; +} + +/** + * Common steps for loose object writers to end writing loose objects: + * + * - End the compression of zlib stream. + * - Get the calculated oid to "oid". + */ +static int end_loose_object_common(struct odb_source_loose *loose, + struct git_hash_ctx *c, struct git_hash_ctx *compat_c, + git_zstream *stream, struct object_id *oid, + struct object_id *compat_oid) +{ + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; + int ret; + + ret = git_deflate_end_gently(stream); + if (ret != Z_OK) + return ret; + git_hash_final_oid(oid, c); + if (compat && compat_c) + git_hash_final_oid(compat_oid, compat_c); + + return Z_OK; +} + +static int write_loose_object(struct odb_source_loose *loose, + const struct object_id *oid, char *hdr, + int hdrlen, const void *buf, unsigned long len, + const time_t *mtime, unsigned flags) +{ + int fd, ret; + unsigned char compressed[4096]; + git_zstream stream; + struct git_hash_ctx c; + struct object_id parano_oid; + static struct strbuf tmp_file = STRBUF_INIT; + static struct strbuf filename = STRBUF_INIT; + + if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) + odb_transaction_files_prepare(loose->base.odb->transaction); + + odb_loose_path(loose, &filename, oid); + + fd = start_loose_object_common(loose, &tmp_file, filename.buf, flags, + &stream, compressed, sizeof(compressed), + &c, NULL, hdr, hdrlen); + if (fd < 0) + return -1; + + /* Then the data itself.. */ + stream.next_in = (void *)buf; + stream.avail_in = len; + do { + unsigned char *in0 = stream.next_in; + + ret = write_loose_object_common(loose, &c, NULL, &stream, 1, in0, fd, + compressed, sizeof(compressed)); + } while (ret == Z_OK); + + if (ret != Z_STREAM_END) + die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid), + ret); + ret = end_loose_object_common(loose, &c, NULL, &stream, ¶no_oid, NULL); + if (ret != Z_OK) + die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid), + ret); + if (!oideq(oid, ¶no_oid)) + die(_("confused by unstable object source data for %s"), + oid_to_hex(oid)); + + close_loose_object(loose, fd, tmp_file.buf); + + if (mtime) { + struct utimbuf utb = { + .actime = *mtime, + .modtime = *mtime, + }; + + if (utime(tmp_file.buf, &utb) < 0 && + !(flags & ODB_WRITE_OBJECT_SILENT)) + warning_errno(_("failed utime() on %s"), tmp_file.buf); + } + + return finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, + FOF_SKIP_COLLISION_CHECK); } static int odb_source_loose_write_object(struct odb_source *source, const void *buf, size_t len, - enum object_type type, struct object_id *oid, - struct object_id *compat_oid_in, + enum object_type type, + const struct object_id *oid, + const struct object_id *compat_oid, + const time_t *mtime, enum odb_write_object_flags flags) { struct odb_source_loose *loose = odb_source_loose_downcast(source); - const struct git_hash_algo *algo = source->odb->repo->hash_algo; - const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; - struct object_id compat_oid; char hdr[MAX_HEADER_LEN]; - size_t hdrlen = sizeof(hdr); - - /* Generate compat_oid */ - if (compat) { - if (compat_oid_in) - oidcpy(&compat_oid, compat_oid_in); - else if (type == OBJ_BLOB) - hash_object_file(compat, buf, len, type, &compat_oid); - else { - struct strbuf converted = STRBUF_INIT; - convert_object_file(source->odb->repo, &converted, algo, compat, - buf, len, type, 0); - hash_object_file(compat, converted.buf, converted.len, - type, &compat_oid); - strbuf_release(&converted); - } - } + int hdrlen; - /* Normally if we have it in the pack then we do not bother writing - * it out into .git/objects/??/?{38} file. - */ - write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); - if (odb_freshen_object(source->odb, oid)) - return 0; - if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, 0, flags)) + hdrlen = format_object_header(hdr, sizeof(hdr), type, len); + + if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, mtime, flags)) return -1; - if (compat) - return repo_add_loose_object_map(loose, oid, &compat_oid); + + if (compat_oid) + return repo_add_loose_object_map(loose, oid, compat_oid); + return 0; } @@ -629,12 +849,120 @@ static int odb_source_loose_write_object_stream(struct odb_source *source, size_t len, struct object_id *oid) { + struct odb_source_loose *loose = odb_source_loose_downcast(source); + const struct git_hash_algo *compat = loose->base.odb->repo->compat_hash_algo; + struct object_id compat_oid; + int fd, ret, err = 0, flush = 0; + unsigned char compressed[4096]; + git_zstream stream; + struct git_hash_ctx c, compat_c; + struct strbuf tmp_file = STRBUF_INIT; + struct strbuf filename = STRBUF_INIT; + unsigned char buf[8192]; + int dirlen; + char hdr[MAX_HEADER_LEN]; + int hdrlen; + + if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) + odb_transaction_files_prepare(loose->base.odb->transaction); + + /* Since oid is not determined, save tmp file to odb path. */ + strbuf_addf(&filename, "%s/", loose->base.path); + hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len); + /* - * TODO: the implementation should be moved here, see the comment on - * the called function in "object-file.h". + * Common steps for write_loose_object and stream_loose_object to + * start writing loose objects: + * + * - Create tmpfile for the loose object. + * - Setup zlib stream for compression. + * - Start to feed header to zlib stream. */ - struct odb_source_loose *loose = odb_source_loose_downcast(source); - return odb_source_loose_write_stream(loose, in_stream, len, oid); + fd = start_loose_object_common(loose, &tmp_file, filename.buf, 0, + &stream, compressed, sizeof(compressed), + &c, &compat_c, hdr, hdrlen); + if (fd < 0) { + err = -1; + goto cleanup; + } + + /* Then the data itself.. */ + do { + unsigned char *in0 = stream.next_in; + + if (!stream.avail_in && !in_stream->is_finished) { + ssize_t read_len = odb_write_stream_read(in_stream, buf, + sizeof(buf)); + if (read_len < 0) { + close(fd); + err = -1; + goto cleanup; + } + + stream.avail_in = read_len; + stream.next_in = buf; + in0 = buf; + /* All data has been read. */ + if (in_stream->is_finished) + flush = 1; + } + ret = write_loose_object_common(loose, &c, &compat_c, &stream, flush, in0, fd, + compressed, sizeof(compressed)); + /* + * Unlike write_loose_object(), we do not have the entire + * buffer. If we get Z_BUF_ERROR due to too few input bytes, + * then we'll replenish them in the next input_stream->read() + * call when we loop. + */ + } while (ret == Z_OK || ret == Z_BUF_ERROR); + + if (stream.total_in != len + hdrlen) + die(_("write stream object %"PRIuMAX" != %"PRIuMAX), (uintmax_t)stream.total_in, + (uintmax_t)len + hdrlen); + + /* + * Common steps for write_loose_object and stream_loose_object to + * end writing loose object: + * + * - End the compression of zlib stream. + * - Get the calculated oid. + */ + if (ret != Z_STREAM_END) + die(_("unable to stream deflate new object (%d)"), ret); + ret = end_loose_object_common(loose, &c, &compat_c, &stream, oid, &compat_oid); + if (ret != Z_OK) + die(_("deflateEnd on stream object failed (%d)"), ret); + close_loose_object(loose, fd, tmp_file.buf); + + if (odb_freshen_object(loose->base.odb, oid)) { + unlink_or_warn(tmp_file.buf); + goto cleanup; + } + odb_loose_path(loose, &filename, oid); + + /* We finally know the object path, and create the missing dir. */ + dirlen = directory_size(filename.buf); + if (dirlen) { + struct strbuf dir = STRBUF_INIT; + strbuf_add(&dir, filename.buf, dirlen); + + if (safe_create_dir_in_gitdir(loose->base.odb->repo, dir.buf) && + errno != EEXIST) { + err = error_errno(_("unable to create directory %s"), dir.buf); + strbuf_release(&dir); + goto cleanup; + } + strbuf_release(&dir); + } + + err = finalize_object_file_flags(loose->base.odb->repo, tmp_file.buf, filename.buf, + FOF_SKIP_COLLISION_CHECK); + if (!err && compat) + err = repo_add_loose_object_map(loose, oid, &compat_oid); +cleanup: + strbuf_release(&tmp_file); + strbuf_release(&filename); + return err; } static int odb_source_loose_begin_transaction(struct odb_source *source UNUSED, diff --git a/odb/source-packed.c b/odb/source-packed.c index d00aec5a452c58..0890704e76879b 100644 --- a/odb/source-packed.c +++ b/odb/source-packed.c @@ -570,18 +570,26 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source, } static int odb_source_packed_freshen_object(struct odb_source *source, - const struct object_id *oid) + const struct object_id *oid, + const time_t *mtime) { struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct utimbuf times, *timesp = NULL; struct pack_entry e; + if (mtime) { + times.actime = *mtime; + times.modtime = *mtime; + timesp = × + } + if (!find_pack_entry(packed, oid, &e)) return 0; if (e.p->is_cruft) return 0; if (e.p->freshened) return 1; - if (utime(e.p->pack_name, NULL)) + if (utime(e.p->pack_name, timesp)) return 0; e.p->freshened = 1; @@ -592,8 +600,9 @@ static int odb_source_packed_write_object(struct odb_source *source UNUSED, const void *buf UNUSED, size_t len UNUSED, enum object_type type UNUSED, - struct object_id *oid UNUSED, - struct object_id *compat_oid UNUSED, + const struct object_id *oid UNUSED, + const struct object_id *compat_oid UNUSED, + const time_t *mtime UNUSED, unsigned flags UNUSED) { return error("packed backend cannot write objects"); diff --git a/odb/source.h b/odb/source.h index cd63dba91f4e2f..fc04dd5cda8800 100644 --- a/odb/source.h +++ b/odb/source.h @@ -190,7 +190,8 @@ struct odb_source { * has been freshened. */ int (*freshen_object)(struct odb_source *source, - const struct object_id *oid); + const struct object_id *oid, + const time_t *mtime); /* * This callback is expected to persist the given object into the @@ -206,8 +207,9 @@ struct odb_source { int (*write_object)(struct odb_source *source, const void *buf, size_t len, enum object_type type, - struct object_id *oid, - struct object_id *compat_oid, + const struct object_id *oid, + const struct object_id *compat_oid, + const time_t *mtime, enum odb_write_object_flags flags); /* @@ -403,9 +405,10 @@ static inline int odb_source_find_abbrev_len(struct odb_source *source, * not exist. */ static inline int odb_source_freshen_object(struct odb_source *source, - const struct object_id *oid) + const struct object_id *oid, + const time_t *mtime) { - return source->freshen_object(source, oid); + return source->freshen_object(source, oid, mtime); } /* @@ -416,12 +419,13 @@ static inline int odb_source_freshen_object(struct odb_source *source, static inline int odb_source_write_object(struct odb_source *source, const void *buf, unsigned long len, enum object_type type, - struct object_id *oid, - struct object_id *compat_oid, + const struct object_id *oid, + const struct object_id *compat_oid, + const time_t *mtime, enum odb_write_object_flags flags) { return source->write_object(source, buf, len, type, oid, - compat_oid, flags); + compat_oid, mtime, flags); } /* diff --git a/pager.c b/pager.c index 35b210e0484f90..543ef129366a19 100644 --- a/pager.c +++ b/pager.c @@ -5,6 +5,8 @@ #include "run-command.h" #include "sigchain.h" #include "alias.h" +#include "repository.h" +#include "environment.h" int pager_use_color = 1; @@ -13,7 +15,6 @@ int pager_use_color = 1; #endif static struct child_process pager_process; -static char *pager_program; static int old_fd1 = -1, old_fd2 = -1; /* Is the value coming back from term_columns() just a guess? */ @@ -75,10 +76,17 @@ static void wait_for_pager_signal(int signo) static int core_pager_config(const char *var, const char *value, const struct config_context *ctx UNUSED, - void *data UNUSED) + void *data) { - if (!strcmp(var, "core.pager")) - return git_config_string(&pager_program, var, value); + struct repository *r = data; + + if (!strcmp(var, "core.pager")) { + struct repo_config_values *cfg = repo_config_values(r); + + FREE_AND_NULL(cfg->pager_program); + return git_config_string(&cfg->pager_program, var, value); + } + return 0; } @@ -91,10 +99,12 @@ const char *git_pager(struct repository *r, int stdout_is_tty) pager = getenv("GIT_PAGER"); if (!pager) { - if (!pager_program) + struct repo_config_values *cfg = repo_config_values(r); + + if (!cfg->pager_program) read_early_config(r, - core_pager_config, NULL); - pager = pager_program; + core_pager_config, r); + pager = cfg->pager_program; } if (!pager) pager = getenv("PAGER"); @@ -302,7 +312,11 @@ int check_pager_config(struct repository *r, const char *cmd) read_early_config(r, pager_command_config, &data); - if (data.value) - pager_program = data.value; + if (data.value) { + struct repo_config_values *cfg = repo_config_values(r); + + free(cfg->pager_program); + cfg->pager_program = data.value; + } return data.want; } diff --git a/pathspec.c b/pathspec.c index f78b22709ccb67..281858f21f9c59 100644 --- a/pathspec.c +++ b/pathspec.c @@ -847,9 +847,9 @@ int pathspec_needs_expanded_index(struct index_state *istate, * - not-in-cone/bar*: may need expanded index * - **.c: may need expanded index */ - if (strspn(item.original + item.nowildcard_len, "*") == + if (strspn(item.match + item.nowildcard_len, "*") == (unsigned int)(item.len - item.nowildcard_len) && - path_in_cone_mode_sparse_checkout(item.original, istate)) + path_in_cone_mode_sparse_checkout(item.match, istate)) continue; for (pos = 0; pos < istate->cache_nr; pos++) { @@ -865,7 +865,7 @@ int pathspec_needs_expanded_index(struct index_state *istate, */ if ((unsigned int)item.nowildcard_len > ce_namelen(ce) && - !strncmp(item.original, ce->name, + !strncmp(item.match, ce->name, ce_namelen(ce))) { res = 1; break; @@ -876,13 +876,13 @@ int pathspec_needs_expanded_index(struct index_state *istate, * directory and the pathspec does not match the whole * directory, need to expand the index. */ - if (!strncmp(item.original, ce->name, item.nowildcard_len) && - wildmatch(item.original, ce->name, 0)) { + if (!strncmp(item.match, ce->name, item.nowildcard_len) && + wildmatch(item.match, ce->name, 0)) { res = 1; break; } } - } else if (!path_in_cone_mode_sparse_checkout(item.original, istate) && + } else if (!path_in_cone_mode_sparse_checkout(item.match, istate) && !matches_skip_worktree(pathspec, i, &skip_worktree_seen)) res = 1; diff --git a/prompt.c b/prompt.c index 706fba2a5030c7..d8d74c7e379dbe 100644 --- a/prompt.c +++ b/prompt.c @@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "parse.h" #include "environment.h" +#include "repository.h" #include "run-command.h" #include "strbuf.h" #include "prompt.h" @@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags) askpass = getenv("GIT_ASKPASS"); if (!askpass) - askpass = askpass_program; + askpass = repo_config_values(the_repository)->askpass_program; if (!askpass) askpass = getenv("SSH_ASKPASS"); if (askpass && *askpass) diff --git a/protocol-caps.c b/protocol-caps.c index 8858ea4489d714..02261be14d817a 100644 --- a/protocol-caps.c +++ b/protocol-caps.c @@ -30,6 +30,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list) return 1; } +/* + * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info() + * but uses the flags: + * + * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a + * object-info request asks for an OID that it doesn't have. + * + * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found. + */ +static enum object_type get_object_info(struct object_database *odb, + const struct object_id *oid, + size_t *sizep) +{ + enum object_type type; + struct object_info oi = OBJECT_INFO_INIT; + + oi.typep = &type; + oi.sizep = sizep; + if (odb_read_object_info_extended(odb, oid, &oi, + OBJECT_INFO_LOOKUP_REPLACE | + OBJECT_INFO_SKIP_FETCH_OBJECT | + OBJECT_INFO_QUICK) < 0) + return OBJ_BAD; + return type; +} + /* * Validates and send requested info back to the client. Any errors detected * are returned as they are detected. @@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer, strbuf_addstr(&send_buffer, oid_str); + /* + * Check the existence of the object first. + * If an object is not recognized by the server append SP to + * the response. + */ + if (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) { + strbuf_addstr(&send_buffer, " "); + goto write; + } + if (info->size) { - if (odb_read_object_info(r->objects, &oid, &object_size) < 0) { - strbuf_addstr(&send_buffer, " "); - } else { - strbuf_addf(&send_buffer, " %"PRIuMAX, - (uintmax_t)object_size); - } + strbuf_addf(&send_buffer, " %"PRIuMAX, + (uintmax_t)object_size); } +write: packet_writer_write(writer, "%s", send_buffer.buf); strbuf_reset(&send_buffer); } diff --git a/reachable.c b/reachable.c index caadacc02ad2ad..3079d2c5f4c441 100644 --- a/reachable.c +++ b/reachable.c @@ -62,7 +62,7 @@ static void add_rebase_files(struct rev_info *revs) "rebase-merge/autostash", "rebase-merge/orig-head", }; - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(the_repository); for (struct worktree **wt = worktrees; *wt; wt++) { char *wt_gitdir = get_worktree_git_dir(*wt); @@ -322,7 +322,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog, /* detached HEAD is not included in the list above */ refs_head_ref(get_main_ref_store(the_repository), add_one_ref, revs); - other_head_refs(add_one_ref, revs); + other_head_refs(the_repository, add_one_ref, revs); /* rebase autostash and orig-head */ add_rebase_files(revs); diff --git a/read-cache.c b/read-cache.c index 38b55323dd739a..6c449f393d8d4d 100644 --- a/read-cache.c +++ b/read-cache.c @@ -205,13 +205,11 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st static unsigned int st_mode_from_ce(const struct cache_entry *ce) { - extern int trust_executable_bit, has_symlinks; - switch (ce->ce_mode & S_IFMT) { case S_IFLNK: - return has_symlinks ? S_IFLNK : (S_IFREG | 0644); + return repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644); case S_IFREG: - return (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG; + return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG; case S_IFGITLINK: return S_IFDIR | 0755; case S_IFDIR: @@ -321,13 +319,13 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st) /* We consider only the owner x bit to be relevant for * "mode changes" */ - if (trust_executable_bit && + if (repo_trust_executable_bit(the_repository) && (0100 & (ce->ce_mode ^ st->st_mode))) changed |= MODE_CHANGED; break; case S_IFLNK: if (!S_ISLNK(st->st_mode) && - (has_symlinks || !S_ISREG(st->st_mode))) + (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode))) changed |= TYPE_CHANGED; break; case S_IFGITLINK: @@ -742,7 +740,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, ce->ce_flags |= CE_INTENT_TO_ADD; - if (trust_executable_bit && has_symlinks) { + if (repo_trust_executable_bit(istate->repo) && + repo_has_symlinks(istate->repo)) { ce->ce_mode = create_ce_mode(st_mode); } else { /* If there is an existing entry, pick the mode bits and type @@ -752,7 +751,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int pos = index_name_pos_also_unmerged(istate, path, namelen); ent = (0 <= pos) ? istate->cache[pos] : NULL; - ce->ce_mode = ce_mode_from_stat(ent, st_mode); + ce->ce_mode = ce_mode_from_stat(istate->repo, ent, st_mode); } /* When core.ignorecase=true, determine if a directory of the same name but differing @@ -2342,7 +2341,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) */ static void freshen_shared_index(const char *shared_index, int warn) { - if (!check_and_freshen_file(shared_index, 1) && warn) + if (!check_and_freshen_file(shared_index, 1, NULL) && warn) warning(_("could not freshen shared index '%s'"), shared_index); } diff --git a/read-cache.h b/read-cache.h index 043da1f1aae706..ab9d40aa81f95e 100644 --- a/read-cache.h +++ b/read-cache.h @@ -4,15 +4,23 @@ #include "read-cache-ll.h" #include "object.h" #include "pathspec.h" +#include "environment.h" -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce, +/* + * Determine the appropriate index mode for a file based on its stat() + * information and the existing cache entry (if any). + * + * This function handles degradation for filesystems that lack + * symlink support or reliable executable bits. + */ +static inline unsigned int ce_mode_from_stat(struct repository *repo, + const struct cache_entry *ce, unsigned int mode) { - extern int trust_executable_bit, has_symlinks; - if (!has_symlinks && S_ISREG(mode) && + if (S_ISREG(mode) && !repo_has_symlinks(repo) && ce && S_ISLNK(ce->ce_mode)) return ce->ce_mode; - if (!trust_executable_bit && S_ISREG(mode)) { + if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) { if (ce && S_ISREG(ce->ce_mode)) return ce->ce_mode; return create_ce_mode(0666); diff --git a/ref-filter.c b/ref-filter.c index 284796c49b2986..29aca08ce7b333 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2402,7 +2402,7 @@ static void lazy_init_worktree_map(void) if (ref_to_worktree_map.worktrees) return; - ref_to_worktree_map.worktrees = get_worktrees(); + ref_to_worktree_map.worktrees = get_worktrees(the_repository); hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0); populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees); } diff --git a/refs.c b/refs.c index 1d2463789167c9..92d5df5b71fa4b 100644 --- a/refs.c +++ b/refs.c @@ -2,8 +2,6 @@ * The backend-independent part of the reference module. */ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "abspath.h" #include "advice.h" @@ -744,14 +742,15 @@ static char *substitute_branch_name(struct repository *r, return NULL; } -void copy_branchname(struct strbuf *sb, const char *name, +void copy_branchname(struct repository *repo, + struct strbuf *sb, const char *name, enum interpret_branch_kind allowed) { int len = strlen(name); struct interpret_branch_name_options options = { .allowed = allowed }; - int used = repo_interpret_branch_name(the_repository, name, len, sb, + int used = repo_interpret_branch_name(repo, name, len, sb, &options); if (used < 0) @@ -759,10 +758,10 @@ void copy_branchname(struct strbuf *sb, const char *name, strbuf_add(sb, name + used, len - used); } -int check_branch_ref(struct strbuf *sb, const char *name) +int check_branch_ref(struct repository *repo, struct strbuf *sb, const char *name) { if (startup_info->have_repository) - copy_branchname(sb, name, INTERPRET_BRANCH_LOCAL); + copy_branchname(repo, sb, name, INTERPRET_BRANCH_LOCAL); else strbuf_addstr(sb, name); @@ -3326,9 +3325,9 @@ static int move_files(const char *from_path, const char *to_path, struct strbuf return ret; } -static int has_worktrees(void) +static int has_worktrees(struct repository *repo) { - struct worktree **worktrees = get_worktrees(); + struct worktree **worktrees = get_worktrees(repo); int ret = 0; size_t i; @@ -3373,12 +3372,8 @@ int repo_migrate_ref_storage_format(struct repository *repo, * Worktrees complicate the migration because every worktree has a * separate ref storage. While it should be feasible to implement, this * is pushed out to a future iteration. - * - * TODO: we should really be passing the caller-provided repository to - * `has_worktrees()`, but our worktree subsystem doesn't yet support - * that. */ - if (has_worktrees()) { + if (has_worktrees(repo)) { strbuf_addstr(errbuf, "migrating repositories with worktrees is not supported yet"); ret = -1; goto done; @@ -3503,7 +3498,7 @@ int repo_migrate_ref_storage_format(struct repository *repo, * repository format so that clients will use the new ref store. * We also need to swap out the repository's main ref store. */ - initialize_repository_version(the_repository, hash_algo_by_ptr(repo->hash_algo), format, 1); + initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format, 1); /* * Unset the old ref store and release it. `get_main_ref_store()` will diff --git a/refs.h b/refs.h index a381022c77065a..9979446d15fd3b 100644 --- a/refs.h +++ b/refs.h @@ -234,7 +234,8 @@ char *repo_default_branch_name(struct repository *r, int quiet); * If "allowed" is non-zero, restrict the set of allowed expansions. See * repo_interpret_branch_name() for details. */ -void copy_branchname(struct strbuf *sb, const char *name, +void copy_branchname(struct repository *repo, + struct strbuf *sb, const char *name, enum interpret_branch_kind allowed); /* @@ -243,7 +244,7 @@ void copy_branchname(struct strbuf *sb, const char *name, * * The return value is "0" if the result is valid, and "-1" otherwise. */ -int check_branch_ref(struct strbuf *sb, const char *name); +int check_branch_ref(struct repository *repo, struct strbuf *sb, const char *name); /* * Similar for a tag name in refs/tags/. diff --git a/refs/files-backend.c b/refs/files-backend.c index e1d963a6f359b8..1cc20aa486c263 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "../git-compat-util.h" @@ -29,6 +28,9 @@ #include "../revision.h" #include +/* So that we can drop `USE_THE_REPOSITORY_VARIABLE`. */ +extern int ignore_case; + /* * This backend uses the following flags in `ref_update::flags` for * internal bookkeeping purposes. Their numerical values must not @@ -788,7 +790,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, files_ref_path(refs, &ref_file, refname); retry: - switch (safe_create_leading_directories(the_repository, ref_file.buf)) { + switch (safe_create_leading_directories(refs->base.repo, ref_file.buf)) { case SCLD_OK: break; /* success */ case SCLD_EXISTS: @@ -842,7 +844,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, goto error_return; } - if (hold_lock_file_for_update_timeout( + if (repo_hold_lock_file_for_update_timeout(refs->base.repo, &lock->lk, ref_file.buf, LOCK_NO_DEREF, get_files_ref_lock_timeout_ms(transaction->ref_store->repo)) < 0) { int myerr = errno; @@ -857,7 +859,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, } else { unable_to_lock_message(ref_file.buf, myerr, err); if (myerr == EEXIST) { - if (repo_ignore_case(the_repository) && + if (repo_ignore_case(refs->base.repo) && transaction_has_case_conflicting_update(transaction, update)) { /* * In case-insensitive filesystems, ensure that conflicts within a @@ -971,7 +973,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, * conflicts between 'foo' and 'Foo/bar'. So let's lowercase * the refname. */ - if (repo_ignore_case(the_repository)) { + if (repo_ignore_case(refs->base.repo)) { struct strbuf lower = STRBUF_INIT; strbuf_addstr(&lower, refname); @@ -1164,7 +1166,8 @@ typedef int create_file_fn(const char *path, void *cb); * recent call of fn. fn is always called at least once, and will be * called more than once if it returns ENOENT or EISDIR. */ -static int raceproof_create_file(const char *path, create_file_fn fn, void *cb) +static int raceproof_create_file(struct files_ref_store *refs, + const char *path, create_file_fn fn, void *cb) { /* * The number of times we will try to remove empty directories @@ -1220,7 +1223,7 @@ static int raceproof_create_file(const char *path, create_file_fn fn, void *cb) strbuf_addstr(&path_copy, path); do { - scld_result = safe_create_leading_directories(the_repository, path_copy.buf); + scld_result = safe_create_leading_directories(refs->base.repo, path_copy.buf); if (scld_result == SCLD_OK) goto retry_fn; } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); @@ -1250,8 +1253,8 @@ struct create_reflock_cb { static int create_reflock(const char *path, void *cb) { struct create_reflock_cb *data = cb; - return hold_lock_file_for_update_timeout( - data->lk, path, LOCK_NO_DEREF, + return repo_hold_lock_file_for_update_timeout( + data->repo, data->lk, path, LOCK_NO_DEREF, get_files_ref_lock_timeout_ms(data->repo)) < 0 ? -1 : 0; } @@ -1289,7 +1292,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, cb_data.lk = &lock->lk; cb_data.repo = refs->base.repo; - if (raceproof_create_file(ref_file.buf, create_reflock, &cb_data)) { + if (raceproof_create_file(refs, ref_file.buf, create_reflock, &cb_data)) { unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } @@ -1383,7 +1386,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) ref_transaction_add_update( transaction, r->name, REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING, - null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, + null_oid(refs->base.repo->hash_algo), &r->oid, NULL, NULL, NULL, NULL, NULL); if (ref_transaction_commit(transaction, &err)) goto cleanup; @@ -1629,7 +1632,7 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname) files_reflog_path(refs, &path, newrefname); files_reflog_path(refs, &tmp, TMP_RENAMED_LOG); cb.tmp_renamed_log = tmp.buf; - ret = raceproof_create_file(path.buf, rename_tmp_log_callback, &cb); + ret = raceproof_create_file(refs, path.buf, rename_tmp_log_callback, &cb); if (ret) { if (errno == EISDIR) error("directory not empty: %s", path.buf); @@ -1736,7 +1739,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, goto out; } - if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) { + if (copy && log && copy_file(refs->base.repo, tmp_renamed_log.buf, sb_oldref.buf, 0644)) { ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s", oldrefname, strerror(errno)); goto out; @@ -1916,13 +1919,13 @@ static int log_ref_setup(struct files_ref_store *refs, char *logfile; if (log_refs_cfg == LOG_REFS_UNSET) - log_refs_cfg = is_bare_repository(the_repository) ? LOG_REFS_NONE : LOG_REFS_NORMAL; + log_refs_cfg = is_bare_repository(refs->base.repo) ? LOG_REFS_NONE : LOG_REFS_NORMAL; files_reflog_path(refs, &logfile_sb, refname); logfile = strbuf_detach(&logfile_sb, NULL); if (force_create || should_autocreate_reflog(log_refs_cfg, refname)) { - if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) { + if (raceproof_create_file(refs, logfile, open_or_create_logfile, logfd)) { if (errno == ENOENT) strbuf_addf(err, "unable to create directory for '%s': " "%s", logfile, strerror(errno)); @@ -1955,7 +1958,7 @@ static int log_ref_setup(struct files_ref_store *refs, } if (*logfd >= 0) - adjust_shared_perm(the_repository, logfile); + adjust_shared_perm(refs->base.repo, logfile); free(logfile); return 0; @@ -3581,7 +3584,9 @@ static int files_reflog_expire(struct ref_store *ref_store, * work we need, including cleaning up if the program * exits unexpectedly. */ - if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) { + if (repo_hold_lock_file_for_update(ref_store->repo, + &reflog_lock, log_file, + 0) < 0) { struct strbuf err = STRBUF_INIT; unable_to_lock_message(log_file, errno, &err); error("%s", err.buf); @@ -3672,8 +3677,8 @@ static int files_ref_store_create_on_disk(struct ref_store *ref_store, * they do not understand the reference format extension. */ strbuf_addf(&sb, "%s/refs", ref_store->gitdir); - safe_create_dir(the_repository, sb.buf, 1); - adjust_shared_perm(the_repository, sb.buf); + safe_create_dir(refs->base.repo, sb.buf, 1); + adjust_shared_perm(refs->base.repo, sb.buf); /* * There is no need to create directories for common refs when creating @@ -3685,11 +3690,11 @@ static int files_ref_store_create_on_disk(struct ref_store *ref_store, */ strbuf_reset(&sb); files_ref_path(refs, &sb, "refs/heads"); - safe_create_dir(the_repository, sb.buf, 1); + safe_create_dir(refs->base.repo, sb.buf, 1); strbuf_reset(&sb); files_ref_path(refs, &sb, "refs/tags"); - safe_create_dir(the_repository, sb.buf, 1); + safe_create_dir(refs->base.repo, sb.buf, 1); } strbuf_release(&sb); diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 499cb55dface4f..b9b04b70105f9a 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "../git-compat-util.h" @@ -162,6 +161,13 @@ struct packed_ref_store { * `packed_ref_store`) must not be freed. */ struct tempfile *tempfile; + + /* + * Timeout when taking the "packed-refs.lock" file. configurable via + * "core.packedRefsTimeout". + */ + bool timeout_configured; + int timeout_value; }; /* @@ -1233,12 +1239,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) struct packed_ref_store *refs = packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN, "packed_refs_lock"); - static int timeout_configured = 0; - static int timeout_value = 1000; - if (!timeout_configured) { - repo_config_get_int(the_repository, "core.packedrefstimeout", &timeout_value); - timeout_configured = 1; + if (!refs->timeout_configured) { + if (repo_config_get_int(ref_store->repo, "core.packedrefstimeout", + &refs->timeout_value)) + refs->timeout_value = 1000; + refs->timeout_configured = true; } /* @@ -1246,10 +1252,9 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) * don't write new content to it, but rather to a separate * tempfile. */ - if (hold_lock_file_for_update_timeout( - &refs->lock, - refs->path, - flags, timeout_value) < 0) { + if (repo_hold_lock_file_for_update_timeout(ref_store->repo, &refs->lock, + refs->path, flags, + refs->timeout_value) < 0) { unable_to_lock_message(refs->path, errno, err); return -1; } @@ -1393,7 +1398,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re packed_refs_path = get_locked_file_path(&refs->lock); strbuf_addf(&sb, "%s.new", packed_refs_path); free(packed_refs_path); - refs->tempfile = create_tempfile(sb.buf); + refs->tempfile = repo_create_tempfile(refs->base.repo, sb.buf); if (!refs->tempfile) { strbuf_addf(err, "unable to create file %s: %s", sb.buf, strerror(errno)); diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 1db48e801d63d0..8a7b3238254d3d 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -21,7 +21,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, /* * Lock the packed-refs file for writing. Flags is passed to - * hold_lock_file_for_update(). Return 0 on success. On errors, write + * repo_hold_lock_file_for_update(). Return 0 on success. On errors, write * an error message to `err` and return a nonzero value. */ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err); diff --git a/refspec.c b/refspec.c index fb89bce1db23fb..7cb479983b507b 100644 --- a/refspec.c +++ b/refspec.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -16,7 +15,8 @@ * Parses the provided refspec 'refspec' and populates the refspec_item 'item'. * Returns 1 if successful and 0 if the refspec is invalid. */ -static int parse_refspec(struct refspec_item *item, const char *refspec, int fetch) +static int parse_refspec(struct refspec_item *item, const char *refspec, + const struct git_hash_algo *algo, int fetch) { size_t llen; int is_glob; @@ -84,7 +84,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet */ if (!*item->src) return 0; /* negative refspecs must not be empty */ - else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused)) + else if (llen == algo->hexsz && !get_oid_hex_algop(item->src, &unused, algo)) return 0; /* negative refspecs cannot be exact sha1 */ else if (!check_refname_format(item->src, flags)) ; /* valid looking ref is ok */ @@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet /* LHS */ if (!*item->src) ; /* empty is ok; it means "HEAD" */ - else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused)) + else if (llen == algo->hexsz && !get_oid_hex_algop(item->src, &unused, algo)) item->exact_sha1 = 1; /* ok */ else if (!check_refname_format(item->src, flags)) ; /* valid looking ref is ok */ @@ -154,21 +154,23 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet } static int refspec_item_init(struct refspec_item *item, const char *refspec, - int fetch) + const struct git_hash_algo *algo, int fetch) { memset(item, 0, sizeof(*item)); item->raw = xstrdup(refspec); - return parse_refspec(item, refspec, fetch); + return parse_refspec(item, refspec, algo, fetch); } -int refspec_item_init_fetch(struct refspec_item *item, const char *refspec) +int refspec_item_init_fetch(struct refspec_item *item, const char *refspec, + const struct git_hash_algo *algo) { - return refspec_item_init(item, refspec, 1); + return refspec_item_init(item, refspec, algo, 1); } -int refspec_item_init_push(struct refspec_item *item, const char *refspec) +int refspec_item_init_push(struct refspec_item *item, const char *refspec, + const struct git_hash_algo *algo) { - return refspec_item_init(item, refspec, 0); + return refspec_item_init(item, refspec, algo, 0); } void refspec_item_clear(struct refspec_item *item) @@ -182,15 +184,15 @@ void refspec_item_clear(struct refspec_item *item) item->exact_sha1 = 0; } -void refspec_init_fetch(struct refspec *rs) +void refspec_init_fetch(struct refspec *rs, const struct git_hash_algo *algo) { - struct refspec blank = REFSPEC_INIT_FETCH; + struct refspec blank = REFSPEC_INIT_FETCH(algo); memcpy(rs, &blank, sizeof(*rs)); } -void refspec_init_push(struct refspec *rs) +void refspec_init_push(struct refspec *rs, const struct git_hash_algo *algo) { - struct refspec blank = REFSPEC_INIT_PUSH; + struct refspec blank = REFSPEC_INIT_PUSH(algo); memcpy(rs, &blank, sizeof(*rs)); } @@ -200,9 +202,9 @@ void refspec_append(struct refspec *rs, const char *refspec) int ret; if (rs->fetch) - ret = refspec_item_init_fetch(&item, refspec); + ret = refspec_item_init_fetch(&item, refspec, rs->hash_algo); else - ret = refspec_item_init_push(&item, refspec); + ret = refspec_item_init_push(&item, refspec, rs->hash_algo); if (!ret) die(_("invalid refspec '%s'"), refspec); @@ -246,10 +248,11 @@ void refspec_clear(struct refspec *rs) rs->fetch = 0; } -int valid_fetch_refspec(const char *fetch_refspec_str) +int valid_fetch_refspec(const char *fetch_refspec_str, + const struct git_hash_algo *algo) { struct refspec_item refspec; - int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str); + int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str, algo); refspec_item_clear(&refspec); return ret; } diff --git a/refspec.h b/refspec.h index 8b04f9995ef2a8..fadef67933c079 100644 --- a/refspec.h +++ b/refspec.h @@ -1,6 +1,10 @@ #ifndef REFSPEC_H #define REFSPEC_H +struct git_hash_algo; +struct string_list; +struct strvec; + #define TAG_REFSPEC "refs/tags/*:refs/tags/*" /** @@ -30,10 +34,11 @@ struct refspec_item { char *raw; }; -struct string_list; - -#define REFSPEC_INIT_FETCH { .fetch = 1 } -#define REFSPEC_INIT_PUSH { .fetch = 0 } +int refspec_item_init_fetch(struct refspec_item *item, const char *refspec, + const struct git_hash_algo *algo); +int refspec_item_init_push(struct refspec_item *item, const char *refspec, + const struct git_hash_algo *algo); +void refspec_item_clear(struct refspec_item *item); /** * An array of strings can be parsed into a struct refspec using @@ -44,23 +49,30 @@ struct refspec { int alloc; int nr; + const struct git_hash_algo *hash_algo; unsigned fetch : 1; }; -int refspec_item_init_fetch(struct refspec_item *item, const char *refspec); -int refspec_item_init_push(struct refspec_item *item, const char *refspec); -void refspec_item_clear(struct refspec_item *item); -void refspec_init_fetch(struct refspec *rs); -void refspec_init_push(struct refspec *rs); +#define REFSPEC_INIT_FETCH(algo) { \ + .fetch = 1, \ + .hash_algo = (algo), \ +} +#define REFSPEC_INIT_PUSH(algo) { \ + .fetch = 0, \ + .hash_algo = (algo), \ +} + +void refspec_init_fetch(struct refspec *rs, const struct git_hash_algo *hash_algo); +void refspec_init_push(struct refspec *rs, const struct git_hash_algo *hash_algo); +void refspec_clear(struct refspec *rs); + void refspec_append(struct refspec *rs, const char *refspec); __attribute__((format (printf,2,3))) void refspec_appendf(struct refspec *rs, const char *fmt, ...); void refspec_appendn(struct refspec *rs, const char **refspecs, int nr); -void refspec_clear(struct refspec *rs); -int valid_fetch_refspec(const char *refspec); +int valid_fetch_refspec(const char *refspec, const struct git_hash_algo *algo); -struct strvec; /* * Determine what values to pass to the peer in ref-prefix lines * (see linkgit:gitprotocol-v2[5]). @@ -76,7 +88,7 @@ int refname_matches_negative_refspec_item(const char *refname, struct refspec *r * Returns 1 if refname matches pattern, 0 otherwise. */ int match_refname_with_pattern(const char *pattern, const char *refname, - const char *replacement, char **result); + const char *replacement, char **result); /* * Queries a refspec for a match and updates the query item. @@ -89,8 +101,8 @@ int refspec_find_match(struct refspec *rs, struct refspec_item *query); * list. */ void refspec_find_all_matches(struct refspec *rs, - struct refspec_item *query, - struct string_list *results); + struct refspec_item *query, + struct string_list *results); /* * Remove all entries in the input list which match any negative refspec in diff --git a/remote-curl.c b/remote-curl.c index 9e614c5567417f..2c35dd52400f83 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -1340,10 +1340,9 @@ static void parse_get(const char *arg) fflush(stdout); } -static int push_dav(int nr_spec, const char **specs) +static int push_dav(const char **specs) { struct child_process child = CHILD_PROCESS_INIT; - size_t i; child.git_cmd = 1; strvec_push(&child.args, "http-push"); @@ -1353,15 +1352,14 @@ static int push_dav(int nr_spec, const char **specs) if (options.verbosity > 1) strvec_push(&child.args, "--verbose"); strvec_push(&child.args, url.buf); - for (i = 0; i < nr_spec; i++) - strvec_push(&child.args, specs[i]); + strvec_pushv(&child.args, specs); if (run_command(&child)) die(_("git-http-push failed")); return 0; } -static int push_git(struct discovery *heads, int nr_spec, const char **specs) +static int push_git(struct discovery *heads, const char **specs) { struct rpc_state rpc = RPC_STATE_INIT; int i, err; @@ -1400,8 +1398,8 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs) strvec_push(&args, "--force-if-includes"); strvec_push(&args, "--stdin"); - for (i = 0; i < nr_spec; i++) - packet_buf_write(&preamble, "%s\n", specs[i]); + for (; *specs; specs++) + packet_buf_write(&preamble, "%s\n", *specs); packet_buf_flush(&preamble); memset(&rpc, 0, sizeof(rpc)); @@ -1416,15 +1414,15 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs) return err; } -static int push(int nr_spec, const char **specs) +static int push(const char **specs) { struct discovery *heads = discover_refs("git-receive-pack", 1); int ret; if (heads->proto_git) - ret = push_git(heads, nr_spec, specs); + ret = push_git(heads, specs); else - ret = push_dav(nr_spec, specs); + ret = push_dav(specs); free_discovery(heads); return ret; } @@ -1448,7 +1446,7 @@ static void parse_push(struct strbuf *buf) break; } while (1); - ret = push(specs.nr, specs.v); + ret = push(specs.v); printf("\n"); fflush(stdout); diff --git a/remote.c b/remote.c index b17648d6ef32e4..b55fc6e18a487f 100644 --- a/remote.c +++ b/remote.c @@ -150,8 +150,8 @@ static struct remote *make_remote(struct remote_state *remote_state, ret->prune = -1; /* unspecified */ ret->prune_tags = -1; /* unspecified */ ret->name = xstrndup(name, len); - refspec_init_push(&ret->push); - refspec_init_fetch(&ret->fetch); + refspec_init_push(&ret->push, the_hash_algo); + refspec_init_fetch(&ret->fetch, the_hash_algo); string_list_init_dup(&ret->server_options); string_list_init_dup(&ret->negotiation_restrict); string_list_init_dup(&ret->negotiation_include); @@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo, if (remote->mirror) return tracking_for_push_dest(remote, branch->refname, err); - switch (push_default) { + switch (repo_config_values(repo)->push_default) { case PUSH_DEFAULT_NOTHING: return error_buf(err, _("push has no destination (push.default is 'nothing')")); @@ -3041,7 +3041,7 @@ int valid_remote_name(const char *name) int result; struct strbuf refspec = STRBUF_INIT; strbuf_addf(&refspec, "refs/heads/test:refs/remotes/%s/test", name); - result = valid_fetch_refspec(refspec.buf); + result = valid_fetch_refspec(refspec.buf, the_hash_algo); strbuf_release(&refspec); return result; } diff --git a/repack-midx.c b/repack-midx.c index 7c7c3620e50b7d..64c7f8d0f42372 100644 --- a/repack-midx.c +++ b/repack-midx.c @@ -954,7 +954,8 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts) lock_name.buf)) die_errno(_("unable to create leading directories of %s"), lock_name.buf); - hold_lock_file_for_update(&lf, lock_name.buf, LOCK_DIE_ON_ERROR); + repo_hold_lock_file_for_update(opts->existing->repo, &lf, lock_name.buf, + LOCK_DIE_ON_ERROR); if (!fdopen_lock_file(&lf, "w")) { ret = error_errno(_("unable to open multi-pack-index chain file")); diff --git a/repository.c b/repository.c index 2ef0778846bcf1..651b0f69339452 100644 --- a/repository.c +++ b/repository.c @@ -390,6 +390,7 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->parsed_objects); repo_settings_clear(repo); + repo_config_values_clear(&repo->config_values_private_); if (repo->config) { git_configset_clear(repo->config); @@ -473,5 +474,5 @@ int repo_hold_locked_index(struct repository *repo, { if (!repo->index_file) BUG("the repo hasn't been setup"); - return hold_lock_file_for_update(lf, repo->index_file, flags); + return repo_hold_lock_file_for_update(repo, lf, repo->index_file, flags); } diff --git a/repository.h b/repository.h index b7673079119e6f..3b467a25133670 100644 --- a/repository.h +++ b/repository.h @@ -167,7 +167,7 @@ struct repository { /* Repository's compatibility hash algorithm. */ const struct git_hash_algo *compat_hash_algo; - /* Repository's config values parsed by git_default_config() */ + /* Repository-specific configuration values. */ struct repo_config_values config_values_private_; /* Repository's reference storage format, as serialized on disk. */ diff --git a/rerere.c b/rerere.c index 216100925a1843..0015e25050df76 100644 --- a/rerere.c +++ b/rerere.c @@ -756,7 +756,7 @@ static void do_rerere_one_path(struct index_state *istate, /* Has the user resolved it already? */ if (variant >= 0) { if (!handle_file(istate, path, NULL, NULL)) { - copy_file(rerere_path(&buf, id, "postimage"), path, 0666); + copy_file(the_repository, rerere_path(&buf, id, "postimage"), path, 0666); id->collection->status[variant] |= RR_HAS_POSTIMAGE; fprintf_ln(stderr, _("Recorded resolution for '%s'."), path); free_rerere_id(rr_item); @@ -911,9 +911,9 @@ int setup_rerere(struct repository *r, struct string_list *merge_rr, int flags) if (flags & RERERE_READONLY) fd = 0; else - fd = hold_lock_file_for_update(&write_lock, - git_path_merge_rr(r), - LOCK_DIE_ON_ERROR); + fd = repo_hold_lock_file_for_update(r, &write_lock, + git_path_merge_rr(r), + LOCK_DIE_ON_ERROR); read_rr(r, merge_rr); return fd; } diff --git a/revision.c b/revision.c index fec5472580e058..8eb7cb9bbccd1e 100644 --- a/revision.c +++ b/revision.c @@ -1711,7 +1711,7 @@ static void add_other_reflogs_to_pending(struct all_refs_cb *cb) { struct worktree **worktrees, **p; - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); for (p = worktrees; *p; p++) { struct worktree *wt = *p; @@ -1837,7 +1837,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags) if (revs->single_worktree) return; - worktrees = get_worktrees(); + worktrees = get_worktrees(the_repository); for (p = worktrees; *p; p++) { struct worktree *wt = *p; struct index_state istate = INDEX_STATE_INIT(revs->repo); @@ -2632,6 +2632,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->graph = NULL; } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) { revs->graph_max_lanes = parse_count(optarg); + } else if (!strcmp(arg, "--graph-indent")) { + revs->no_graph_indent = 0; + revs->graph_indent_set = 1; + } else if (!strcmp(arg, "--no-graph-indent")) { + revs->no_graph_indent = 1; + revs->graph_indent_set = 1; } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -2818,7 +2824,7 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); - other_head_refs(handle_one_ref, &cb); + other_head_refs(the_repository, handle_one_ref, &cb); } clear_ref_exclusions(&revs->ref_excludes); } else if (!strcmp(arg, "--branches")) { @@ -3206,6 +3212,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->graph_max_lanes > 0 && !revs->graph) die(_("the option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); + if (revs->graph_indent_set && !revs->graph) + die(_("the option '%s' requires '%s'"), "--[no-]graph-indent", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); @@ -4664,12 +4673,34 @@ static void retrieve_oldest_commits(struct rev_info *revs, commit_list_insert(c, queue); } +/* + * Returns the next commit that will be shown, regardless of whether it comes + * directly from the revision walk or from the list saved by the staged output + * of --max-count-oldest. + */ +static struct commit *next_commit_to_show(struct rev_info *revs) +{ + struct commit *c; + struct commit_list *p; + + if (!revs->max_count_stage) + return get_revision_internal(revs); + + c = pop_commit(&revs->commits); + if (c) { + c->object.flags |= SHOWN; + if (!(c->object.flags & BOUNDARY)) + for (p = c->parents; p; p = p->next) + p->item->object.flags |= CHILD_SHOWN; + } + return c; +} + struct commit *get_revision(struct rev_info *revs) { struct commit *c; struct commit_list *reversed; struct commit_list *queue = NULL; - struct commit_list *p; if (revs->max_count_type == 1 && !revs->max_count_stage) { retrieve_oldest_commits(revs, &queue); @@ -4699,20 +4730,24 @@ struct commit *get_revision(struct rev_info *revs) return c; } - if (revs->max_count_stage) { - c = pop_commit(&revs->commits); - if (c) { - c->object.flags |= SHOWN; - if (!(c->object.flags & BOUNDARY)) - for (p = c->parents; p; p = p->next) - p->item->object.flags |= CHILD_SHOWN; - } + if (revs->graph) { + c = graph_pop_lookahead(revs->graph); + if (!c) + c = next_commit_to_show(revs); } else { - c = get_revision_internal(revs); + c = next_commit_to_show(revs); } - if (c && revs->graph) + if (c && revs->graph) { + while (graph_get_lookahead_room(revs->graph)) { + struct commit *next = next_commit_to_show(revs); + if (!next) + break; + graph_push_lookahead(revs->graph, next); + } graph_update(revs->graph, c); + } + if (!c) { free_saved_parents(revs); commit_list_free(revs->previous_parents); diff --git a/revision.h b/revision.h index 569b3fa1cb5a33..acf6d06b24126c 100644 --- a/revision.h +++ b/revision.h @@ -314,6 +314,8 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; int graph_max_lanes; + unsigned int no_graph_indent:1; + unsigned int graph_indent_set:1; /* special limits */ int skip_count; diff --git a/sequencer.c b/sequencer.c index 1355a99a092268..82ab3c536f94a2 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2260,10 +2260,17 @@ static const char *reflog_message(struct replay_opts *opts, return buf.buf; } -static int do_pick_commit(struct repository *r, - struct todo_item *item, - struct replay_opts *opts, - int final_fixup, int *check_todo) +enum pick_result { + PICK_RESULT_ERROR = -1, + PICK_RESULT_OK, + PICK_RESULT_CONFLICTS, + PICK_RESULT_DROPPED, +}; + +static enum pick_result do_pick_commit(struct repository *r, + struct todo_item *item, + struct replay_opts *opts, + int final_fixup, int *check_todo) { struct replay_ctx *ctx = opts->ctx; unsigned int flags = should_edit(opts) ? EDIT_MSG : 0; @@ -2273,7 +2280,7 @@ static int do_pick_commit(struct repository *r, const char *base_label, *next_label, *reflog_action; char *author = NULL; struct commit_message msg = { NULL, NULL, NULL, NULL }; - int res, unborn = 0, reword = 0, allow, drop_commit; + int res, unborn = 0, reword = 0, allow, drop_commit = 0; enum todo_command command = item->command; struct commit *commit = item->commit; @@ -2419,7 +2426,7 @@ static int do_pick_commit(struct repository *r, } else { const char *dest = git_path_squash_msg(r); unlink(dest); - if (copy_file(dest, rebase_path_squash_msg(), 0666)) { + if (copy_file(r, dest, rebase_path_squash_msg(), 0666)) { res = error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), dest); goto leave; @@ -2453,14 +2460,25 @@ static int do_pick_commit(struct repository *r, struct commit_list *common = NULL; struct commit_list *remotes = NULL; - res = write_message(ctx->message.buf, ctx->message.len, - git_path_merge_msg(r), 0); + if (write_message(ctx->message.buf, ctx->message.len, + git_path_merge_msg(r), 0)) { + res = -1; + goto leave; + } commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res |= try_merge_command(r, opts->strategy, - opts->xopts.nr, opts->xopts.v, + res = try_merge_command(r, opts->strategy, + opts->xopts.nr, opts->xopts.v, common, oid_to_hex(&head), remotes); + /* + * If there were conflicts, try_merge_command() returns 1, + * any other no-zero return code means that either the merge + * command could not be run, or it failed to merge. + */ + if (res && res != 1) + res = -1; + commit_list_free(common); commit_list_free(remotes); } @@ -2492,7 +2510,6 @@ static int do_pick_commit(struct repository *r, goto leave; } - drop_commit = 0; allow = allow_empty(r, opts, commit); if (allow < 0) { res = allow; @@ -2531,6 +2548,12 @@ static int do_pick_commit(struct repository *r, res = run_git_commit(NULL, reflog_action, opts, flags); *check_todo = 1; } + /* + * If "git commit" failed to run then res == -1, but we don't + * want reschedule the last command because the picking the + * commit was successful. + */ + res = !!res; } @@ -2547,7 +2570,14 @@ static int do_pick_commit(struct repository *r, free(author); update_abort_safety_file(); - return res; + if (res < 0) + return PICK_RESULT_ERROR; + else if (res > 0) + return PICK_RESULT_CONFLICTS; + else if (drop_commit) + return PICK_RESULT_DROPPED; + else + return PICK_RESULT_OK; } static int prepare_revs(struct replay_opts *opts) @@ -3864,15 +3894,15 @@ static int error_failed_squash(struct repository *r, int subject_len, const char *subject) { - if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666)) + if (copy_file(r, rebase_path_message(), rebase_path_squash_msg(), 0666)) return error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), rebase_path_message()); unlink(git_path_merge_msg(r)); - if (copy_file(git_path_merge_msg(r), rebase_path_message(), 0666)) + if (copy_file(r, git_path_merge_msg(r), rebase_path_message(), 0666)) return error(_("could not copy '%s' to '%s'"), rebase_path_message(), git_path_merge_msg(r)); - return error_with_patch(r, commit, subject, subject_len, opts, 1, 0); + return error_with_patch(r, commit, subject, subject_len, opts, 1, 1); } static int do_exec(struct repository *r, const char *command_line, int quiet) @@ -4963,37 +4993,59 @@ static int pick_one_commit(struct repository *r, struct replay_opts *opts, int *check_todo, int* reschedule) { - int res; + enum pick_result pick_res; struct todo_item *item = todo_list->items + todo_list->current; const char *arg = todo_item_get_arg(todo_list, item); - res = do_pick_commit(r, item, opts, is_final_fixup(todo_list), - check_todo); - if (is_rebase_i(opts) && res < 0) { + pick_res = do_pick_commit(r, item, opts, is_final_fixup(todo_list), + check_todo); + if (!is_rebase_i(opts)) + switch (pick_res) { + case PICK_RESULT_ERROR: + return -1; + case PICK_RESULT_CONFLICTS: + return 1; + default: + return 0; + } + + if (pick_res == PICK_RESULT_ERROR) { /* Reschedule */ *reschedule = 1; return -1; - } - if (item->command == TODO_EDIT) { + } else if (item->command == TODO_EDIT) { struct commit *commit = item->commit; - if (!res) { + int res = pick_res == PICK_RESULT_CONFLICTS; + int to_amend = pick_res != PICK_RESULT_CONFLICTS && + pick_res != PICK_RESULT_DROPPED; + + /* + * NEEDSWORK: Do not record the commit as rewritten when + * continuing if it was dropped. Does it even make sense + * to stop if the commit was dropped? + */ + if (pick_res == PICK_RESULT_OK || + pick_res == PICK_RESULT_DROPPED) { if (!opts->verbose) term_clear_line(); fprintf(stderr, _("Stopped at %s... %.*s\n"), short_commit_name(r, commit), item->arg_len, arg); } - return error_with_patch(r, commit, - arg, item->arg_len, opts, res, !res); - } - if (is_rebase_i(opts) && !res) + return error_with_patch(r, commit, arg, item->arg_len, opts, + res, to_amend); + } else if (pick_res == PICK_RESULT_OK) { record_in_rewritten(&item->commit->object.oid, peek_command(todo_list, 1)); - if (res && is_fixup(item->command)) { - if (res == 1) - intend_to_amend(); + return 0; + } else if (pick_res == PICK_RESULT_DROPPED) { + if (is_final_fixup(todo_list)) + flush_rewritten_pending(); + return 0; + } else if (pick_res == PICK_RESULT_CONFLICTS && + is_fixup(item->command)) { return error_failed_squash(r, item->commit, opts, item->arg_len, arg); - } else if (res && is_rebase_i(opts) && item->commit) { + } else if (pick_res == PICK_RESULT_CONFLICTS) { int to_amend = 0; struct object_id oid; @@ -5010,11 +5062,11 @@ static int pick_one_commit(struct repository *r, oideq(&opts->squash_onto, &oid)))) to_amend = 1; - return res | error_with_patch(r, item->commit, - arg, item->arg_len, opts, - res, to_amend); + return error_with_patch(r, item->commit, arg, item->arg_len, + opts, 1, to_amend); } - return res; + + BUG("Unhandled return value from do_pick_commit()"); } static int pick_commits(struct repository *r, @@ -5550,7 +5602,15 @@ static int single_pick(struct repository *r, TODO_PICK : TODO_REVERT; item.commit = cmit; - return do_pick_commit(r, &item, opts, 0, &check_todo); + switch (do_pick_commit(r, &item, opts, 0, &check_todo)) { + case PICK_RESULT_ERROR: + return -1; + case PICK_RESULT_CONFLICTS: + return 1; + default: + return 0; + } + } int sequencer_pick_revisions(struct repository *r, diff --git a/serve.c b/serve.c index 49a6e39b1dd25c..2b07d922b3dde1 100644 --- a/serve.c +++ b/serve.c @@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED, trace2_data_string("transfer", NULL, "client-sid", client_sid); } -static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED) +static int object_info_advertise(struct repository *r, struct strbuf *value) { if (advertise_object_info == -1 && repo_config_get_bool(r, "transfer.advertiseobjectinfo", @@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS /* disabled by default */ advertise_object_info = 0; } + /* Currently only size is supported */ + if (value && advertise_object_info) + strbuf_addstr(value, "size"); return advertise_object_info; } diff --git a/setup.c b/setup.c index d31808130b47fc..95909e96031262 100644 --- a/setup.c +++ b/setup.c @@ -2355,7 +2355,7 @@ static void copy_templates_1(struct repository *repo, strbuf_release(&lnk); } else if (S_ISREG(st_template.st_mode)) { - if (copy_file(path->buf, template_path->buf, st_template.st_mode)) + if (copy_file(repo, path->buf, template_path->buf, st_template.st_mode)) die_errno(_("cannot copy '%s' to '%s'"), template_path->buf, path->buf); } @@ -2674,7 +2674,8 @@ static void create_object_directory(struct repository *repo) strbuf_release(&path); } -static void separate_git_dir(const char *git_dir, const char *git_link) +static void separate_git_dir(struct repository *repo, + const char *git_dir, const char *git_link) { struct stat st; @@ -2690,7 +2691,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link) if (rename(src, git_dir)) die_errno(_("unable to move %s to %s"), src, git_dir); - repair_worktrees_after_gitdir_move(src); + repair_worktrees_after_gitdir_move(repo, src); } write_file(git_link, "gitdir: %s", git_dir); @@ -2849,7 +2850,7 @@ int init_db(struct repository *repo, apply_and_export_relative_gitdir(repo, real_git_dir, 1); git_dir = repo_get_git_dir(repo); - separate_git_dir(git_dir, original_git_dir); + separate_git_dir(repo, git_dir, original_git_dir); } else { apply_and_export_relative_gitdir(repo, git_dir, 1); git_dir = repo_get_git_dir(repo); diff --git a/src/hash.rs b/src/hash.rs index dea2998de4cadd..e1f2d31fc36239 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -181,7 +181,10 @@ impl CryptoDigest for CryptoHasher { impl Clone for CryptoHasher { fn clone(&self) -> Self { let ctx = unsafe { c::git_hash_alloc() }; - unsafe { c::git_hash_clone(ctx, self.ctx) }; + unsafe { + c::git_hash_init(ctx, self.algo.hash_algo_ptr()); + c::git_hash_clone(ctx, self.ctx) + }; Self { algo: self.algo, ctx, @@ -191,7 +194,10 @@ impl Clone for CryptoHasher { impl Drop for CryptoHasher { fn drop(&mut self) { - unsafe { c::git_hash_free(self.ctx) }; + unsafe { + c::git_hash_discard(self.ctx); + c::git_hash_free(self.ctx); + }; } } @@ -353,6 +359,7 @@ pub mod c { pub fn git_hash_clone(dst: *mut c_void, src: *const c_void); pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize); pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void); + pub fn git_hash_discard(ctx: *mut c_void); pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void); } } @@ -447,6 +454,7 @@ mod tests { h.update(&data[2..]); let h2 = h.clone(); + let h3 = h2.clone(); let actual_oid = h.into_oid(); assert_eq!(**oid, actual_oid); @@ -460,6 +468,7 @@ mod tests { let actual_oid = h.into_oid(); assert_eq!(**oid, actual_oid); + std::mem::drop(h3); } } } diff --git a/submodule.c b/submodule.c index b30bb9679ee50f..5c9257588856a9 100644 --- a/submodule.c +++ b/submodule.c @@ -2494,7 +2494,7 @@ static void relocate_single_git_dir_into_superproject(const char *path, if (validate_submodule_path(path) < 0) exit(128); - if (submodule_uses_worktrees(path)) + if (submodule_uses_worktrees(the_repository, path)) die(_("relocate_gitdir for submodule '%s' with " "more than one worktree not supported"), path); diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 3866d0aca49bc2..5a9a3053d9d81a 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -84,7 +84,7 @@ static const char **get_store(const char **argv, struct ref_store **refs) *refs = repo_get_submodule_ref_store(the_repository, gitdir); } else if (skip_prefix(argv[0], "worktree:", &gitdir)) { - struct worktree **p, **worktrees = get_worktrees(); + struct worktree **p, **worktrees = get_worktrees(the_repository); for (p = worktrees; *p; p++) { struct worktree *wt = *p; diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh new file mode 100644 index 00000000000000..7c2e8770164ce3 --- /dev/null +++ b/t/lib-cat-file.sh @@ -0,0 +1,16 @@ +# Library of git-cat-file related test functions. + +# Print a string without a trailing newline. +echo_without_newline () { + printf '%s' "$*" +} + +# Print a string without newlines and replace them with a NUL character (\0). +echo_without_newline_nul () { + echo_without_newline "$@" | tr '\n' '\0' +} + +# Calculate the length of a string. +strlen () { + echo_without_newline "$1" | wc -c | sed -e 's/^ *//' +} diff --git a/t/lib-log-graph.sh b/t/lib-log-graph.sh index bf952ef9204dbb..1eae8f60c21bae 100644 --- a/t/lib-log-graph.sh +++ b/t/lib-log-graph.sh @@ -26,3 +26,8 @@ lib_test_cmp_colored_graph () { test_decode_color output.colors && test_cmp expect.colors output.colors } + +lib_test_check_graph () { + cat >expect && + lib_test_cmp_graph --format=%s "$@" +} diff --git a/t/meson.build b/t/meson.build index 8ae6ab6c5fe1e2..a25f37d2f5ae7d 100644 --- a/t/meson.build +++ b/t/meson.build @@ -171,6 +171,7 @@ integration_tests = [ 't1014-read-tree-confusing.sh', 't1015-read-index-unmerged.sh', 't1016-compatObjectFormat.sh', + 't1017-cat-file-remote-object-info.sh', 't1020-subdirectory.sh', 't1022-read-tree-partial-clone.sh', 't1050-large.sh', @@ -582,6 +583,7 @@ integration_tests = [ 't4215-log-skewed-merges.sh', 't4216-log-bloom.sh', 't4217-log-limit.sh', + 't4218-log-graph-indentation.sh', 't4219-log-follow-merge.sh', 't4252-am-options.sh', 't4253-am-keep-cr-dos.sh', diff --git a/t/perf/p1500-graph-walks.sh b/t/perf/p1500-graph-walks.sh index 5b23ce5db93ad3..d167b4f7e1f2ad 100755 --- a/t/perf/p1500-graph-walks.sh +++ b/t/perf/p1500-graph-walks.sh @@ -32,7 +32,16 @@ test_expect_success 'setup' ' echo "X:$line" >>test-tool-tags || return 1 done && - commit=$(git commit-tree $(git rev-parse HEAD^{tree})) && + git rev-list --first-parent --max-count=8192 HEAD >contains-commits && + test_file_not_empty contains-commits && + git update-ref refs/contains-perf-base "$(tail -n 1 contains-commits)" && + awk "{ + printf \"update refs/contains-perf/%04d %s\\n\", NR, \$1 + }" contains-commits | + git update-ref --stdin && + git pack-refs --include "refs/contains-perf/*" && + + commit=$(git commit-tree HEAD^{tree}) && git update-ref refs/heads/disjoint-base $commit && git commit-graph write --reachable @@ -62,6 +71,23 @@ test_perf 'contains: git tag --merged' ' xargs git tag --merged=HEAD /dev/null +' + test_perf 'is-base check: test-tool reach (refs)' ' test-tool reach get_branch_base_for_tip >new && git stash -u && git stash pop' test_perf_on_all git add -A test_perf_on_all git add . diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 762c77c3510254..0c1dc0fb400d7d 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -3,7 +3,8 @@ test_description='git cat-file' . ./test-lib.sh -. "$TEST_DIRECTORY/lib-loose.sh" +. "$TEST_DIRECTORY"/lib-loose.sh +. "$TEST_DIRECTORY"/lib-cat-file.sh test_cmdmode_usage () { test_expect_code 129 "$@" 2>err && @@ -99,18 +100,6 @@ do ' done -echo_without_newline () { - printf '%s' "$*" -} - -echo_without_newline_nul () { - echo_without_newline "$@" | tr '\n' '\0' -} - -strlen () { - echo_without_newline "$1" | wc -c | sed -e 's/^ *//' -} - run_tests () { type=$1 object_name="$2" diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh new file mode 100755 index 00000000000000..116862f9d0b447 --- /dev/null +++ b/t/t1017-cat-file-remote-object-info.sh @@ -0,0 +1,747 @@ +#!/bin/sh + +test_description='git cat-file --batch-command with remote-object-info command' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-cat-file.sh + +hello_content="Hello World" +hello_size=$(strlen "$hello_content") +hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin) +hello_short_oid=$(git rev-parse --short "$hello_oid") + +unstored_content="Hello Git" +unstored_oid=$(echo_without_newline "$unstored_content" | git hash-object --stdin) + +# This is how we get 13: +# 13 = + + + , where +# file mode is 100644, which is 6 characters; +# file name is hello, which is 5 characters +# a space is 1 character and a null is 1 character +tree_size=$(($(test_oid rawsz) + 13)) + +commit_message="Initial commit" + +# This is how we get 137: +# 137 = + + + +# + + +# + + +# + +# +# An easier way to calculate is: 1. use `git cat-file commit | wc -c`, +# to get 177, 2. then deduct 40 hex characters to get 137 +commit_size=$(($(test_oid hexsz) + 137)) + +tag_header_without_oid="type blob +tag hellotag +tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +tag_header_without_timestamp="object $hello_oid +$tag_header_without_oid" +tag_description="This is a tag" +tag_content="$tag_header_without_timestamp 0 +0000 + +$tag_description" + +tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w) +tag_size=$(strlen "$tag_content") + +set_transport_variables () { + hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin) + tree_oid=$(git -C "$1" write-tree) + commit_oid=$(echo_without_newline "$commit_message" | git -C "$1" commit-tree $tree_oid) + tag_oid=$(echo_without_newline "$tag_content" | git -C "$1" hash-object -t tag --stdin -w) + tag_size=$(strlen "$tag_content") +} + +# This section tests --batch-command with remote-object-info command +# Since "%(objecttype)" is currently not supported by the command remote-object-info , +# the filters are set to "%(objectname) %(objectsize)" in some test cases. + +# Test --batch-command remote-object-info with 'git://' transport with +# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability +. "$TEST_DIRECTORY"/lib-git-daemon.sh +start_git_daemon --export-all +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent + +test_expect_success 'create repo to be served by git-daemon' ' + git init "$daemon_parent" && + echo_without_newline "$hello_content" > $daemon_parent/hello && + git -C "$daemon_parent" update-index --add hello && + git -C "$daemon_parent" config transfer.advertiseobjectinfo true && + git clone "$GIT_DAEMON_URL/parent" -n "$daemon_parent/daemon_client_empty" +' + +test_expect_success 'batch-command remote-object-info git://' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid + remote-object-info "$GIT_DAEMON_URL/parent" $tree_oid + remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid + remote-object-info "$GIT_DAEMON_URL/parent" $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid $commit_oid $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command remote-object-info git:// default filter' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + git cat-file --batch-command >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid + remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'remote-object-info does not change the default format of info' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + local_content="local object" && + local_oid=$(echo_without_newline "$local_content" | git hash-object -w --stdin) && + local_size=$(strlen "$local_content") && + + echo "$local_oid blob $local_size" >expect && + echo "$hello_oid $hello_size" >>expect && + echo "$local_oid blob $local_size" >>expect && + + git cat-file --batch-command >actual <<-EOF && + info $local_oid + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid + info $local_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command --buffer remote-object-info git://' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid + remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + flush + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command -Z remote-object-info git:// default filter' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + printf "%s\0" "$hello_oid $hello_size" >expect && + printf "%s\0" "$tree_oid $tree_size" >>expect && + printf "%s\0" "$commit_oid $commit_size" >>expect && + printf "%s\0" "$tag_oid $tag_size" >>expect && + + printf "%s\0" "$hello_oid missing" >>expect && + printf "%s\0" "$tree_oid missing" >>expect && + printf "%s\0" "$commit_oid missing" >>expect && + printf "%s\0" "$tag_oid missing" >>expect && + + batch_input="remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid +remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid +info $hello_oid +info $tree_oid +info $commit_oid +info $tag_oid +" && + echo_without_newline_nul "$batch_input" >commands_null_delimited && + + git cat-file --batch-command -Z < commands_null_delimited >actual && + test_cmp expect actual + ) +' + +test_expect_success 'remote-object-info does not support short oids' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + test_must_fail git cat-file --batch-command 2>err <<-EOF && + remote-object-info $GIT_DAEMON_URL/parent $hello_short_oid + EOF + test_grep "does not support short oids" err + ) +' + +test_expect_success 'remote-object-info does not die on missing oid like info' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + git cat-file --batch-command >local <<-EOF && + info $unstored_oid + EOF + git cat-file --batch-command >remote <<-EOF && + remote-object-info $GIT_DAEMON_URL/parent $unstored_oid + EOF + test_cmp local remote + ) +' + +# This tests depends on %(objecttype) not being supported yet, once supported +# it needs to be updated. +test_expect_success 'unsupported placeholder on remote returns empty string' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + echo "" >expect && + git cat-file --batch-command="%(objecttype)" >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'requesting only objectname echoes back' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + echo $hello_oid >expect && + git cat-file --batch-command="%(objectname)" >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'objectname goes through existence check' ' + ( + set_transport_variables "$daemon_parent" && + cd "$daemon_parent/daemon_client_empty" && + + echo "$unstored_oid missing" >expect && + + git cat-file --batch-command="%(objectname)" >actual <<-EOF && + remote-object-info "$GIT_DAEMON_URL/parent" $unstored_oid + EOF + + test_cmp expect actual + ) +' + +# Test --batch-command remote-object-info with 'git://' and +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability +test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' ' + ( + git -C "$daemon_parent" config transfer.advertiseobjectinfo false && + set_transport_variables "$daemon_parent" && + + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF && + remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid + EOF + test_grep "object-info capability is not enabled on the server" err && + + # revert server state back + git -C "$daemon_parent" config transfer.advertiseobjectinfo true + + ) +' + +stop_git_daemon + +# Test --batch-command remote-object-info with 'file://' transport with +# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability +# shellcheck disable=SC2016 +test_expect_success 'create repo to be served by file:// transport' ' + git init server && + git -C server config protocol.version 2 && + git -C server config transfer.advertiseobjectinfo true && + echo_without_newline "$hello_content" > server/hello && + git -C server update-index --add hello && + git clone -n "file://$(pwd)/server" file_client_empty +' + +test_expect_success 'batch-command remote-object-info file://' ' + ( + set_transport_variables "server" && + server_path="$(pwd)/server" && + cd file_client_empty && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF && + remote-object-info "file://${server_path}" $hello_oid + remote-object-info "file://${server_path}" $tree_oid + remote-object-info "file://${server_path}" $commit_oid + remote-object-info "file://${server_path}" $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' ' + ( + set_transport_variables "server" && + server_path="$(pwd)/server" && + cd file_client_empty && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF && + remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command --buffer remote-object-info file://' ' + ( + set_transport_variables "server" && + server_path="$(pwd)/server" && + cd file_client_empty && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF && + remote-object-info "file://${server_path}" $hello_oid $tree_oid + remote-object-info "file://${server_path}" $commit_oid $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + flush + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command remote-object-info file:// default filter' ' + ( + set_transport_variables "server" && + server_path="$(pwd)/server" && + cd file_client_empty && + + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + git cat-file --batch-command >actual <<-EOF && + remote-object-info "file://${server_path}" $hello_oid $tree_oid + remote-object-info "file://${server_path}" $commit_oid $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command -Z remote-object-info file:// default filter' ' + ( + set_transport_variables "server" && + server_path="$(pwd)/server" && + cd file_client_empty && + + printf "%s\0" "$hello_oid $hello_size" >expect && + printf "%s\0" "$tree_oid $tree_size" >>expect && + printf "%s\0" "$commit_oid $commit_size" >>expect && + printf "%s\0" "$tag_oid $tag_size" >>expect && + + printf "%s\0" "$hello_oid missing" >>expect && + printf "%s\0" "$tree_oid missing" >>expect && + printf "%s\0" "$commit_oid missing" >>expect && + printf "%s\0" "$tag_oid missing" >>expect && + + batch_input="remote-object-info \"file://${server_path}\" $hello_oid $tree_oid +remote-object-info \"file://${server_path}\" $commit_oid $tag_oid +info $hello_oid +info $tree_oid +info $commit_oid +info $tag_oid +" && + echo_without_newline_nul "$batch_input" >commands_null_delimited && + + git cat-file --batch-command -Z < commands_null_delimited >actual && + test_cmp expect actual + ) +' + +# Test --batch-command remote-object-info with 'file://' and +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability +test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' ' + ( + set_transport_variables "server" && + server_path="$(pwd)/server" && + git -C "${server_path}" config transfer.advertiseobjectinfo false && + + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF && + remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid + EOF + test_grep "object-info capability is not enabled on the server" err && + + # revert server state back + git -C "${server_path}" config transfer.advertiseobjectinfo true + ) +' + +# Test --batch-command remote-object-info with 'http://' transport with +# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'create repo to be served by http:// transport' ' + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true && + echo_without_newline "$hello_content" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" update-index --add hello && + git clone "$HTTPD_URL/smart/http_parent" -n "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" +' + +test_expect_success 'batch-command remote-object-info http://' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid + remote-object-info "$HTTPD_URL/smart/http_parent" $tree_oid + remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid + remote-object-info "$HTTPD_URL/smart/http_parent" $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command remote-object-info http:// one line' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command --buffer remote-object-info http://' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" && + + # These results prove remote-object-info can get object info from the remote + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + # These results prove remote-object-info did not download objects from the remote + echo "$hello_oid missing" >>expect && + echo "$tree_oid missing" >>expect && + echo "$commit_oid missing" >>expect && + echo "$tag_oid missing" >>expect && + + git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid + remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid + info $hello_oid + info $tree_oid + info $commit_oid + info $tag_oid + flush + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command remote-object-info http:// default filter' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" && + + echo "$hello_oid $hello_size" >expect && + echo "$tree_oid $tree_size" >>expect && + echo "$commit_oid $commit_size" >>expect && + echo "$tag_oid $tag_size" >>expect && + + git cat-file --batch-command >actual <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid + remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'batch-command -Z remote-object-info http:// default filter' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" && + + printf "%s\0" "$hello_oid $hello_size" >expect && + printf "%s\0" "$tree_oid $tree_size" >>expect && + printf "%s\0" "$commit_oid $commit_size" >>expect && + printf "%s\0" "$tag_oid $tag_size" >>expect && + + batch_input="remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid +remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid +" && + echo_without_newline_nul "$batch_input" >commands_null_delimited && + + git cat-file --batch-command -Z < commands_null_delimited >actual && + test_cmp expect actual + ) +' + +test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + + echo "$hello_oid " >expect && + + git cat-file --batch-command="%(objectname) %(objectsize:disk)" >actual <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + + echo "" >expect && + + git cat-file --batch-command="%(deltabase)" >actual <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid + EOF + test_cmp expect actual + ) +' + +test_expect_success 'remote-object-info fails on server with legacy protocol' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + + test_must_fail git -c protocol.version=0 cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid + EOF + test_grep "object-info requires protocol v2" err + ) +' + +test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + + test_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid + EOF + test_grep "object-info requires protocol v2" err + ) +' + +test_expect_success 'remote-object-info fails on malformed OID' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + malformed_object_id="this_id_is_not_valid" && + + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id + EOF + test_grep "not a valid object name '$malformed_object_id'" err + ) +' + +test_expect_success 'remote-object-info fails on malformed OID with default filter' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + malformed_object_id="this_id_is_not_valid" && + + test_must_fail git cat-file --batch-command 2>err <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id + EOF + test_grep "not a valid object name '$malformed_object_id'" err + ) +' + +test_expect_success 'remote-object-info fails on not providing OID' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" + EOF + test_grep "remote-object-info requires objects" err + ) +' + + +# Test --batch-command remote-object-info with 'http://' transport and +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability +test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' ' + ( + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo false && + + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF && + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid + EOF + test_grep "object-info capability is not enabled on the server" err && + + # revert server state back + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true + ) +' + +# DO NOT add non-httpd-specific tests here, because the last part of this +# test script is only executed when httpd is available and enabled. + +test_done diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 9814431cd74aff..4140c4d8ef2436 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -1598,6 +1598,61 @@ test_expect_success 'sparse-index is not expanded: stash' ' ensure_not_expanded stash pop ' +test_expect_success 'sparse-index is not expanded: stash in-cone pathspec' ' + init_repos && + + echo unrelated >>sparse-index/deep/e && + echo literal >>sparse-index/deep/a && + ensure_not_expanded stash push -- deep/a && + test_grep ! literal sparse-index/deep/a && + test_grep unrelated sparse-index/deep/e && + ensure_not_expanded stash pop && + test_grep literal sparse-index/deep/a && + + echo prefixed >>sparse-index/deep/a && + ensure_not_expanded -C deep stash push -- a && + test_grep ! prefixed sparse-index/deep/a && + test_grep unrelated sparse-index/deep/e && + ensure_not_expanded stash pop && + test_grep prefixed sparse-index/deep/a && + + echo wildcard >>sparse-index/deep/a && + ensure_not_expanded stash push -- "deep/a*" && + test_grep ! wildcard sparse-index/deep/a && + test_grep unrelated sparse-index/deep/e && + ensure_not_expanded stash pop && + test_grep wildcard sparse-index/deep/a && + + echo pathspec-file >>sparse-index/deep/a && + echo deep/a >pathspec-file && + ensure_not_expanded stash push --pathspec-from-file=../pathspec-file && + test_grep ! pathspec-file sparse-index/deep/a && + test_grep unrelated sparse-index/deep/e && + ensure_not_expanded stash pop && + test_grep pathspec-file sparse-index/deep/a && + + echo multiple-a >>sparse-index/deep/a && + echo multiple-e >>sparse-index/deep/e && + ensure_not_expanded stash push -- deep/a deep/e && + test_grep ! multiple-a sparse-index/deep/a && + test_grep ! multiple-e sparse-index/deep/e && + ensure_not_expanded stash pop && + test_grep multiple-a sparse-index/deep/a && + test_grep multiple-e sparse-index/deep/e && + + echo staged >>sparse-index/deep/a && + git -C sparse-index add deep/a && + ensure_not_expanded stash push --staged -- deep/a && + test_grep ! staged sparse-index/deep/a && + test_grep unrelated sparse-index/deep/e && + ensure_not_expanded stash pop --index && + test_grep staged sparse-index/deep/a && + test_must_fail git -C sparse-index diff --cached --quiet -- deep/a && + + ensure_not_expanded ! stash push -- deep/does-not-exist && + test_grep "did not match any file" sparse-index-error +' + test_expect_success 'describe tested on all' ' init_repos && @@ -2119,6 +2174,13 @@ test_expect_success 'sparse index is not expanded: rm' ' ensure_not_expanded rm -r deep ' +test_expect_success 'sparse index is not expanded: prefixed wildcard pathspec' ' + init_repos && + + ensure_not_expanded -C deep rm --dry-run -- "a*" && + ensure_not_expanded -C deep reset base -- "a*" +' + test_expect_success 'grep with and --cached' ' init_repos && diff --git a/t/t1300-config.sh b/t/t1300-config.sh index b99f782d5dee2d..e3f8064889210a 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -3002,4 +3002,21 @@ test_expect_success 'writing value with trailing CR not stripped on read' ' test_cmp expect actual ' +test_expect_success 'writing config fails immediately with core.configLockTimeout=0' ' + test_when_finished "rm -f .git/config.lock" && + >.git/config.lock && + test_must_fail git -c core.configLockTimeout=0 config foo.bar baz 2>err && + test_grep "could not lock config file" err +' + +test_expect_success 'writing config retries until lock is released' ' + test_when_finished "rm -f .git/config.lock" && + >.git/config.lock && + { + ( sleep 1 && rm -f .git/config.lock ) & + } && + git -c core.configLockTimeout=5000 config retried.key value && + test "$(git config retried.key)" = value +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 1ecbafbee18e03..7f7807eb88e928 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1075,7 +1075,8 @@ test_expect_success '--set-upstream-to fails on locked config' ' test_when_finished "rm -f .git/config.lock" && >.git/config.lock && git branch locked && - test_must_fail git branch --set-upstream-to locked 2>err && + test_must_fail git -c core.configLockTimeout=0 \ + branch --set-upstream-to locked 2>err && test_grep "could not lock config file .git/config" err ' @@ -1106,7 +1107,8 @@ test_expect_success '--unset-upstream should fail if config is locked' ' test_when_finished "rm -f .git/config.lock" && git branch --set-upstream-to locked && >.git/config.lock && - test_must_fail git branch --unset-upstream 2>err && + test_must_fail git -c core.configLockTimeout=0 \ + branch --unset-upstream 2>err && test_grep "could not lock config file .git/config" err ' diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index e62e07b894669e..accaaba4d452d2 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -270,12 +270,24 @@ test_expect_success 'rebase can copy notes' ' test "a note" = "$(git notes show HEAD)" ' -test_expect_success 'rebase -m can copy notes' ' +test_expect_success 'rebase --apply can copy notes' ' git reset --hard n3 && - git rebase -m --onto n1 n2 && + git rebase --apply --onto n1 n2 && test "a note" = "$(git notes show HEAD)" ' +test_expect_success 'rebase drops notes of dropped commits' ' + git checkout n1 && + echo n3 >n3.t && + echo n4 >n4.t && + git add n3.t n4.t && + git commit -m n34 && + git rebase HEAD n3 && + test_commit_message HEAD -m n2 && + test_must_fail git notes list HEAD >actual && + test_must_be_empty actual +' + test_expect_success 'rebase commit with an ancient timestamp' ' git reset --hard && diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index e64816770a3d6e..9bf5351e431b59 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1251,6 +1251,17 @@ test_expect_success 'interrupted rebase -i with --strategy and -X' ' test $(cat file1) = Z ' +test_expect_success 'failing pick with --strategy is rescheduled' ' + test_when_finished "rm -rf bin; test_might_fail git rebase --abort" && + mkdir bin && + echo exit 2 | write_script bin/git-merge-fail && + git log -1 --format="pick %H # %s" HEAD >expect && + test_must_fail env PATH="$PWD/bin:$PATH" \ + git rebase --no-ff --strategy fail HEAD^ && + test_cmp expect .git/rebase-merge/git-rebase-todo && + test_cmp expect .git/rebase-merge/done +' + test_expect_success 'rebase -i error on commits with \ in message' ' current_head=$(git rev-parse HEAD) && test_when_finished "git rebase --abort; git reset --hard $current_head; rm -f error" && diff --git a/t/t4018/swift-actor b/t/t4018/swift-actor new file mode 100644 index 00000000000000..e4852f40a7ebd8 --- /dev/null +++ b/t/t4018/swift-actor @@ -0,0 +1,5 @@ +actor RIGHT { + let a = 1 + // a comment + let b = ChangeMe +} diff --git a/t/t4018/swift-attribute-with-args b/t/t4018/swift-attribute-with-args new file mode 100644 index 00000000000000..22b1ee32f110bd --- /dev/null +++ b/t/t4018/swift-attribute-with-args @@ -0,0 +1,7 @@ +struct View { + @available(iOS 13, *) public func RIGHT() { + let a = 1 + // a comment + print(ChangeMe) + } +} diff --git a/t/t4018/swift-class b/t/t4018/swift-class new file mode 100644 index 00000000000000..c3a93360277f81 --- /dev/null +++ b/t/t4018/swift-class @@ -0,0 +1,5 @@ +class RIGHT { + let a = 1 + // a comment + let b = ChangeMe +} diff --git a/t/t4018/swift-enum b/t/t4018/swift-enum new file mode 100644 index 00000000000000..0a843029930409 --- /dev/null +++ b/t/t4018/swift-enum @@ -0,0 +1,5 @@ +enum RIGHT { + case first + // a comment + case ChangeMe +} diff --git a/t/t4018/swift-extension b/t/t4018/swift-extension new file mode 100644 index 00000000000000..cbc18ab6ef42c8 --- /dev/null +++ b/t/t4018/swift-extension @@ -0,0 +1,5 @@ +extension RIGHT { + static let a = 1 + // a comment + static let b = ChangeMe +} diff --git a/t/t4018/swift-failable-init b/t/t4018/swift-failable-init new file mode 100644 index 00000000000000..4bbd6217c9147a --- /dev/null +++ b/t/t4018/swift-failable-init @@ -0,0 +1,7 @@ +class Bar { + init?(RIGHT: Int) { + let x = 0 + // a comment + print(ChangeMe) + } +} diff --git a/t/t4018/swift-func b/t/t4018/swift-func new file mode 100644 index 00000000000000..1fecae0911d411 --- /dev/null +++ b/t/t4018/swift-func @@ -0,0 +1,5 @@ +func RIGHT(x: Int) -> Int { + let y = x + // a comment + return ChangeMe +} diff --git a/t/t4018/swift-generic-subscript b/t/t4018/swift-generic-subscript new file mode 100644 index 00000000000000..423cb589412ea5 --- /dev/null +++ b/t/t4018/swift-generic-subscript @@ -0,0 +1,7 @@ +struct Container { + subscript(index: Int) -> Int { + let a = 0 + // a comment + return ChangeMe + } +} diff --git a/t/t4018/swift-init b/t/t4018/swift-init new file mode 100644 index 00000000000000..dc7a298f3806b5 --- /dev/null +++ b/t/t4018/swift-init @@ -0,0 +1,7 @@ +class Foo { + init(RIGHT: Int) { + let x = 0 + // a comment + print(ChangeMe) + } +} diff --git a/t/t4018/swift-inline-attribute b/t/t4018/swift-inline-attribute new file mode 100644 index 00000000000000..2374c4b6036efa --- /dev/null +++ b/t/t4018/swift-inline-attribute @@ -0,0 +1,7 @@ +class Service { + @objc func RIGHT() { + let path = "/api" + // a comment + log(ChangeMe) + } +} diff --git a/t/t4018/swift-modifiers b/t/t4018/swift-modifiers new file mode 100644 index 00000000000000..9d80685a786bea --- /dev/null +++ b/t/t4018/swift-modifiers @@ -0,0 +1,4 @@ +public static func RIGHT() -> Int { + // a comment + return ChangeMe +} diff --git a/t/t4018/swift-protocol b/t/t4018/swift-protocol new file mode 100644 index 00000000000000..07c39ec2a3ca89 --- /dev/null +++ b/t/t4018/swift-protocol @@ -0,0 +1,5 @@ +protocol RIGHT { + var first: Int { get } + // a comment + var second: ChangeMe { get } +} diff --git a/t/t4018/swift-struct b/t/t4018/swift-struct new file mode 100644 index 00000000000000..e399ed77597656 --- /dev/null +++ b/t/t4018/swift-struct @@ -0,0 +1,5 @@ +struct RIGHT { + let a = 1 + // a comment + let b = ChangeMe +} diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 1612f05f1b39ce..eebab71039438b 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -5,11 +5,6 @@ test_description='git log --graph of skewed merges' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-log-graph.sh -check_graph () { - cat >expect && - lib_test_cmp_graph --format=%s "$@" -} - test_expect_success 'log --graph with merge fusing with its left and right neighbors' ' git checkout --orphan _p && test_commit A && @@ -21,7 +16,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh git checkout _p && git merge --no-ff _r -m G && git checkout @^^ && git merge --no-ff _p -m H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * H |\ | * G @@ -49,7 +44,7 @@ test_expect_success 'log --graph with left-skewed merge' ' git checkout 0_p && git merge --no-ff 0_s -m 0_G && git checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF *-----. 0_H |\ \ \ \ | | | | * 0_G @@ -83,7 +78,7 @@ test_expect_success 'log --graph with nested left-skewed merge' ' git checkout 1_p && git merge --no-ff 1_r -m 1_G && git checkout @^^ && git merge --no-ff 1_p -m 1_H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 1_H |\ | * 1_G @@ -115,7 +110,7 @@ test_expect_success 'log --graph with nested left-skewed merge following normal git checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J && git checkout 2_p && git merge --no-ff 2_s -m 2_K && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 2_K |\ | * 2_J @@ -151,7 +146,7 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s git checkout 3_p && git merge --no-ff 3_r -m 3_H && git checkout @^^ && git merge --no-ff 3_p -m 3_J && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 3_J |\ | * 3_H @@ -182,7 +177,7 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed git merge --no-ff 4_p -m 4_G && git checkout @^^ && git merge --no-ff 4_s -m 4_H && - check_graph --date-order <<-\EOF + lib_test_check_graph --date-order <<-\EOF * 4_H |\ | * 4_G @@ -218,7 +213,7 @@ test_expect_success 'log --graph with octopus merge with column joining its penu git checkout 5_r && git merge --no-ff 5_s -m 5_H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 5_H |\ | *-. 5_G @@ -257,7 +252,7 @@ test_expect_success 'log --graph with multiple tips' ' git checkout 6_1 && git merge --no-ff 6_2 -m 6_I && - check_graph 6_1 6_3 6_5 <<-\EOF + lib_test_check_graph 6_1 6_3 6_5 <<-\EOF * 6_I |\ | | * 6_H @@ -334,7 +329,7 @@ test_expect_success 'log --graph with multiple tips' ' git checkout -b M_7 7_1 && git merge --no-ff 7_2 7_3 -m 7_M4 && - check_graph M_1 M_3 M_5 M_7 <<-\EOF + lib_test_check_graph M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 @@ -371,7 +366,7 @@ test_expect_success 'log --graph with multiple tips' ' ' test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' - check_graph --graph-lane-limit=2 M_7 <<-\EOF + lib_test_check_graph --graph-lane-limit=2 M_7 <<-\EOF *-. 7_M4 |\ \ | | * 7_G @@ -388,7 +383,7 @@ test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' ' test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' ' - check_graph --graph-lane-limit=1 M_7 <<-\EOF + lib_test_check_graph --graph-lane-limit=1 M_7 <<-\EOF *-~ 7_M4 |\~ | ~ 7_G @@ -405,7 +400,7 @@ test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge ' test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' - check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF + lib_test_check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 @@ -441,7 +436,7 @@ test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' ' test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' ' - check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF + lib_test_check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 @@ -478,7 +473,7 @@ test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows fir ' test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' ' - check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF + lib_test_check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 diff --git a/t/t4218-log-graph-indentation.sh b/t/t4218-log-graph-indentation.sh new file mode 100755 index 00000000000000..24dc9b497d9dbc --- /dev/null +++ b/t/t4218-log-graph-indentation.sh @@ -0,0 +1,596 @@ +#!/bin/sh + +test_description='git log --graph visual root indentations' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-log-graph.sh + +check_graph_with_description () { + cat >expect && + lib_test_cmp_graph --format="%s%ndescription%nsecond-line" "$@" +} + +create_orphan () { + git checkout --orphan "$1" && + test_might_fail git rm -rf . +} + +# disable commit-graph topo order to have the graph to render in different +# ways (used in --first-parent tests to have multiple visual roots while a +# column is active at the same time). +unset_commit_graph () { + sane_unset GIT_TEST_COMMIT_GRAPH && + rm -f .git/objects/info/commit-graph && + rm -rf .git/objects/info/commit-graphs +} + +test_expect_success 'single root commit is not indented' ' + create_orphan _1 && test_commit 1_A && + lib_test_check_graph _1 <<-\EOF + * 1_A + EOF +' + +test_expect_success 'visual root indented before unrelated branch' ' + create_orphan _2 && test_commit 2_A && test_commit 2_B && + create_orphan _3 && test_commit 3_A && + lib_test_check_graph _2 _3 <<-\EOF + * 3_A + * 2_B + * 2_A + EOF +' + +test_expect_success 'visual root indentation with --left-right' ' + lib_test_check_graph --left-right _2..._3 <<-\EOF + > 3_A + < 2_B + < 2_A + EOF +' + +# A better case of why indentation is still needed with '--left-right' flag is +# that unrelated branches can be on the same side, so it's needed to +# differentiate visual roots on the same side. +test_expect_success 'visual root indentation with --left-right having unrelated commits on the same side' ' + lib_test_check_graph --left-right _2..._3 _1 <<-\EOF + > 3_A + < 2_B + \ + < 2_A + > 1_A + EOF +' + +test_expect_success 'visual root indents the description also' ' + check_graph_with_description _2 _3 <<-\EOF + * 3_A + description + second-line + * 2_B + | description + | second-line + * 2_A + description + second-line + EOF +' + +test_expect_success 'indented visual root parent gets connected to its child' ' + create_orphan _4 && test_commit 4_A && test_commit 4_B && + create_orphan _5 && test_commit 5_A && test_commit 5_B && + lib_test_check_graph _4 _5 <<-\EOF + * 5_B + \ + * 5_A + * 4_B + * 4_A + EOF +' + +test_expect_success 'indented visual root parent gets connected to its child with description' ' + check_graph_with_description _4 _5 <<-\EOF + * 5_B + | description + | second-line + \ + * 5_A + description + second-line + * 4_B + | description + | second-line + * 4_A + description + second-line + EOF +' + +test_expect_success 'visual roots cascade and last root does not' ' + create_orphan _7 && test_commit 7_A && test_commit 7_B && + create_orphan _8 && test_commit 8_A && + create_orphan _9 && test_commit 9_A && + create_orphan _10 && test_commit 10_A && + lib_test_check_graph _7 _8 _9 _10 <<-\EOF + * 10_A + * 9_A + * 8_A + * 7_B + * 7_A + EOF +' + +test_expect_success 'last root does not cascade' ' + lib_test_check_graph _8 _9 _10 <<-\EOF + * 10_A + * 9_A + * 8_A + EOF +' + +test_expect_success 'merge parents are roots between them but they do not indent' ' + create_orphan _11 && test_commit 11_A && + create_orphan _12 && test_commit 12_A && + create_orphan _13 && test_commit 13_A && + git checkout _11 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _11 -p _12 -p _13 -m 11_octopus) && + git reset --hard $MERGE && + lib_test_check_graph _11 <<-\EOF + *-. 11_octopus + |\ \ + | | * 13_A + | * 12_A + * 11_A + EOF +' + +# The last parent of a merge can be indented if nothing related to it needs to +# be rendered after, if it's another visual root, merge parent must not get +# indented but rather activate cascading. +test_expect_success 'merge then unrelated visual root and unrelated branch' ' + create_orphan _16 && test_commit 16_A && test_commit 16_B && + create_orphan _17 && test_commit 17_A && + create_orphan _18 && test_commit 18_A && + create_orphan _19 && test_commit 19_A && + create_orphan _20 && test_commit 20_A && + git checkout _18 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _18 -p _19 -p _20 -m 18_octopus) && + git reset --hard $MERGE && + lib_test_check_graph _18 _17 _16 <<-\EOF + *-. 18_octopus + |\ \ + | | * 20_A + | * 19_A + * 18_A + * 17_A + * 16_B + * 16_A + EOF +' + +# The last commit root does not get indented, if the next thing after the root +# merge parent is the last commit, indent the merge parent. +test_expect_success 'merge then unrelated root indents merge parent' ' + lib_test_check_graph _18 _17 <<-\EOF + *-. 18_octopus + |\ \ + | | * 20_A + | * 19_A + \ + * 18_A + * 17_A + EOF +' + +test_expect_success 'merge then unrelated branch indents merge parent' ' + lib_test_check_graph _18 _16 <<-\EOF + *-. 18_octopus + |\ \ + | | * 20_A + | * 19_A + \ + * 18_A + * 16_B + * 16_A + EOF +' + +test_expect_success 'two-parent merge of orphans' ' + create_orphan _21 && test_commit 21_A && + create_orphan _22 && test_commit 22_A && + git checkout _21 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _21 -p _22 -m 21_merge) && + git reset --hard $MERGE && + lib_test_check_graph _21 <<-\EOF + * 21_merge + |\ + | * 22_A + * 21_A + EOF +' + +test_expect_success 'commit with filtered parent becomes a visual root' ' + create_orphan _23 && + echo test >other.txt && + git add other.txt && + git commit -m "23_A" && + echo test >foo.txt && + git add foo.txt && + git commit -m "23_B" && + create_orphan _24 && + echo test >foo.txt && + git add foo.txt && + git commit -m "24_A" && + lib_test_check_graph _23 _24 -- foo.txt <<-\EOF + * 23_B + * 24_A + EOF +' + +test_expect_success 'filtered parent cascading edge case' ' + create_orphan _27 && + echo test >foo.txt && + git add foo.txt && + test_tick && + git commit -m "D (last)" && + + create_orphan _25 && + echo test >other.txt && + git add other.txt && + test_tick && + git commit -m "C-filtered" && + + echo test >foo.txt && + git add foo.txt && + test_tick && + git commit -m "B (child of filtered)" && + + create_orphan _26 && + echo test >foo.txt && + git add foo.txt && + test_tick && + git commit -m "A (visual root)" && + + lib_test_check_graph _25 _26 _27 -- foo.txt <<-\EOF + * A (visual root) + * B (child of filtered) + * D (last) + EOF +' + +test_expect_success 'multiple filtered parents in sequence' ' + create_orphan _44 && + echo a >other.txt && git add other.txt && git commit -m "44_F" && + echo b >foo.txt && git add foo.txt && git commit -m "44_C" && + + create_orphan _45 && + echo c >other.txt && git add other.txt && git commit -m "45_F" && + echo d >foo.txt && git add foo.txt && git commit -m "45_C" && + + create_orphan _46 && + echo e >foo.txt && git add foo.txt && git commit -m "46_A" && + + lib_test_check_graph _44 _45 _46 -- foo.txt <<-\EOF + * 44_C + * 45_C + * 46_A + EOF +' + +# These tests prove why there is no need to have indentation for boundary +# commits. +# +# Boundary commits rather than starting a column they 'inherit' the one of +# its child so there will always be an edge that connects it removing the +# ambiguity. +test_expect_success 'unrelated boundaries are not ambiguous' ' + create_orphan _28 && test_commit 28_A && test_commit 28_B && + test_commit 28_C && + create_orphan _29 && test_commit 29_A && test_commit 29_B && + lib_test_check_graph --boundary 28_A.._28 29_A.._29 <<-\EOF + * 29_B + | * 28_C + | * 28_B + | o 28_A + o 29_A + EOF +' + +# Same structure as t6016 +test_expect_success 'boundary commits big test' ' + # 3 commits on branch _30 + create_orphan _30 && + test_commit 30_A && + test_commit 30_B && + test_commit 30_C && + + # 2 commits on branch _31, started from 30_A + git checkout -b _31 30_A && + test_commit 31_A && + test_commit 31_B && + + # 2 commits on branch _32, started from 30_B + git checkout -b _32 30_B && + test_commit 32_A && + test_commit 32_B && + + # Octopus merge _31 and _32 into -30 + git checkout _30 && + git merge _31 _32 -m 30_D && + git tag 30_D && + test_commit 30_E && + + # More commits on _32, then merge _32 into _30 + git checkout _32 && + test_commit 32_C && + test_commit 32_D && + git checkout _30 && + git merge -s ours _32 -m 30_F && + git tag 30_F && + test_commit 30_G && + lib_test_check_graph --boundary _30 _31 _32 ^32_C <<-\EOF + * 30_G + * 30_F + |\ + | * 32_D + * | 30_E + | | + | \ + *-. \ 30_D + |\ \ \ + | * | | 31_B + | * | | 31_A + * | | | 30_C + o | | | 30_B + |/ / / + o / / 30_A + / / + | o 32_C + |/ + o 32_B + EOF +' + +# Filter by --first-parent and then forcing the filtered parents to be shown. +test_expect_success '--first-parent flag with the filtered parents' ' + ( + unset_commit_graph && + create_orphan _35 && test_commit 35_A && test_commit 35_B && + create_orphan _36 && test_commit 36_A && + create_orphan _37 && test_commit 37_A && + git checkout _35 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _35 -p _36 -p _37 -m 35_octopus) && + git reset --hard $MERGE && + lib_test_check_graph --first-parent _35 _36 _37 <<-\EOF + * 35_octopus + | * 37_A + | * 36_A + * 35_B + * 35_A + EOF + ) +' + +test_expect_success '--first-parent with filtered parents but one has a child' ' + ( + unset_commit_graph && + create_orphan _38 && test_commit 38_A && test_commit 38_B && + create_orphan _39 && test_commit 39_A && + create_orphan _40 && test_commit 40_A && test_commit 40_B && + git checkout _38 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _38 -p _39 -p _40 -m 38_octopus) && + git reset --hard $MERGE && + lib_test_check_graph --first-parent _38 _39 _40 <<-\EOF + * 38_octopus + | * 40_B + | * 40_A + | * 39_A + * 38_B + * 38_A + EOF + ) +' + +test_expect_success '--first-parent with filtered parents but both have children' ' + ( + unset_commit_graph && + create_orphan _41 && test_commit 41_A && test_commit 41_B && + create_orphan _42 && test_commit 42_A && test_commit 42_B && + create_orphan _43 && test_commit 43_A && test_commit 43_B && + git checkout _41 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _41 -p _42 -p _43 -m 41_octopus) && + git reset --hard $MERGE && + lib_test_check_graph --first-parent _41 _42 _43 <<-\EOF + * 41_octopus + | * 43_B + | \ + | * 43_A + | * 42_B + | * 42_A + * 41_B + * 41_A + EOF + ) +' + +test_expect_success 'two unrelated merges' ' + create_orphan _50 && test_commit 50_A && + git checkout -b _51 && + test_commit 51_A && test_commit 51_B && + git checkout _50 && + git merge --no-ff _51 -m 50_B && + + create_orphan _52 && test_commit 52_A && + git checkout -b _53 && + test_commit 53_A && test_commit 53_B && + git checkout _52 && + git merge --no-ff _53 -m 52_B && + + lib_test_check_graph _52 _50 <<-\EOF + * 52_B + |\ + | * 53_B + | * 53_A + |/ + \ + * 52_A + * 50_B + |\ + | * 51_B + | * 51_A + |/ + * 50_A + EOF +' + +test_expect_success '--max-count treats the last visible commit as the last commit' ' + lib_test_check_graph --max-count=2 _8 _9 _10 <<-\EOF + * 10_A + * 9_A + EOF +' + +test_expect_success '--max-count=1 shows a single root without indentation' ' + lib_test_check_graph --max-count=1 _8 _9 _10 <<-\EOF + * 10_A + EOF +' + +test_expect_success '--max-count-oldest indents visual roots' ' + lib_test_check_graph --max-count-oldest=3 _8 _9 _10 <<-\EOF + * 10_A + * 9_A + * 8_A + EOF +' + +# when the graph commits are filtered with regex options like --author, the +# commit parents do not come NULL so it is needed to check if the parents are +# interesting. +test_expect_success '--author skipped parent makes a visual root' ' + create_orphan _55 && + test_tick && + git commit --allow-empty -m 55_A && + create_orphan _54 && + test_tick && + git commit --allow-empty --author="Other " -m 54_A && + test_tick && + git commit --allow-empty -m 54_B && + test_tick && + git commit --allow-empty -m 54_C && + lib_test_check_graph --author="A U Thor" _54 _55 <<-\EOF + * 54_C + \ + * 54_B + * 55_A + EOF +' + +test_expect_success '--grep skipped parent makes a visual root' ' + create_orphan _57 && + test_tick && + git commit --allow-empty -m 57_keep_A && + create_orphan _56 && + test_tick && + git commit --allow-empty -m 56_skip && + test_tick && + git commit --allow-empty -m 56_keep_A && + test_tick && + git commit --allow-empty -m 56_keep_B && + lib_test_check_graph --grep=keep _56 _57 <<-\EOF + * 56_keep_B + \ + * 56_keep_A + * 57_keep_A + EOF +' + +# The cascading wraps after 4 columns and when wraping (column % 4 == 0) if the +# next is a non visual-root, force indentation to avoid an ambiguous graph +# (commit 59_A is forcefully indented) +test_expect_success 'visual root cascading gets wrapped after 4 columns' ' + create_orphan _58 && test_commit 58_A && test_commit 58_B && + create_orphan _59 && test_commit 59_A && + create_orphan _60 && test_commit 60_A && + create_orphan _61 && test_commit 61_A && + create_orphan _62 && test_commit 62_A && + create_orphan _63 && test_commit 63_A && + create_orphan _64 && test_commit 64_A && + create_orphan _65 && test_commit 65_A && + create_orphan _66 && test_commit 66_A && + create_orphan _67 && test_commit 67_A && + lib_test_check_graph _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF + * 67_A + * 66_A + * 65_A + * 64_A + * 63_A + * 62_A + * 61_A + * 60_A + * 59_A + * 58_B + * 58_A + EOF +' + +test_expect_success '--no-graph-indent disables indentation' ' + lib_test_check_graph --no-graph-indent _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF + * 67_A + * 66_A + * 65_A + * 64_A + * 63_A + * 62_A + * 61_A + * 60_A + * 59_A + * 58_B + * 58_A + EOF +' + +test_expect_success 'log.graphIndent config disables indentation' ' + test_config log.graphIndent false && + lib_test_check_graph _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF + * 67_A + * 66_A + * 65_A + * 64_A + * 63_A + * 62_A + * 61_A + * 60_A + * 59_A + * 58_B + * 58_A + EOF +' + +test_expect_success '--graph-indent forces indentation when graph.indent is unset' ' + test_config log.graphIndent false && + lib_test_check_graph --graph-indent _58 _59 _60 _61 _62 _63 _64 _65 _66 _67 <<-\EOF + * 67_A + * 66_A + * 65_A + * 64_A + * 63_A + * 62_A + * 61_A + * 60_A + * 59_A + * 58_B + * 58_A + EOF +' + +# log.graphIndent unset and no --option (which activates graph indentation) is +# the default state. + +test_done diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index ed9896e27a4e78..ca8a10fbb1f9ee 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -310,4 +310,27 @@ test_expect_success 'git rebase -i (exec)' ' verify_hook_input ' +test_expect_success 'rebase with commits that become empty' ' + cat >todo <<-\EOF && + pick H + pick E + fixup I + fixup H + pick G + pick I + EOF + ( + set_replace_editor todo && + git rebase -i --empty=drop A A + ) && + echo rebase >expected.args && + cat >expected.data <<-EOF && + $(git rev-parse H) $(git rev-parse HEAD~2) + $(git rev-parse E) $(git rev-parse HEAD~1) + $(git rev-parse I) $(git rev-parse HEAD~1) + $(git rev-parse G) $(git rev-parse HEAD) + EOF + verify_hook_input +' + test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 6f5e86dedeb209..81cc251d3bc3a1 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1327,7 +1327,8 @@ test_expect_success 'remote set-url with locked config' ' test_when_finished "rm -f .git/config.lock" && git config --get-all remote.someremote.url >expect && >.git/config.lock && - test_must_fail git remote set-url someremote baz && + test_must_fail git -c core.configLockTimeout=0 \ + remote set-url someremote baz && git config --get-all remote.someremote.url >actual && cmp expect actual ' diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 108eb30945247b..9a575aa098afd3 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -7,6 +7,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +unknown_oid=$(printf "test" | git hash-object --stdin) + test_expect_success 'setup to generate files with expected content' ' printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability && @@ -364,6 +366,67 @@ test_expect_success 'basics of object-info' ' test_cmp expect actual ' +test_expect_success 'bare OID request' ' + test_config transfer.advertiseObjectInfo true && + + test-tool pkt-line pack >in <<-EOF && + command=object-info + object-format=$(test_oid algo) + 0001 + oid $(git rev-parse two:two.t) + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse two:two.t) + 0000 + EOF + + test-tool serve-v2 --stateless-rpc out && + test-tool pkt-line unpack actual && + test_cmp expect actual +' + +test_expect_success 'object-info with bare unrecognized OID' ' + test_config transfer.advertiseObjectInfo true && + + test-tool pkt-line pack >in <<-EOF && + command=object-info + object-format=$(test_oid algo) + 0001 + oid $unknown_oid + 0000 + EOF + + printf "%s \n" "$unknown_oid" >expect && + printf "0000\n" >>expect && + + test-tool serve-v2 --stateless-rpc out && + test-tool pkt-line unpack actual && + test_cmp expect actual +' + +test_expect_success 'object-info with size for unrecognized OID' ' + test_config transfer.advertiseObjectInfo true && + + test-tool pkt-line pack >in <<-EOF && + command=object-info + object-format=$(test_oid algo) + 0001 + size + oid $unknown_oid + 0000 + EOF + + printf "size\n" >expect && + printf "%s \n" "$unknown_oid" >>expect && + printf "0000\n" >>expect && + + test-tool serve-v2 --stateless-rpc out && + test-tool pkt-line unpack actual && + test_cmp expect actual +' + test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' ' test_config uploadpack.advertiseBundleURIs true && diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh index 54b0a6f5f8a4b2..e0d9c3c1acc88c 100755 --- a/t/t6016-rev-list-graph-simplify-history.sh +++ b/t/t6016-rev-list-graph-simplify-history.sh @@ -13,11 +13,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-log-graph.sh -check_graph () { - cat >expect && - lib_test_cmp_graph --format=%s "$@" -} - test_expect_success 'set up rev-list --graph test' ' # 3 commits on branch A test_commit A1 foo.txt && @@ -54,7 +49,7 @@ test_expect_success 'set up rev-list --graph test' ' ' test_expect_success '--graph --all' ' - check_graph --all <<-\EOF + lib_test_check_graph --all <<-\EOF * A7 * A6 |\ @@ -82,7 +77,7 @@ test_expect_success '--graph --all' ' # that undecorated merges are interesting, even with --simplify-by-decoration test_expect_success '--graph --simplify-by-decoration' ' git tag -d A4 && - check_graph --all --simplify-by-decoration <<-\EOF + lib_test_check_graph --all --simplify-by-decoration <<-\EOF * A7 * A6 |\ @@ -114,7 +109,7 @@ test_expect_success 'setup: get rid of decorations on B' ' # Graph with branch B simplified away test_expect_success '--graph --simplify-by-decoration prune branch B' ' - check_graph --simplify-by-decoration --all <<-\EOF + lib_test_check_graph --simplify-by-decoration --all <<-\EOF * A7 * A6 |\ @@ -133,7 +128,7 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' ' ' test_expect_success '--graph --full-history -- bar.txt' ' - check_graph --full-history --all -- bar.txt <<-\EOF + lib_test_check_graph --full-history --all -- bar.txt <<-\EOF * A7 * A6 |\ @@ -148,7 +143,7 @@ test_expect_success '--graph --full-history -- bar.txt' ' ' test_expect_success '--graph --full-history --simplify-merges -- bar.txt' ' - check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF + lib_test_check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF * A7 * A6 |\ @@ -161,7 +156,7 @@ test_expect_success '--graph --full-history --simplify-merges -- bar.txt' ' ' test_expect_success '--graph -- bar.txt' ' - check_graph --all -- bar.txt <<-\EOF + lib_test_check_graph --all -- bar.txt <<-\EOF * A7 * A5 * A3 @@ -172,7 +167,7 @@ test_expect_success '--graph -- bar.txt' ' ' test_expect_success '--graph --sparse -- bar.txt' ' - check_graph --sparse --all -- bar.txt <<-\EOF + lib_test_check_graph --sparse --all -- bar.txt <<-\EOF * A7 * A6 * A5 @@ -189,7 +184,7 @@ test_expect_success '--graph --sparse -- bar.txt' ' ' test_expect_success '--graph ^C4' ' - check_graph --all ^C4 <<-\EOF + lib_test_check_graph --all ^C4 <<-\EOF * A7 * A6 * A5 @@ -202,7 +197,7 @@ test_expect_success '--graph ^C4' ' ' test_expect_success '--graph ^C3' ' - check_graph --all ^C3 <<-\EOF + lib_test_check_graph --all ^C3 <<-\EOF * A7 * A6 |\ @@ -220,7 +215,7 @@ test_expect_success '--graph ^C3' ' # that important, but this test depends on it. If the ordering ever changes # in the code, we'll need to update this test. test_expect_success '--graph --boundary ^C3' ' - check_graph --boundary --all ^C3 <<-\EOF + lib_test_check_graph --boundary --all ^C3 <<-\EOF * A7 * A6 |\ diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh index e06feb06e91956..72b27c8be37d44 100755 --- a/t/t6301-for-each-ref-errors.sh +++ b/t/t6301-for-each-ref-errors.sh @@ -52,6 +52,28 @@ test_expect_success 'Missing objects are reported correctly' ' test_must_be_empty brief-err ' +test_expect_success 'missing ancestors are reported by contains filters' ' + test_when_finished "git update-ref -d refs/heads/missing-parent" && + { + echo "tree $(git rev-parse HEAD^{tree})" && + echo "parent $MISSING" && + git cat-file commit HEAD | + sed -n -e "/^author /p" -e "/^committer /p" && + echo && + echo "missing parent" + } >commit && + broken=$(git hash-object -t commit -w commit) && + git update-ref refs/heads/missing-parent "$broken" && + for option in --contains --no-contains + do + test_must_fail git for-each-ref "$option=HEAD" \ + refs/heads/missing-parent >out 2>err && + test_must_be_empty out && + test_grep "parse commit $MISSING" err || + return 1 + done +' + test_expect_success 'ahead-behind requires an argument' ' test_must_fail git for-each-ref \ --format="%(ahead-behind)" 2>err && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 2ad30040df065d..8c795d72188cfe 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1611,6 +1611,24 @@ test_expect_success 'checking that first commit is in all tags (hash)' ' test_cmp expected actual ' +test_expect_success 'tag --contains rejects cyclic replacement histories' ' + first=$(git rev-parse HEAD~2) && + second=$(git rev-parse HEAD~) && + third=$(git rev-parse HEAD) && + test_when_finished " + git replace -d $first && + git replace -d $third && + git tag -d cycle-a cycle-b + " && + git tag cycle-a "$first" && + git tag cycle-b "$third" && + git replace --graft "$first" "$third" "$second" && + git replace --graft "$third" "$first" && + test_must_fail git tag --contains="$second" --list "cycle-*" \ + >/dev/null 2>err && + test_grep "fatal: commit ancestry contains a cycle" err +' + # other ways of specifying the commit test_expect_success 'checking that first commit is in all tags (tag)' ' cat >expected <<-\EOF && diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index 9587ce212ac798..00cf077e52f2a0 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -115,6 +115,19 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' ' test_debug 'cat gitweb.headers' +test_expect_success 'commitdiff: index line shortens hashes with mode' ' + old_blob=$(git rev-parse HEAD:foo) && + old_short=$(git rev-parse --short=7 HEAD:foo) && + echo changed >foo && + git commit -am "change foo" && + new_blob=$(git rev-parse HEAD:foo) && + new_short=$(git rev-parse --short=7 HEAD:foo) && + gitweb_run "p=.git;a=commitdiff;h=HEAD" && + test_grep ">${old_short}\\.\\.]*>${new_short} 100644 (file)" \ + gitweb.body && + test_grep ! "index ${old_blob}\\.\\.${new_blob} 100644" gitweb.body +' + # ---------------------------------------------------------------------- # forks of projects diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c index 6844bfc37ccfdc..ddf2db5c811fb8 100644 --- a/t/unit-tests/u-odb-inmemory.c +++ b/t/unit-tests/u-odb-inmemory.c @@ -1,5 +1,6 @@ #include "unit-test.h" #include "hex.h" +#include "object-file.h" #include "odb/source-inmemory.h" #include "odb/streaming.h" #include "oidset.h" @@ -36,6 +37,17 @@ static void cl_assert_object_info(struct odb_source_inmemory *source, free(actual_content); } +static void cl_assert_write_object(struct odb_source_inmemory *source, + const char *content, + enum object_type type, + struct object_id *oid) +{ + size_t content_len = strlen(content); + hash_object_file(repo.hash_algo, content, content_len, type, oid); + cl_must_pass(odb_source_write_object(&source->base, content, content_len, + type, oid, NULL, NULL, 0)); +} + void test_odb_inmemory__initialize(void) { odb = odb_new(&repo, "", ""); @@ -78,8 +90,7 @@ void test_odb_inmemory__read_written_object(void) const char data[] = "foobar"; struct object_id written_oid; - cl_must_pass(odb_source_write_object(&source->base, data, strlen(data), - OBJ_BLOB, &written_oid, NULL, 0)); + cl_assert_write_object(source, data, OBJ_BLOB, &written_oid); cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID); cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar"); @@ -94,8 +105,7 @@ void test_odb_inmemory__read_stream_object(void) const char data[] = "foobar"; char buf[3] = { 0 }; - cl_must_pass(odb_source_write_object(&source->base, data, strlen(data), - OBJ_BLOB, &written_oid, NULL, 0)); + cl_assert_write_object(source, data, OBJ_BLOB, &written_oid); cl_must_pass(odb_source_read_object_stream(&stream, &source->base, &written_oid)); @@ -141,8 +151,7 @@ void test_odb_inmemory__for_each_object(void) strbuf_reset(&buf); strbuf_addf(&buf, "%d", i); - cl_must_pass(odb_source_write_object(&source->base, buf.buf, buf.len, - OBJ_BLOB, &written_oid, NULL, 0)); + cl_assert_write_object(source, buf.buf, OBJ_BLOB, &written_oid); cl_must_pass(oidset_insert(&expected_oids, &written_oid)); } @@ -174,12 +183,9 @@ void test_odb_inmemory__for_each_object_can_abort_iteration(void) struct object_id written_oid; unsigned counter = 0; - cl_must_pass(odb_source_write_object(&source->base, "1", 1, - OBJ_BLOB, &written_oid, NULL, 0)); - cl_must_pass(odb_source_write_object(&source->base, "2", 1, - OBJ_BLOB, &written_oid, NULL, 0)); - cl_must_pass(odb_source_write_object(&source->base, "3", 1, - OBJ_BLOB, &written_oid, NULL, 0)); + cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid); + cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid); + cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid); cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL, abort_after_two_objects, @@ -199,12 +205,9 @@ void test_odb_inmemory__count_objects(void) cl_must_pass(odb_source_count_objects(&source->base, 0, &count)); cl_assert_equal_u(count, 0); - cl_must_pass(odb_source_write_object(&source->base, "1", 1, - OBJ_BLOB, &written_oid, NULL, 0)); - cl_must_pass(odb_source_write_object(&source->base, "2", 1, - OBJ_BLOB, &written_oid, NULL, 0)); - cl_must_pass(odb_source_write_object(&source->base, "3", 1, - OBJ_BLOB, &written_oid, NULL, 0)); + cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid); + cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid); + cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid); cl_must_pass(odb_source_count_objects(&source->base, 0, &count)); cl_assert_equal_u(count, 3); @@ -228,8 +231,7 @@ void test_odb_inmemory__find_abbrev_len(void) * * With only one blob written we expect a length of 4. */ - cl_must_pass(odb_source_write_object(&source->base, "368317", strlen("368317"), - OBJ_BLOB, &oid1, NULL, 0)); + cl_assert_write_object(source, "368317", OBJ_BLOB, &oid1); cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4, &abbrev_len)); cl_assert_equal_u(abbrev_len, 4); @@ -238,8 +240,7 @@ void test_odb_inmemory__find_abbrev_len(void) * With both objects present, the shared 10-character prefix means we * need at least 11 characters to uniquely identify either object. */ - cl_must_pass(odb_source_write_object(&source->base, "514796", strlen("514796"), - OBJ_BLOB, &oid2, NULL, 0)); + cl_assert_write_object(source, "514796", OBJ_BLOB, &oid2); cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4, &abbrev_len)); cl_assert_equal_u(abbrev_len, 11); @@ -255,13 +256,11 @@ void test_odb_inmemory__freshen_object(void) const char *end; cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo)); - cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0); + cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid, NULL), 0); - cl_must_pass(odb_source_write_object(&source->base, "foobar", - strlen("foobar"), OBJ_BLOB, - &written_oid, NULL, 0)); + cl_assert_write_object(source, "foobar", OBJ_BLOB, &written_oid); cl_assert_equal_i(odb_source_freshen_object(&source->base, - &written_oid), 1); + &written_oid, NULL), 1); odb_source_free(&source->base); } diff --git a/tempfile.c b/tempfile.c index f0fdf582794ba5..dc9ca4e6459c64 100644 --- a/tempfile.c +++ b/tempfile.c @@ -42,8 +42,6 @@ * file created by its parent. */ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "abspath.h" #include "path.h" @@ -134,7 +132,8 @@ static void deactivate_tempfile(struct tempfile *tempfile) } /* Make sure errno contains a meaningful value on error */ -struct tempfile *create_tempfile_mode(const char *path, int mode) +struct tempfile *repo_create_tempfile_mode(struct repository *r, + const char *path, int mode) { struct tempfile *tempfile = new_tempfile(); @@ -150,7 +149,7 @@ struct tempfile *create_tempfile_mode(const char *path, int mode) return NULL; } activate_tempfile(tempfile); - if (adjust_shared_perm(the_repository, tempfile->filename.buf)) { + if (adjust_shared_perm(r, tempfile->filename.buf)) { int save_errno = errno; error("cannot fix permission bits on %s", tempfile->filename.buf); delete_tempfile(&tempfile); diff --git a/tempfile.h b/tempfile.h index 2227a095fd4289..f571f3c609c04a 100644 --- a/tempfile.h +++ b/tempfile.h @@ -4,6 +4,8 @@ #include "list.h" #include "strbuf.h" +struct repository; + /* * Handle temporary files. * @@ -92,11 +94,13 @@ struct tempfile { * `core.sharedRepository`, so it is not guaranteed to have the given * mode. */ -struct tempfile *create_tempfile_mode(const char *path, int mode); +struct tempfile *repo_create_tempfile_mode(struct repository *r, + const char *path, int mode); -static inline struct tempfile *create_tempfile(const char *path) +static inline struct tempfile *repo_create_tempfile(struct repository *r, + const char *path) { - return create_tempfile_mode(path, 0666); + return repo_create_tempfile_mode(r, path, 0666); } /* diff --git a/transport-helper.c b/transport-helper.c index 80f90eb7bace6f..b109fbd8c3ae63 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -162,7 +162,7 @@ static struct child_process *get_helper(struct transport *transport) data->helper = helper; data->no_disconnect_req = 0; - refspec_init_fetch(&data->rs); + refspec_init_fetch(&data->rs, the_hash_algo); /* * Open the output as FILE* so strbuf_getline_*() family of @@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport) close(data->helper->out); fclose(data->out); res = finish_command(data->helper); - FREE_AND_NULL(data->name); FREE_AND_NULL(data->helper); } + FREE_AND_NULL(data->name); return res; } @@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport, return -1; } +static int fetch_object_info_helper(struct transport *transport) +{ + get_helper(transport); + if (process_connect(transport, 0)) + return transport->vtable->fetch_object_info(transport); + + die(_("object-info requires protocol v2")); +} + struct push_update_ref_state { struct ref *hint; struct ref_push_report *report; @@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = { .get_refs_list = get_refs_list, .get_bundle_uri = get_bundle_uri, .fetch_refs = fetch_refs, + .fetch_object_info = fetch_object_info_helper, .push_refs = push_refs, .connect = connect_helper, .disconnect = release_helper diff --git a/transport-internal.h b/transport-internal.h index 051f3ab0dc95ec..60db0bedcdb9ae 100644 --- a/transport-internal.h +++ b/transport-internal.h @@ -45,6 +45,14 @@ struct transport_vtable { **/ int (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs); + /* + * Fetch object info (only size currently) from remote without + * downloading the objects. + * + * Uses object-info capability of v2 protocol. + */ + int (*fetch_object_info)(struct transport *transport); + /** * Push the objects and refs. Send the necessary objects, and * then, for any refs where peer_ref is set and diff --git a/transport.c b/transport.c index fc144f0aedabd9..f0a6a455479800 100644 --- a/transport.c +++ b/transport.c @@ -9,6 +9,7 @@ #include "hook.h" #include "pkt-line.h" #include "fetch-pack.h" +#include "fetch-object-info.h" #include "remote.h" #include "connect.h" #include "send-pack.h" @@ -432,6 +433,47 @@ static int get_bundle_uri(struct transport *transport) transport->bundles, stateless_rpc); } +static int fetch_object_info_via_pack(struct transport *transport) +{ + int ret = 0; + struct git_transport_data *data = transport->data; + struct packet_reader reader; + struct object_info_args args = { 0 }; + + args.server_options = transport->server_options; + args.oids = transport->smart_options->object_info_oids; + args.object_info_options = transport->smart_options->object_info_options; + + connect_setup(transport, 0); + packet_reader_init(&reader, data->fd[0], NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_DIE_ON_ERR_PACKET); + + data->version = discover_version(&reader); + transport->hash_algo = reader.hash_algo; + + ret = fetch_object_info(data->version, &args, &reader, + data->options.object_info_data, + transport->stateless_rpc, data->fd[1]); + + close(data->fd[0]); + if (data->fd[1] >= 0) + close(data->fd[1]); + if (finish_connect(data->conn)) + ret = -1; + data->conn = NULL; + + return ret; +} + +int transport_fetch_object_info(struct transport *transport) +{ + if (!transport->vtable->fetch_object_info) + die(_("remote does not support object-info")); + return transport->vtable->fetch_object_info(transport); +} + static int fetch_refs_via_pack(struct transport *transport, int nr_heads, struct ref **to_fetch) { @@ -1004,6 +1046,7 @@ static struct transport_vtable taken_over_vtable = { .get_refs_list = get_refs_via_connect, .get_bundle_uri = get_bundle_uri, .fetch_refs = fetch_refs_via_pack, + .fetch_object_info = fetch_object_info_via_pack, .push_refs = git_transport_push, .disconnect = disconnect_git }; @@ -1169,6 +1212,7 @@ static struct transport_vtable builtin_smart_vtable = { .get_refs_list = get_refs_via_connect, .get_bundle_uri = get_bundle_uri, .fetch_refs = fetch_refs_via_pack, + .fetch_object_info = fetch_object_info_via_pack, .push_refs = git_transport_push, .connect = connect_git, .disconnect = disconnect_git diff --git a/transport.h b/transport.h index 7e5867cffaaa4a..a7869d18e020fb 100644 --- a/transport.h +++ b/transport.h @@ -55,6 +55,10 @@ struct git_transport_options { * common commits to this oidset instead of fetching any packfiles. */ struct oidset *acked_commits; + + struct oid_array *object_info_oids; + struct object_info *object_info_data; + struct string_list *object_info_options; }; enum transport_family { @@ -309,6 +313,11 @@ int transport_get_remote_bundle_uri(struct transport *transport); const struct git_hash_algo *transport_get_hash_algo(struct transport *transport); int transport_fetch_refs(struct transport *transport, struct ref *refs); +/* + * Fetch the object info from remote + */ +int transport_fetch_object_info(struct transport *transport); + /* * If this flag is set, unlocking will avoid to call non-async-signal-safe * functions. This will necessarily leave behind some data structures which diff --git a/userdiff.c b/userdiff.c index b5412e6bc3ecd3..7129bf148266b7 100644 --- a/userdiff.c +++ b/userdiff.c @@ -362,6 +362,16 @@ PATTERNS("scheme", "\\|([^|\\\\]|\\\\.)*\\|" /* All other words should be delimited by spaces or parentheses. */ "|([^][)(}{ \t])+"), +PATTERNS("swift", + "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + /* hexadecimal, octal, and binary literals */ + "|0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+" + /* integers and floating-point numbers */ + "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?" + /* unary and binary operators */ + "|[-+*/%<>=!&|^~?]=|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"), PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", "\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"), { .name = "default", .binary = -1 }, diff --git a/worktree.c b/worktree.c index 30125827fd39ed..cbf95328a331ad 100644 --- a/worktree.c +++ b/worktree.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -111,34 +110,36 @@ static int is_main_worktree_bare(struct repository *repo) /** * get the main worktree */ -static struct worktree *get_main_worktree(int skip_reading_head) +static struct worktree *get_main_worktree(struct repository *repo, + int skip_reading_head) { struct worktree *worktree = NULL; struct strbuf worktree_path = STRBUF_INIT; - strbuf_add_real_path(&worktree_path, repo_get_common_dir(the_repository)); + strbuf_add_real_path(&worktree_path, repo_get_common_dir(repo)); strbuf_strip_suffix(&worktree_path, "/.git"); CALLOC_ARRAY(worktree, 1); - worktree->repo = the_repository; + worktree->repo = repo; worktree->path = strbuf_detach(&worktree_path, NULL); worktree->is_current = is_current_worktree(worktree); - worktree->is_bare = (the_repository->bare_cfg == 1) || - is_bare_repository(the_repository) || + worktree->is_bare = (repo->bare_cfg == 1) || + is_bare_repository(repo) || /* * When in a secondary worktree we have to also verify if the main * worktree is bare in $commondir/config.worktree. * This check is unnecessary if we're currently in the main worktree, * as prior checks already consulted all configs of the current worktree. */ - (!worktree->is_current && is_main_worktree_bare(the_repository)); + (!worktree->is_current && is_main_worktree_bare(repo)); if (!skip_reading_head) add_head_info(worktree); return worktree; } -struct worktree *get_linked_worktree(const char *id, +struct worktree *get_linked_worktree(struct repository *repo, + const char *id, int skip_reading_head) { struct worktree *worktree = NULL; @@ -148,7 +149,7 @@ struct worktree *get_linked_worktree(const char *id, if (!id) die("Missing linked worktree name"); - repo_common_path_append(the_repository, &path, "worktrees/%s/gitdir", id); + repo_common_path_append(repo, &path, "worktrees/%s/gitdir", id); if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0) /* invalid gitdir file */ goto done; @@ -162,7 +163,7 @@ struct worktree *get_linked_worktree(const char *id, } CALLOC_ARRAY(worktree, 1); - worktree->repo = the_repository; + worktree->repo = repo; worktree->path = strbuf_detach(&worktree_path, NULL); worktree->id = xstrdup(id); worktree->is_current = is_current_worktree(worktree); @@ -182,7 +183,8 @@ struct worktree *get_linked_worktree(const char *id, * retrieving worktree metadata that could be used when the worktree is known * to not be in a healthy state, e.g. when creating or repairing it. */ -static struct worktree **get_worktrees_internal(int skip_reading_head) +static struct worktree **get_worktrees_internal(struct repository *repo, + int skip_reading_head) { struct worktree **list = NULL; struct strbuf path = STRBUF_INIT; @@ -192,16 +194,16 @@ static struct worktree **get_worktrees_internal(int skip_reading_head) ALLOC_ARRAY(list, alloc); - list[counter++] = get_main_worktree(skip_reading_head); + list[counter++] = get_main_worktree(repo, skip_reading_head); - strbuf_addf(&path, "%s/worktrees", repo_get_common_dir(the_repository)); + strbuf_addf(&path, "%s/worktrees", repo_get_common_dir(repo)); dir = opendir(path.buf); strbuf_release(&path); if (dir) { while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) { struct worktree *linked = NULL; - if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) { + if ((linked = get_linked_worktree(repo, d->d_name, skip_reading_head))) { ALLOC_GROW(list, counter + 1, alloc); list[counter++] = linked; } @@ -214,14 +216,14 @@ static struct worktree **get_worktrees_internal(int skip_reading_head) return list; } -struct worktree **get_worktrees(void) +struct worktree **get_worktrees(struct repository *repo) { - return get_worktrees_internal(0); + return get_worktrees_internal(repo, 0); } -struct worktree **get_worktrees_without_reading_head(void) +struct worktree **get_worktrees_without_reading_head(struct repository *repo) { - return get_worktrees_internal(1); + return get_worktrees_internal(repo, 1); } char *get_worktree_git_dir(const struct worktree *wt) @@ -334,7 +336,7 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire) if (wt->prune_reason_valid) return wt->prune_reason; - if (should_prune_worktree(wt->id, &reason, &path, expire)) + if (should_prune_worktree(wt->repo, wt->id, &reason, &path, expire)) wt->prune_reason = strbuf_detach(&reason, NULL); wt->prune_reason_valid = 1; @@ -392,7 +394,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg, if (!is_absolute_path(wt->path)) { strbuf_addf_gently(errmsg, _("'%s' file does not contain absolute path to the working tree location"), - repo_common_path_replace(the_repository, &buf, "worktrees/%s/gitdir", wt->id)); + repo_common_path_replace(wt->repo, &buf, "worktrees/%s/gitdir", wt->id)); goto done; } @@ -414,12 +416,12 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg, goto done; } - strbuf_realpath(&realpath, repo_common_path_replace(the_repository, &buf, "worktrees/%s", wt->id), 1); + strbuf_realpath(&realpath, repo_common_path_replace(wt->repo, &buf, "worktrees/%s", wt->id), 1); ret = fspathcmp(path, realpath.buf); if (ret) strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"), - wt->path, repo_common_path_replace(the_repository, &buf, + wt->path, repo_common_path_replace(wt->repo, &buf, "worktrees/%s", wt->id)); done: free(path); @@ -440,12 +442,13 @@ void update_worktree_location(struct worktree *wt, const char *path_, if (is_main_worktree(wt)) BUG("can't relocate main worktree"); - wt_gitdir = repo_common_path(the_repository, "worktrees/%s/gitdir", wt->id); + wt_gitdir = repo_common_path(wt->repo, "worktrees/%s/gitdir", wt->id); strbuf_realpath(&gitdir, wt_gitdir, 1); strbuf_realpath(&path, path_, 1); strbuf_addf(&dotgit, "%s/.git", path.buf); if (fspathcmp(wt->path, path.buf)) { - write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); + write_worktree_linking_files(wt->repo, dotgit.buf, + gitdir.buf, use_relative_paths); free(wt->path); wt->path = strbuf_detach(&path, NULL); @@ -533,7 +536,8 @@ const struct worktree *find_shared_symref(struct worktree **worktrees, return NULL; } -int submodule_uses_worktrees(const char *path) +int submodule_uses_worktrees(struct repository *repo, + const char *path) { char *submodule_gitdir; struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT; @@ -542,7 +546,7 @@ int submodule_uses_worktrees(const char *path) int ret = 0; struct repository_format format = REPOSITORY_FORMAT_INIT; - submodule_gitdir = repo_submodule_path(the_repository, + submodule_gitdir = repo_submodule_path(repo, path, "%s", ""); if (!submodule_gitdir) return 0; @@ -595,13 +599,14 @@ void strbuf_worktree_ref(const struct worktree *wt, strbuf_addstr(sb, refname); } -int other_head_refs(refs_for_each_cb fn, void *cb_data) +int other_head_refs(struct repository *repo, + refs_for_each_cb fn, void *cb_data) { struct worktree **worktrees, **p; struct strbuf refname = STRBUF_INIT; int ret = 0; - worktrees = get_worktrees(); + worktrees = get_worktrees(repo); for (p = worktrees; *p; p++) { struct worktree *wt = *p; struct object_id oid; @@ -612,7 +617,7 @@ int other_head_refs(refs_for_each_cb fn, void *cb_data) strbuf_reset(&refname); strbuf_worktree_ref(wt, &refname, "HEAD"); - if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository), + if (refs_resolve_ref_unsafe(get_main_ref_store(repo), refname.buf, RESOLVE_REF_READING, &oid, &flag)) { @@ -658,7 +663,7 @@ static void repair_gitfile(struct worktree *wt, goto done; } - path = repo_common_path(the_repository, "worktrees/%s", wt->id); + path = repo_common_path(wt->repo, "worktrees/%s", wt->id); strbuf_realpath(&repo, path, 1); strbuf_addf(&dotgit, "%s/.git", wt->path); strbuf_addf(&gitdir, "%s/gitdir", repo.buf); @@ -685,7 +690,8 @@ static void repair_gitfile(struct worktree *wt, if (repair) { fn(0, wt->path, repair, cb_data); - write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); + write_worktree_linking_files(wt->repo, dotgit.buf, + gitdir.buf, use_relative_paths); } done: @@ -705,9 +711,10 @@ static void repair_noop(int iserr UNUSED, /* nothing */ } -void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths) +void repair_worktrees(struct repository *repo, worktree_repair_fn fn, + void *cb_data, int use_relative_paths) { - struct worktree **worktrees = get_worktrees_internal(1); + struct worktree **worktrees = get_worktrees_internal(repo, 1); struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ if (!fn) @@ -727,7 +734,7 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path if (is_main_worktree(wt)) goto done; - path = repo_common_path(the_repository, "worktrees/%s/gitdir", wt->id); + path = repo_common_path(wt->repo, "worktrees/%s/gitdir", wt->id); strbuf_realpath(&gitdir, path, 1); if (strbuf_read_file(&dotgit, gitdir.buf, 0) < 0) @@ -743,16 +750,17 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path if (!file_exists(dotgit.buf)) goto done; - write_worktree_linking_files(dotgit.buf, gitdir.buf, is_relative_path); + write_worktree_linking_files(wt->repo, dotgit.buf, + gitdir.buf, is_relative_path); done: strbuf_release(&gitdir); strbuf_release(&dotgit); free(path); } -void repair_worktrees_after_gitdir_move(const char *old_path) +void repair_worktrees_after_gitdir_move(struct repository *repo, const char *old_path) { - struct worktree **worktrees = get_worktrees_internal(1); + struct worktree **worktrees = get_worktrees_internal(repo, 1); struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ for (; *wt; wt++) @@ -760,7 +768,7 @@ void repair_worktrees_after_gitdir_move(const char *old_path) free_worktrees(worktrees); } -static int is_main_worktree_path(const char *path) +static int is_main_worktree_path(struct repository *repo, const char *path) { struct strbuf target = STRBUF_INIT; struct strbuf maindir = STRBUF_INIT; @@ -768,7 +776,7 @@ static int is_main_worktree_path(const char *path) strbuf_add_real_path(&target, path); strbuf_strip_suffix(&target, "/.git"); - strbuf_add_real_path(&maindir, repo_get_common_dir(the_repository)); + strbuf_add_real_path(&maindir, repo_get_common_dir(repo)); strbuf_strip_suffix(&maindir, "/.git"); cmp = fspathcmp(maindir.buf, target.buf); @@ -786,7 +794,9 @@ static int is_main_worktree_path(const char *path) * * Returns -1 on failure and strbuf.len on success. */ -static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred) +static ssize_t infer_backlink(struct repository *repo, + const char *gitfile, + struct strbuf *inferred) { struct strbuf actual = STRBUF_INIT; const char *id; @@ -801,7 +811,7 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred) id++; /* advance past '/' to point at */ if (!*id) goto error; - repo_common_path_replace(the_repository, inferred, "worktrees/%s", id); + repo_common_path_replace(repo, inferred, "worktrees/%s", id); if (!is_directory(inferred->buf)) goto error; @@ -817,7 +827,8 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred) * Repair /worktrees//gitdir if missing, corrupt, or not pointing at * the worktree's path. */ -void repair_worktree_at_path(const char *path, +void repair_worktree_at_path(struct repository *repo, + const char *path, worktree_repair_fn fn, void *cb_data, int use_relative_paths) { @@ -833,7 +844,7 @@ void repair_worktree_at_path(const char *path, if (!fn) fn = repair_noop; - if (is_main_worktree_path(path)) + if (is_main_worktree_path(repo, path)) goto done; strbuf_addf(&dotgit, "%s/.git", path); @@ -842,7 +853,7 @@ void repair_worktree_at_path(const char *path, goto done; } - infer_backlink(dotgit.buf, &inferred_backlink); + infer_backlink(repo, dotgit.buf, &inferred_backlink); strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0); dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); if (dotgit_contents) { @@ -915,7 +926,8 @@ void repair_worktree_at_path(const char *path, if (repair) { fn(0, gitdir.buf, repair, cb_data); - write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); + write_worktree_linking_files(repo, dotgit.buf, + gitdir.buf, use_relative_paths); } done: free(dotgit_contents); @@ -926,12 +938,16 @@ void repair_worktree_at_path(const char *path, strbuf_release(&dotgit); } -int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire) +int should_prune_worktree(struct repository *repo, + const char *id, + struct strbuf *reason, + char **wtpath, + timestamp_t expire) { struct stat st; struct strbuf dotgit = STRBUF_INIT; struct strbuf gitdir = STRBUF_INIT; - struct strbuf repo = STRBUF_INIT; + struct strbuf repo_path = STRBUF_INIT; struct strbuf file = STRBUF_INIT; char *path = NULL; int rc = 0; @@ -941,17 +957,17 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, *wtpath = NULL; - path = repo_common_path(the_repository, "worktrees/%s", id); - strbuf_realpath(&repo, path, 1); + path = repo_common_path(repo, "worktrees/%s", id); + strbuf_realpath(&repo_path, path, 1); FREE_AND_NULL(path); - strbuf_addf(&gitdir, "%s/gitdir", repo.buf); - if (!is_directory(repo.buf)) { + strbuf_addf(&gitdir, "%s/gitdir", repo_path.buf); + if (!is_directory(repo_path.buf)) { strbuf_addstr(reason, _("not a valid directory")); rc = 1; goto done; } - strbuf_addf(&file, "%s/locked", repo.buf); + strbuf_addf(&file, "%s/locked", repo_path.buf); if (file_exists(file.buf)) { goto done; } @@ -995,12 +1011,12 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, if (is_absolute_path(path)) { strbuf_addstr(&dotgit, path); } else { - strbuf_addf(&dotgit, "%s/%s", repo.buf, path); + strbuf_addf(&dotgit, "%s/%s", repo_path.buf, path); strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0); } if (!file_exists(dotgit.buf)) { strbuf_reset(&file); - strbuf_addf(&file, "%s/index", repo.buf); + strbuf_addf(&file, "%s/index", repo_path.buf); if (stat(file.buf, &st) || st.st_mtime <= expire) { strbuf_addstr(reason, _("gitdir file points to non-existent location")); rc = 1; @@ -1012,17 +1028,18 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, free(path); strbuf_release(&dotgit); strbuf_release(&gitdir); - strbuf_release(&repo); + strbuf_release(&repo_path); strbuf_release(&file); return rc; } -static int move_config_setting(const char *key, const char *value, +static int move_config_setting(struct repository *repo, + const char *key, const char *value, const char *from_file, const char *to_file) { - if (repo_config_set_in_file_gently(the_repository, to_file, key, NULL, value)) + if (repo_config_set_in_file_gently(repo, to_file, key, NULL, value)) return error(_("unable to set %s in '%s'"), key, to_file); - if (repo_config_set_in_file_gently(the_repository, from_file, key, NULL, NULL)) + if (repo_config_set_in_file_gently(repo, from_file, key, NULL, NULL)) return error(_("unable to unset %s in '%s'"), key, from_file); return 0; } @@ -1042,7 +1059,7 @@ int init_worktree_config(struct repository *r) */ if (r->repository_format_worktree_config) return 0; - if ((res = repo_config_set_gently(the_repository, "extensions.worktreeConfig", "true"))) + if ((res = repo_config_set_gently(r, "extensions.worktreeConfig", "true"))) return error(_("failed to set extensions.worktreeConfig setting")); common_config_file = xstrfmt("%s/config", r->commondir); @@ -1058,7 +1075,7 @@ int init_worktree_config(struct repository *r) * _could_ be negating a global core.bare=true. */ if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) { - if ((res = move_config_setting("core.bare", "true", + if ((res = move_config_setting(r, "core.bare", "true", common_config_file, main_worktree_file))) goto cleanup; @@ -1070,7 +1087,7 @@ int init_worktree_config(struct repository *r) * upgrade to worktree config. */ if (!git_configset_get_value(&cs, "core.worktree", &core_worktree, NULL)) { - if ((res = move_config_setting("core.worktree", core_worktree, + if ((res = move_config_setting(r, "core.worktree", core_worktree, common_config_file, main_worktree_file))) goto cleanup; @@ -1089,37 +1106,38 @@ int init_worktree_config(struct repository *r) return res; } -void write_worktree_linking_files(const char *dotgit, const char *gitdir, +void write_worktree_linking_files(struct repository *repo, + const char *dotgit, const char *gitdir, int use_relative_paths) { struct strbuf path = STRBUF_INIT; - struct strbuf repo = STRBUF_INIT; + struct strbuf repo_path = STRBUF_INIT; struct strbuf tmp = STRBUF_INIT; strbuf_addstr(&path, dotgit); strbuf_strip_suffix(&path, "/.git"); strbuf_realpath(&path, path.buf, 1); - strbuf_addstr(&repo, gitdir); - strbuf_strip_suffix(&repo, "/gitdir"); - strbuf_realpath(&repo, repo.buf, 1); + strbuf_addstr(&repo_path, gitdir); + strbuf_strip_suffix(&repo_path, "/gitdir"); + strbuf_realpath(&repo_path, repo_path.buf, 1); - if (use_relative_paths && !the_repository->repository_format_relative_worktrees) { - if (upgrade_repository_format(the_repository, 1) < 0) + if (use_relative_paths && !repo->repository_format_relative_worktrees) { + if (upgrade_repository_format(repo, 1) < 0) die(_("unable to upgrade repository format to support relative worktrees")); - if (repo_config_set_gently(the_repository, "extensions.relativeWorktrees", "true")) + if (repo_config_set_gently(repo, "extensions.relativeWorktrees", "true")) die(_("unable to set extensions.relativeWorktrees setting")); - the_repository->repository_format_relative_worktrees = 1; + repo->repository_format_relative_worktrees = 1; } if (use_relative_paths) { - write_file(gitdir, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); - write_file(dotgit, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + write_file(gitdir, "%s/.git", relative_path(path.buf, repo_path.buf, &tmp)); + write_file(dotgit, "gitdir: %s", relative_path(repo_path.buf, path.buf, &tmp)); } else { write_file(gitdir, "%s/.git", path.buf); - write_file(dotgit, "gitdir: %s", repo.buf); + write_file(dotgit, "gitdir: %s", repo_path.buf); } strbuf_release(&path); - strbuf_release(&repo); + strbuf_release(&repo_path); strbuf_release(&tmp); } diff --git a/worktree.h b/worktree.h index 1075409f9aa2b7..fbb2757f5bf3c3 100644 --- a/worktree.h +++ b/worktree.h @@ -28,7 +28,7 @@ struct worktree { * The caller is responsible for freeing the memory from the returned * worktrees by calling free_worktrees(). */ -struct worktree **get_worktrees(void); +struct worktree **get_worktrees(struct repository *repo); /* * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to @@ -36,7 +36,7 @@ struct worktree **get_worktrees(void); * the HEAD ref. This is useful in contexts where it is assumed that the * refdb may not be in a consistent state. */ -struct worktree **get_worktrees_without_reading_head(void); +struct worktree **get_worktrees_without_reading_head(struct repository *repo); /* * Construct a struct worktree corresponding to repo->gitdir and @@ -47,7 +47,7 @@ struct worktree *get_current_worktree(struct repository *repo); /* * Returns 1 if linked worktrees exist, 0 otherwise. */ -int submodule_uses_worktrees(const char *path); +int submodule_uses_worktrees(struct repository *repo, const char *path); /* * Return git dir of the worktree. Note that the path may be relative. @@ -76,7 +76,8 @@ struct worktree *find_worktree(struct worktree **list, * Look up the worktree corresponding to `id`, or NULL of no such worktree * exists. */ -struct worktree *get_linked_worktree(const char *id, +struct worktree *get_linked_worktree(struct repository *repo, + const char *id, int skip_reading_head); /* @@ -112,7 +113,8 @@ const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire); * `expire` defines a grace period to prune the worktree when its path * does not exist. */ -int should_prune_worktree(const char *id, +int should_prune_worktree(struct repository *repo, + const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire); @@ -142,12 +144,14 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path, * function, if non-NULL, is called with the path of the worktree and a * description of the repair or error, along with the callback user-data. */ -void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths); +void repair_worktrees(struct repository *repo, worktree_repair_fn, + void *cb_data, int use_relative_paths); /* * Repair the linked worktrees after the gitdir has been moved. */ -void repair_worktrees_after_gitdir_move(const char *old_path); +void repair_worktrees_after_gitdir_move(struct repository *repo, + const char *old_path); /* * Repair the linked worktree after the gitdir has been moved. @@ -164,7 +168,9 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path * worktree and a description of the repair or error, along with the callback * user-data. */ -void repair_worktree_at_path(const char *, worktree_repair_fn, +void repair_worktree_at_path(struct repository *repo, + const char *path, + worktree_repair_fn fn, void *cb_data, int use_relative_paths); /* @@ -196,7 +202,7 @@ int is_shared_symref(const struct worktree *wt, * Similar to head_ref() for all HEADs _except_ one from the current * worktree, which is covered by head_ref(). */ -int other_head_refs(refs_for_each_cb fn, void *cb_data); +int other_head_refs(struct repository *repo, refs_for_each_cb fn, void *cb_data); int is_worktree_being_rebased(const struct worktree *wt, const char *target); int is_worktree_being_bisected(const struct worktree *wt, const char *target); @@ -239,7 +245,8 @@ int init_worktree_config(struct repository *r); * dotgit: "/path/to/foo/.git" * gitdir: "/path/to/repo/worktrees/foo/gitdir" */ -void write_worktree_linking_files(const char *dotgit, const char *gitdir, +void write_worktree_linking_files(struct repository *repo, + const char *dotgit, const char *gitdir, int use_relative_paths); #endif diff --git a/wt-status.c b/wt-status.c index 58461e02f886ae..57772c7501fdba 100644 --- a/wt-status.c +++ b/wt-status.c @@ -832,14 +832,16 @@ static void wt_status_collect_untracked(struct wt_status *s) for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; if (index_name_is_other(istate, ent->name, ent->len)) - string_list_insert(&s->untracked, ent->name); + string_list_append(&s->untracked, ent->name); } + string_list_sort_u(&s->untracked, 0); for (i = 0; i < dir.ignored_nr; i++) { struct dir_entry *ent = dir.ignored[i]; if (index_name_is_other(istate, ent->name, ent->len)) - string_list_insert(&s->ignored, ent->name); + string_list_append(&s->ignored, ent->name); } + string_list_sort_u(&s->ignored, 0); dir_clear(&dir);