diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc index 27becd62a941da..f86d3eb2635cb5 100644 --- a/Documentation/RelNotes/2.56.0.adoc +++ b/Documentation/RelNotes/2.56.0.adoc @@ -139,6 +139,11 @@ UI, Workflows & Features * The command line completion (in contrib/) has been taught to handle the experimental 'git history' command. + * 'git checkout' and 'git worktree add' makes guesses based on a name + of a remote-tracking branch, but does not give an error when such a + remote-tracking branch cannot be uniquely identified, which has + been corrected. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -484,6 +489,15 @@ Performance, Internal Implementation, Development Support etc. 'struct repository', continuing the libification process and allowing per-repository control (such as for submodules). + * The reftable code has been optimized to avoid an unnecessary + stat/reload of the stack when an addition already holds the + list_file lock, reducing the number of newfstatat syscalls from + linear to constant when writing refs. + + * The application of the edited patch in 'git add -e' has been + refactored to use the internal apply API directly, avoiding the need + to spawn a 'git apply' subprocess. + Fixes since v2.55 ----------------- @@ -748,6 +762,23 @@ Fixes since v2.55 This prevents intended textual URLs from being mangled or mistakenly treated as metadata keys. + * The instructions for deprecated commands emitted by + you_still_use_that() have been reworded to clarify that the removal + decision is final and to provide more assertive guidance on finding + a replacement. + (merge 8ace32221c jc/you-still-use-that later to maint). + + * The zsh completion script (in 'contrib/') has been updated to + correctly locate the Git command after global options like '-C' by + properly skipping them, similar to how the bash completion does. + + * The git worktree repair command failed to rewrite the .git file of + a working tree from a relative path to an absolute path when the + command was run in the working tree itself. The + read_gitfile_gently() function was modified to also return whether + the path originally recorded in the file was absolute, and this new + capability is used to correctly detect such mismatches. + * Other code cleanup, docfix, build fix, etc. (merge 026636128f ss/submittingpatches-typofix later to maint). (merge d2af22cc21 jc/rerere-doc-typofix later to maint). diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc index a248076ea50bd5..0930183b91cb8f 100644 --- a/Documentation/config/worktree.adoc +++ b/Documentation/config/worktree.adoc @@ -5,8 +5,9 @@ set to true, `worktree add` tries to find a remote-tracking branch whose name uniquely matches the new branch name. If such a branch exists, it is checked out and set as "upstream" - for the new branch. If no such match can be found, it falls - back to creating a new branch from the current `HEAD`. + for the new branch. If multiple matches are found, the command + fails. If no such match can be found, it falls back to + creating a new branch from the current `HEAD`. `worktree.useRelativePaths`:: Link worktrees using relative paths (when "`true`") or absolute diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc index fbf8426cd974e4..32787eacc3c17d 100644 --- a/Documentation/git-worktree.adoc +++ b/Documentation/git-worktree.adoc @@ -219,7 +219,9 @@ To remove a locked worktree, specify `--force` twice. of creating a new branch from `HEAD`, if there exists a tracking branch in exactly one remote matching the basename of __, base the new branch on the remote-tracking branch, and mark - the remote-tracking branch as "upstream" from the new branch. + the remote-tracking branch as "upstream" from the new branch. If + there are multiple matches, the command fails. If there is no + match, the command falls back to creating a new branch from `HEAD`. + This can also be set up as the default behaviour by using the `worktree.guessRemote` config option. diff --git a/builtin/add.c b/builtin/add.c index 7c95dd65d63b5f..f565d4d0bc46d2 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -13,7 +13,6 @@ #include "dir.h" #include "gettext.h" #include "pathspec.h" -#include "run-command.h" #include "object-file.h" #include "odb.h" #include "odb/transaction.h" @@ -23,10 +22,10 @@ #include "diff.h" #include "read-cache.h" #include "revision.h" -#include "strvec.h" #include "submodule.h" #include "add-interactive.h" #include "merge-ll.h" +#include "apply.h" static const char * const builtin_add_usage[] = { N_("git add [] [--] ..."), @@ -189,7 +188,8 @@ static int edit_patch(struct repository *repo, const char *prefix) { char *file = repo_git_path(repo, "ADD_EDIT.patch"); - struct child_process child = CHILD_PROCESS_INIT; + struct apply_state state; + const char *apply_argv[2]; struct rev_info rev; int out; struct stat st; @@ -219,11 +219,16 @@ static int edit_patch(struct repository *repo, if (!st.st_size) die(_("empty patch. aborted")); - child.git_cmd = 1; - strvec_pushl(&child.args, "apply", "--recount", "--cached", file, - NULL); - if (run_command(&child)) + apply_argv[0] = file; + apply_argv[1] = NULL; + if (init_apply_state(&state, repo, NULL)) + die(_("could not initialize apply state")); + state.cached = 1; + if (check_apply_state(&state, 0)) + die(_("could not check apply state")); + if (apply_all_patches(&state, 1, apply_argv, APPLY_OPT_RECOUNT)) die(_("could not apply '%s'"), file); + clear_apply_state(&state); unlink(file); free(file); diff --git a/builtin/checkout.c b/builtin/checkout.c index 55e3a89a852712..2bc21aa49be05c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1343,13 +1343,51 @@ enum checkout_command { CHECKOUT_RESTORE = 3, }; +static void advise_disambiguating_remotes(enum checkout_command which_command, + const char *branch, + const struct string_list *matched_remote_names) +{ + const char *cmdname; + struct string_list_item *item; + + switch (which_command) { + case CHECKOUT_CHECKOUT: + cmdname = "checkout"; + break; + case CHECKOUT_SWITCH: + cmdname = "switch"; + break; + default: + BUG("command <%d> should not reach advise_disambiguating_remotes", + which_command); + break; + } + + advise(_("Branch name '%s' appears in multiple remotes:"), branch); + for_each_string_list_item(item, matched_remote_names) { + advise(_(" %s"), item->string); + } + advise(_("If you meant to check out a remote tracking branch on ,\n" + "you can do so by fully qualifying the name with the --track option:\n" + "\n" + " git %s --track /%s\n" + "\n" + "If you'd like to always have checkouts of an ambiguous name prefer\n" + "one remote, e.g. the 'origin' remote, consider setting\n" + "checkout.defaultRemote=origin in your config."), + cmdname, branch); +} + static char *parse_remote_branch(const char *arg, struct object_id *rev, int could_be_checkout_paths, enum checkout_command which_command) { int num_matches = 0; - char *remote = unique_tracking_name(arg, rev, &num_matches); + struct string_list matched_remote_names = STRING_LIST_INIT_DUP; + + char *remote = unique_tracking_name(arg, rev, &num_matches, + &matched_remote_names); if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n" @@ -1358,37 +1396,15 @@ static char *parse_remote_branch(const char *arg, } if (!remote && num_matches > 1) { - if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) { - const char *cmdname; - - switch (which_command) { - case CHECKOUT_CHECKOUT: - cmdname = "checkout"; - break; - case CHECKOUT_SWITCH: - cmdname = "switch"; - break; - default: - BUG("command <%d> should not reach parse_remote_branch", - which_command); - break; - } - - advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" - "you can do so by fully qualifying the name with the --track option:\n" - "\n" - " git %s --track origin/\n" - "\n" - "If you'd like to always have checkouts of an ambiguous prefer\n" - "one remote, e.g. the 'origin' remote, consider setting\n" - "checkout.defaultRemote=origin in your config."), - cmdname); - } - - die(_("'%s' matched multiple (%d) remote tracking branches"), - arg, num_matches); + if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) + advise_disambiguating_remotes(which_command, arg, + &matched_remote_names); + die(_("'%s' matched multiple (%d) remote tracking branches"), + arg, num_matches); } + string_list_clear(&matched_remote_names, 0); + return remote; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 15a1c9624a79db..ddf8d78fd53c86 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -764,7 +764,26 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote) return 1; } -static char *dwim_branch(const char *path, char **new_branch) +static void advise_disambiguating_remotes(const char *path, const char *branch, + const struct string_list *matched_remote_names) +{ + struct string_list_item *item; + + advise(_("Branch name '%s' appears in multiple remotes:"), branch); + for_each_string_list_item(item, matched_remote_names) { + advise(_(" %s"), item->string); + } + advise(_("If you meant to create a worktree from a remote tracking branch on\n" + ", you can do so by:\n" + "\n" + " git worktree add -b %s %s /%s\n" + "\n" + "If you'd like to always prefer some remote, e.g. 'origin',\n" + "consider setting checkout.defaultRemote=origin in your config."), + branch, path, branch); +} + +static char *dwim_branch(const struct add_opts *opts, const char *path, char **new_branch) { int n; int branch_exists; @@ -782,7 +801,21 @@ static char *dwim_branch(const char *path, char **new_branch) *new_branch = branchname; if (guess_remote) { struct object_id oid; - char *remote = unique_tracking_name(*new_branch, &oid, NULL); + char *remote; + int num_matches = 0; + struct string_list matched_remote_names = STRING_LIST_INIT_DUP; + + remote = unique_tracking_name(*new_branch, &oid, &num_matches, + &matched_remote_names); + if (!remote && num_matches > 1) { + if (!opts->quiet && + advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) + advise_disambiguating_remotes(path, *new_branch, + &matched_remote_names); + die(_("'%s' matched multiple (%d) remote tracking branches"), + *new_branch, num_matches); + } + string_list_clear(&matched_remote_names, 0); return remote; } return NULL; @@ -890,7 +923,7 @@ static int add(int ac, const char **av, const char *prefix, opts.orphan = dwim_orphan(&opts, !!opt_track, 0); } else if (ac < 2) { /* DWIM: Guess branch name from path. */ - char *s = dwim_branch(path, &new_branch_to_free); + char *s = dwim_branch(&opts, path, &new_branch_to_free); if (s) branch = branch_to_free = s; new_branch = new_branch_to_free; @@ -901,17 +934,29 @@ static int add(int ac, const char **av, const char *prefix, if (!strcmp(branch, "HEAD")) can_use_local_refs(&opts); } else if (ac == 2) { - struct object_id oid; struct commit *commit; - char *remote; commit = lookup_commit_reference_by_name(branch); if (!commit) { - remote = unique_tracking_name(branch, &oid, NULL); + struct object_id oid; + char *remote; + int num_matches = 0; + struct string_list matched_remote_names = STRING_LIST_INIT_DUP; + + remote = unique_tracking_name(branch, &oid, &num_matches, + &matched_remote_names); if (remote) { new_branch = branch; branch = new_branch_to_free = remote; + } else if (num_matches > 1) { + if (!opts.quiet && + advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) + advise_disambiguating_remotes(path, branch, + &matched_remote_names); + die(_("'%s' matched multiple (%d) remote tracking branches"), + branch, num_matches); } + string_list_clear(&matched_remote_names, 0); } if (!strcmp(branch, "HEAD")) diff --git a/checkout.c b/checkout.c index 1588b116eedf06..a0d0229435d2e0 100644 --- a/checkout.c +++ b/checkout.c @@ -8,6 +8,7 @@ #include "checkout.h" #include "config.h" #include "strbuf.h" +#include "string-list.h" struct tracking_name_data { /* const */ char *src_ref; @@ -17,6 +18,7 @@ struct tracking_name_data { const char *default_remote; char *default_dst_ref; struct object_id *default_dst_oid; + struct string_list *remote_names; }; #define TRACKING_NAME_DATA_INIT { 0 } @@ -39,6 +41,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data) oidcpy(dst, cb->dst_oid); cb->default_dst_oid = dst; } + if (cb->remote_names) + string_list_append(cb->remote_names, remote->name); if (cb->dst_ref) { free(query.dst); return 0; @@ -48,14 +52,19 @@ static int check_tracking_name(struct remote *remote, void *cb_data) } char *unique_tracking_name(const char *name, struct object_id *oid, - int *dwim_remotes_matched) + int *dwim_remotes_matched, + struct string_list *dwim_remote_names) { struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT; const char *default_remote = NULL; - if (!repo_config_get_string_tmp(the_repository, "checkout.defaultremote", &default_remote)) + + if (!repo_config_get_string_tmp(the_repository, + "checkout.defaultremote", + &default_remote)) cb_data.default_remote = default_remote; cb_data.src_ref = xstrfmt("refs/heads/%s", name); cb_data.dst_oid = oid; + cb_data.remote_names = dwim_remote_names; for_each_remote(check_tracking_name, &cb_data); if (dwim_remotes_matched) *dwim_remotes_matched = cb_data.num_matches; diff --git a/checkout.h b/checkout.h index 55920e7aeb243d..0b185a0fc934eb 100644 --- a/checkout.h +++ b/checkout.h @@ -3,6 +3,8 @@ #include "hash.h" +struct string_list; + /* * Check if the branch name uniquely matches a branch name on a remote * tracking branch. Return the name of the remote if such a branch @@ -10,6 +12,7 @@ */ char *unique_tracking_name(const char *name, struct object_id *oid, - int *dwim_remotes_matched); + int *dwim_remotes_matched, + struct string_list *dwim_remote_names); #endif /* CHECKOUT_H */ diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index c32186a977e2d1..d5c526665b2b31 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -227,6 +227,7 @@ __git_zsh_main () '(-p --paginate --no-pager)'{-p,--paginate}'[pipe all output into ''less'']' \ '(-p --paginate)--no-pager[do not pipe git output into a pager]' \ '--git-dir=-[set the path to the repository]: :_directories' \ + '*-C[run as if git was started in ]: :_directories' \ '--bare[treat the repository as a bare repository]' \ '(- :)--version[prints the git suite version]' \ '--exec-path=-[path to where your core git programs are installed]:: :_directories' \ @@ -251,7 +252,29 @@ __git_zsh_main () done ;; (arg) - local command="${words[1]}" __git_dir __git_cmd_idx=1 + local command="${words[1]}" __git_dir __git_cmd_idx + local -a __git_C_args + local -i i=2 + + while (( i <= $#orig_words )); do + case ${orig_words[i]} in + -C) + __git_C_args+=(-C ${orig_words[i+1]}) + (( i++ )) + ;; + -c|--git-dir|--work-tree|--namespace) + (( i++ )) + ;; + -*) + ;; + *) + break + ;; + esac + (( i++ )) + done + + __git_cmd_idx=$(( i - 1 )) if (( $+opt_args[--bare] )); then __git_dir='.' diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 08a75fb3287502..10db03991e689a 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1001,9 +1001,8 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out, if (!arg) { struct reftable_addition *addition; - ret = reftable_stack_new_addition(&addition, be->stack, - &reftable_be_write_options(refs)->opts, - REFTABLE_STACK_NEW_ADDITION_RELOAD); + ret = reftable_stack_addition_new(&addition, be->stack, + &reftable_be_write_options(refs)->opts); if (ret) { if (ret == REFTABLE_LOCK_ERROR) strbuf_addstr(err, "cannot lock references"); @@ -2009,8 +2008,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, - &reftable_be_write_options(refs)->opts, - REFTABLE_STACK_NEW_ADDITION_RELOAD); + &reftable_be_write_options(refs)->opts); done: assert(ret != REFTABLE_API_ERROR); @@ -2040,8 +2038,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, - &reftable_be_write_options(refs)->opts, - REFTABLE_STACK_NEW_ADDITION_RELOAD); + &reftable_be_write_options(refs)->opts); done: assert(ret != REFTABLE_API_ERROR); @@ -2423,8 +2420,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg, - &reftable_be_write_options(refs)->opts, - REFTABLE_STACK_NEW_ADDITION_RELOAD); + &reftable_be_write_options(refs)->opts); done: return ret; @@ -2498,8 +2494,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg, - &reftable_be_write_options(refs)->opts, - REFTABLE_STACK_NEW_ADDITION_RELOAD); + &reftable_be_write_options(refs)->opts); assert(ret != REFTABLE_API_ERROR); return ret; @@ -2620,9 +2615,8 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, if (ret < 0) goto done; - ret = reftable_stack_new_addition(&add, be->stack, - &reftable_be_write_options(refs)->opts, - REFTABLE_STACK_NEW_ADDITION_RELOAD); + ret = reftable_stack_addition_new(&add, be->stack, + &reftable_be_write_options(refs)->opts); if (ret < 0) goto done; diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index 5d22d84e80a4cb..875d09d2415b19 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h @@ -58,22 +58,13 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st); /* holds a transaction to add tables at the top of a stack. */ struct reftable_addition; -enum { - /* - * Reload the stack when the stack is out-of-date after locking it. - */ - REFTABLE_STACK_NEW_ADDITION_RELOAD = (1 << 0), -}; - /* * returns a new transaction to add reftables to the given stack. As a side - * effect, the ref database is locked. Accepts REFTABLE_STACK_NEW_ADDITION_* - * flags. + * effect, the ref database is locked. */ -int reftable_stack_new_addition(struct reftable_addition **dest, +int reftable_stack_addition_new(struct reftable_addition **dest, struct reftable_stack *st, - const struct reftable_write_options *opts, - unsigned int flags); + const struct reftable_write_options *opts); /* Adds a reftable to transaction. */ int reftable_addition_add(struct reftable_addition *add, @@ -93,14 +84,12 @@ void reftable_addition_destroy(struct reftable_addition *add); /* * Add a new table to the stack. The write_table function must call * reftable_writer_set_limits, add refs and return an error value. - * The flags are passed through to `reftable_stack_new_addition()`. */ int reftable_stack_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *write_arg), void *write_arg, - const struct reftable_write_options *opts, - unsigned flags); + const struct reftable_write_options *opts); struct reftable_iterator; diff --git a/reftable/stack.c b/reftable/stack.c index 308f9578f01c9c..47a60db079b266 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -536,6 +536,8 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir, goto out; } + p->list_lock = REFTABLE_FLOCK_INIT; + err = reftable_stack_reload_maybe_reuse(p, 1); if (err < 0) goto out; @@ -551,14 +553,21 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir, /* * Check whether the given stack is up-to-date with what we have in memory. + * If skip_if_locked is set skip stack reloading if the stack is currently + * locked. Stack reloading must _not_ be skipped right after obtaining the + * lock, to check for concurrent updates which may have happened. + * * Returns 0 if so, 1 if the stack is out-of-date or a negative error code * otherwise. */ -static int stack_uptodate(struct reftable_stack *st) +static int stack_uptodate(struct reftable_stack *st, int skip_if_locked) { char **names = NULL; int err; + if (skip_if_locked && st->list_lock.fd != -1) + return 0; + /* * When we have cached stat information available then we use it to * verify whether the file has been rewritten. @@ -621,17 +630,23 @@ static int stack_uptodate(struct reftable_stack *st) int reftable_stack_reload(struct reftable_stack *st) { - int err = stack_uptodate(st); + int err = stack_uptodate(st, 1); if (err > 0) return reftable_stack_reload_maybe_reuse(st, 1); return err; } struct reftable_addition { - struct reftable_flock tables_list_lock; struct reftable_stack *stack; struct reftable_write_options opts; + /* + * While the list lock is acquired on the stack, we need to distinguish + * which 'reftable_addition' is responsible for the lock. This avoids + * clearing the lock of another 'reftable_addition'. + */ + unsigned int locked : 1; + char **new_tables; size_t new_tables_len, new_tables_cap; uint64_t next_update_index; @@ -653,14 +668,15 @@ static void reftable_addition_close(struct reftable_addition *add) add->new_tables_len = 0; add->new_tables_cap = 0; - flock_release(&add->tables_list_lock); + if (add->locked) + flock_release(&add->stack->list_lock); + add->locked = 0; reftable_buf_release(&nm); } static int reftable_stack_init_addition(struct reftable_addition *add, struct reftable_stack *st, - const struct reftable_write_options *opts, - unsigned int flags) + const struct reftable_write_options *opts) { struct reftable_buf lock_file_name = REFTABLE_BUF_INIT; int err; @@ -670,31 +686,28 @@ static int reftable_stack_init_addition(struct reftable_addition *add, if (opts) add->opts = *opts; - err = flock_acquire(&add->tables_list_lock, st->list_file, + err = flock_acquire(&add->stack->list_lock, st->list_file, add->opts.lock_timeout_ms); if (err < 0) goto done; + add->locked = 1; if (add->opts.default_permissions) { - if (chmod(add->tables_list_lock.path, + if (chmod(add->stack->list_lock.path, add->opts.default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } } - err = stack_uptodate(st); + err = stack_uptodate(st, 0); if (err < 0) goto done; - if (err > 0 && flags & REFTABLE_STACK_NEW_ADDITION_RELOAD) { + if (err > 0) { err = reftable_stack_reload_maybe_reuse(add->stack, 1); if (err) goto done; } - if (err > 0) { - err = REFTABLE_OUTDATED_ERROR; - goto done; - } add->next_update_index = reftable_stack_next_update_index(st); done: @@ -708,13 +721,12 @@ static int stack_try_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *arg), void *arg, - const struct reftable_write_options *opts, - unsigned flags) + const struct reftable_write_options *opts) { struct reftable_addition add; int err; - err = reftable_stack_init_addition(&add, st, opts, flags); + err = reftable_stack_init_addition(&add, st, opts); if (err < 0) goto done; @@ -731,17 +743,10 @@ static int stack_try_add(struct reftable_stack *st, int reftable_stack_add(struct reftable_stack *st, int (*write)(struct reftable_writer *wr, void *arg), void *arg, - const struct reftable_write_options *opts, - unsigned flags) + const struct reftable_write_options *opts) { - int err = stack_try_add(st, write, arg, opts, flags); + int err = stack_try_add(st, write, arg, opts); if (err < 0) { - if (err == REFTABLE_OUTDATED_ERROR) { - /* Ignore error return, we want to propagate - REFTABLE_OUTDATED_ERROR. - */ - reftable_stack_reload(st); - } return err; } @@ -787,7 +792,7 @@ int reftable_addition_commit(struct reftable_addition *add) goto done; } - err = reftable_write_data(add->tables_list_lock.fd, + err = reftable_write_data(add->stack->list_lock.fd, table_list.buf, table_list.len); reftable_buf_release(&table_list); if (err < 0) { @@ -795,17 +800,18 @@ int reftable_addition_commit(struct reftable_addition *add) goto done; } - err = fsync(add->tables_list_lock.fd); + err = fsync(add->stack->list_lock.fd); if (err < 0) { err = REFTABLE_IO_ERROR; goto done; } - err = flock_commit(&add->tables_list_lock); + err = flock_commit(&add->stack->list_lock); if (err < 0) { err = REFTABLE_IO_ERROR; goto done; } + add->locked = 0; /* success, no more state to clean up. */ for (i = 0; i < add->new_tables_len; i++) @@ -841,10 +847,9 @@ int reftable_addition_commit(struct reftable_addition *add) return err; } -int reftable_stack_new_addition(struct reftable_addition **dest, +int reftable_stack_addition_new(struct reftable_addition **dest, struct reftable_stack *st, - const struct reftable_write_options *opts, - unsigned int flags) + const struct reftable_write_options *opts) { int err; @@ -852,7 +857,7 @@ int reftable_stack_new_addition(struct reftable_addition **dest, if (!*dest) return REFTABLE_OUT_OF_MEMORY_ERROR; - err = reftable_stack_init_addition(*dest, st, opts, flags); + err = reftable_stack_init_addition(*dest, st, opts); if (err) { reftable_free(*dest); *dest = NULL; @@ -1202,7 +1207,7 @@ static int stack_compact_range(struct reftable_stack *st, * we could check that relevant tables still exist. But for now it's * good enough to just abort. */ - err = stack_uptodate(st); + err = stack_uptodate(st, 0); if (err < 0) goto done; if (err > 0) { @@ -1321,7 +1326,7 @@ static int stack_compact_range(struct reftable_stack *st, * tables with our compacted version. If they don't, then we need to * abort. */ - err = stack_uptodate(st); + err = stack_uptodate(st, 0); if (err < 0) goto done; if (err > 0) { @@ -1840,12 +1845,7 @@ static int reftable_stack_clean_locked(struct reftable_stack *st) int reftable_stack_clean(struct reftable_stack *st) { struct reftable_addition *add = NULL; - int err = reftable_stack_new_addition(&add, st, NULL, 0); - if (err < 0) { - goto done; - } - - err = reftable_stack_reload(st); + int err = reftable_stack_addition_new(&add, st, NULL); if (err < 0) { goto done; } diff --git a/reftable/stack.h b/reftable/stack.h index f7901e6c6f5a01..52e07ad551ba69 100644 --- a/reftable/stack.h +++ b/reftable/stack.h @@ -10,7 +10,6 @@ #define STACK_H #include "system.h" -#include "reftable-writer.h" #include "reftable-stack.h" struct reftable_stack { @@ -18,6 +17,12 @@ struct reftable_stack { char *list_file; int list_fd; + /* + * Set while an addition holds the stack locked. Used by + * stack_uptodate() to skip reload checks while locked. + */ + struct reftable_flock list_lock; + char *reftable_dir; struct reftable_stack_options opts; diff --git a/setup.c b/setup.c index dfe05d9a03acb9..0d157ac2548f00 100644 --- a/setup.c +++ b/setup.c @@ -962,16 +962,54 @@ void read_gitfile_error_die(int error_code, const char *path) * cases). */ const char *read_gitfile_gently(const char *path, int *return_error_code) +{ + int error_code = 0; + const char *slash; + struct strbuf contents = STRBUF_INIT; + static struct strbuf realpath = STRBUF_INIT; + + error_code = read_gitfile_raw(&contents, path); + if (error_code) + goto cleanup_return; + + if (!is_absolute_path(contents.buf) && (slash = strrchr(path, '/'))) { + size_t pathlen = slash+1 - path; + char *dir = xstrfmt("%.*s%s", (int)pathlen, path, contents.buf); + strbuf_reset(&contents); + strbuf_addstr(&contents, dir); + free(dir); + } + if (!is_git_directory(contents.buf)) { + error_code = READ_GITFILE_ERR_NOT_A_REPO; + goto cleanup_return; + } + + strbuf_realpath(&realpath, contents.buf, 1); + +cleanup_return: + if (return_error_code) + *return_error_code = error_code; + else if (error_code) + read_gitfile_error_die(error_code, path); + + strbuf_release(&contents); + return error_code ? NULL : realpath.buf; +} + +/* + * Read the path following "gitdir: " from the .git file into strbuf. + * + * Unlike read_gitfile_gently(), this function does not resolve a + * relative path or validate it using is_git_directory(). + */ +int read_gitfile_raw(struct strbuf *contents, const char *path) { const int max_file_size = 1 << 20; /* 1MB */ int error_code = 0; char *buf = NULL; - char *dir = NULL; - const char *slash; struct stat st; int fd; ssize_t len; - static struct strbuf realpath = STRBUF_INIT; if (stat(path, &st)) { if (errno == ENOENT || errno == ENOTDIR) @@ -1014,32 +1052,11 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) error_code = READ_GITFILE_ERR_NO_PATH; goto cleanup_return; } - buf[len] = '\0'; - dir = buf + 8; - - if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) { - size_t pathlen = slash+1 - path; - dir = xstrfmt("%.*s%.*s", (int)pathlen, path, - (int)(len - 8), buf + 8); - free(buf); - buf = dir; - } - if (!is_git_directory(dir)) { - error_code = READ_GITFILE_ERR_NOT_A_REPO; - goto cleanup_return; - } - - strbuf_realpath(&realpath, dir, 1); - path = realpath.buf; + strbuf_add(contents, buf+8, len-8); cleanup_return: - if (return_error_code) - *return_error_code = error_code; - else if (error_code) - read_gitfile_error_die(error_code, path); - free(buf); - return error_code ? NULL : path; + return error_code; } static void apply_gitdir_and_environment(struct repository *repo, const char *path) diff --git a/setup.h b/setup.h index 763fd384e86c28..7394473e954df1 100644 --- a/setup.h +++ b/setup.h @@ -40,6 +40,7 @@ int is_nonbare_repository_dir(struct strbuf *path); #define READ_GITFILE_ERR_IS_A_DIR 10 void read_gitfile_error_die(int error_code, const char *path); const char *read_gitfile_gently(const char *path, int *return_error_code); +int read_gitfile_raw(struct strbuf *contents, const char *path); #define read_gitfile(path) read_gitfile_gently((path), NULL) const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); #define resolve_gitdir(path) resolve_gitdir_gently((path), NULL) diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index ba3bec078fcdcc..4ce8ffbb901fcf 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -634,12 +634,12 @@ test_expect_success '"add" does not dwim with -b' ' test_expect_success '"add" dwims with checkout.defaultRemote' ' test_when_finished rm -rf repo_upstream repo_dwim foo && setup_remote_repo repo_upstream repo_dwim && - git init repo_dwim && ( cd repo_dwim && git remote add repo_upstream2 ../repo_upstream && git fetch repo_upstream2 && - test_must_fail git worktree add ../foo foo && + test_must_fail git worktree add ../foo foo 2>error.actual && + test_grep "matched multiple (2) remote tracking branches" error.actual && git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo && git status -uno --porcelain >status.actual && test_must_be_empty status.actual @@ -679,6 +679,19 @@ test_expect_success 'git worktree add --guess-remote sets up tracking' ' test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo ) ' + +test_expect_success 'git worktree add --guess-remote fails if there are multiple matches' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git remote add repo_a2 ../repo_a && + git fetch repo_a2 && + test_must_fail git worktree add --guess-remote ../foo 2>actual && + test_grep "matched multiple (2) remote tracking branches" actual + ) +' + test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' ' test_when_finished rm -rf repo_a repo_b foo && setup_remote_repo repo_a repo_b && diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh index f5f19b3169384f..d4e53d492b833d 100755 --- a/t/t2406-worktree-repair.sh +++ b/t/t2406-worktree-repair.sh @@ -228,30 +228,60 @@ test_expect_success 'repair worktree with relative path with missing gitfile' ' test_cmp expect wt/.git ' -test_expect_success 'repair absolute worktree to use relative paths' ' - test_when_finished "rm -rf main side sidemoved" && +test_expect_success 'repair absolute to relative from side worktree' ' + test_when_finished "rm -rf main side" && test_create_repo main && test_commit -C main init && git -C main worktree add --detach ../side && - echo "../../../../sidemoved/.git" >expect-gitdir && + echo "../../../../side/.git" >expect-gitdir && echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile && - mv side sidemoved && - git -C main worktree repair --relative-paths ../sidemoved && + git -C main worktree repair --relative-paths ../side 2>main/err && + test_grep "gitdir absolute/relative path mismatch" main/err && test_cmp expect-gitdir main/.git/worktrees/side/gitdir && - test_cmp expect-gitfile sidemoved/.git + test_cmp expect-gitfile side/.git ' -test_expect_success 'repair relative worktree to use absolute paths' ' - test_when_finished "rm -rf main side sidemoved" && +test_expect_success 'repair relative to absolute from side worktree' ' + test_when_finished "rm -rf main side" && test_create_repo main && test_commit -C main init && git -C main worktree add --relative-paths --detach ../side && - echo "$(pwd)/sidemoved/.git" >expect-gitdir && + echo "$(pwd)/side/.git" >expect-gitdir && echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile && - mv side sidemoved && - git -C main worktree repair ../sidemoved && + git -C main worktree repair ../side 2>main/err && + test_grep "gitdir absolute/relative path mismatch" main/err && test_cmp expect-gitdir main/.git/worktrees/side/gitdir && - test_cmp expect-gitfile sidemoved/.git + test_cmp expect-gitfile side/.git +' + +test_expect_success 'repair absolute to relative from main worktree' ' + test_when_finished "rm -rf main side" && + test_create_repo main && + git -C main config worktree.useRelativePaths false && + test_commit -C main init && + git -C main worktree add --detach ../side && + echo "../../../../side/.git" >expect-gitdir && + echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile && + git -C main config worktree.useRelativePaths true && + git -C main worktree repair 2>main/err && + test_grep ".git file absolute/relative path mismatch" main/err && + test_cmp expect-gitdir main/.git/worktrees/side/gitdir && + test_cmp expect-gitfile side/.git +' + +test_expect_success 'repair relative to absolute from main worktree' ' + test_when_finished "rm -rf main side" && + test_create_repo main && + git -C main config worktree.useRelativePaths true && + test_commit -C main init && + git -C main worktree add --detach ../side && + echo "$(pwd)/side/.git" >expect-gitdir && + echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile && + git -C main config worktree.useRelativePaths false && + git -C main worktree repair 2>main/err && + test_grep ".git file absolute/relative path mismatch" main/err && + test_cmp expect-gitdir main/.git/worktrees/side/gitdir && + test_cmp expect-gitfile side/.git ' test_done diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh index 8bacacbac6807c..f6285640051e59 100755 --- a/t/t3702-add-edit.sh +++ b/t/t3702-add-edit.sh @@ -124,5 +124,15 @@ test_expect_success 'add -e notices editor failure' ' test_must_fail env GIT_EDITOR=false git add -e && test_expect_code 1 git diff --exit-code ' +test_expect_success 'add -e works from a subdirectory' ' + git reset --hard && + echo change >>file && + mkdir -p subdir && + ( + cd subdir && + GIT_EDITOR=cat git add -e ../file + ) && + git diff --cached | grep -q "^+change" +' test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index b7a382c88130ec..21c9ad7c5d00c4 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -478,7 +478,7 @@ EOF test_expect_success !WITH_BREAKING_CHANGES 'whatchanged needs --i-still-use-this' ' test_must_fail git whatchanged >message 2>&1 && - test_grep "nominated for removal" message + test_grep "will be removed soon" message ' test_expect_success 'log -m matches pure log' ' diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh index 2d96afd6f7ac54..aff0bce0996e30 100755 --- a/t/t5323-pack-redundant.sh +++ b/t/t5323-pack-redundant.sh @@ -47,7 +47,7 @@ shared_repo=shared.git test_expect_success 'pack-redundant needs --i-still-use-this' ' test_must_fail git pack-redundant >message 2>&1 && - test_grep "nominated for removal" message + test_grep "will be removed soon" message ' git_pack_redundant='git pack-redundant --i-still-use-this' diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c index e6c163594013d7..b6f1c6cc523e89 100644 --- a/t/unit-tests/u-reftable-stack.c +++ b/t/unit-tests/u-reftable-stack.c @@ -127,7 +127,7 @@ static void write_n_ref_tables(struct reftable_stack *st, cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1); cl_assert_equal_i(reftable_stack_add(st, - &write_test_ref, &ref, &opts, 0), 0); + &write_test_ref, &ref, &opts), 0); } } @@ -168,7 +168,7 @@ void test_reftable_stack__add_one(void) err = reftable_new_stack(&st, dir, NULL); cl_assert(!err); - err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0); + err = reftable_stack_add(st, write_test_ref, &ref, &opts); cl_assert(!err); err = reftable_stack_read_ref(st, ref.refname, &dest); @@ -231,12 +231,9 @@ void test_reftable_stack__uptodate(void) cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st1, write_test_ref, - &ref1, NULL, 0), 0); + &ref1, NULL), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2, NULL, 0), REFTABLE_OUTDATED_ERROR); - cl_assert_equal_i(reftable_stack_reload(st2), 0); - cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2, NULL, 0), 0); + &ref2, NULL), 0); reftable_stack_destroy(st1); reftable_stack_destroy(st2); clear_dir(dir); @@ -260,7 +257,7 @@ void test_reftable_stack__transaction_api(void) reftable_addition_destroy(add); - cl_assert_equal_i(reftable_stack_new_addition(&add, st, NULL, 0), 0); + cl_assert_equal_i(reftable_stack_addition_new(&add, st, NULL), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &ref), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -301,21 +298,17 @@ void test_reftable_stack__transaction_with_reload(void) cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); - cl_assert_equal_i(reftable_stack_new_addition(&add, st1, NULL, 0), 0); + cl_assert_equal_i(reftable_stack_addition_new(&add, st1, NULL), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &refs[0]), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); reftable_addition_destroy(add); /* - * The second stack is now outdated, which we should notice. We do not - * create the addition and lock the stack by default, but allow the - * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set. + * The second stack is now outdated, but it should automatically reload it + * with the newer updates. */ - cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0), - REFTABLE_OUTDATED_ERROR); - cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, - REFTABLE_STACK_NEW_ADDITION_RELOAD), 0); + cl_assert_equal_i(reftable_stack_addition_new(&add, st2, NULL), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &refs[1]), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -362,8 +355,8 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void) * we can ensure that we indeed honor this setting and have * better control over when exactly auto compaction runs. */ - cl_assert_equal_i(reftable_stack_new_addition(&add, - st, &write_opts, 0), 0); + cl_assert_equal_i(reftable_stack_addition_new(&add, + st, &write_opts), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &ref), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -400,7 +393,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, NULL, 0), 0); + &ref, NULL), 0); cl_assert_equal_i(st->merged->tables_len, 1); cl_assert_equal_i(st->stats.attempts, 0); cl_assert_equal_i(st->stats.failures, 0); @@ -418,7 +411,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) write_file_buf(table_path.buf, "", 0); ref.update_index = 2; - err = reftable_stack_add(st, write_test_ref, &ref, NULL, 0); + err = reftable_stack_add(st, write_test_ref, &ref, NULL); cl_assert(!err); cl_assert_equal_i(st->merged->tables_len, 2); cl_assert_equal_i(st->stats.attempts, 1); @@ -453,9 +446,9 @@ void test_reftable_stack__update_index_check(void) cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref1, NULL, 0), 0); + &ref1, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref2, NULL, 0), REFTABLE_API_ERROR); + &ref2, NULL), REFTABLE_API_ERROR); reftable_stack_destroy(st); clear_dir(dir); } @@ -469,7 +462,7 @@ void test_reftable_stack__lock_failure(void) cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) cl_assert_equal_i(reftable_stack_add(st, write_error, - &i, NULL, 0), i); + &i, NULL), i); reftable_stack_destroy(st); clear_dir(dir); @@ -513,7 +506,7 @@ void test_reftable_stack__add(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], &opts, 0), 0); + &refs[i], &opts), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -521,7 +514,7 @@ void test_reftable_stack__add(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, &opts, 0), 0); + &arg, &opts), 0); } cl_assert_equal_i(reftable_stack_compact_all(st, &opts, NULL), 0); @@ -604,7 +597,7 @@ void test_reftable_stack__iterator(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], NULL, 0), 0); + &refs[i], NULL), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -613,7 +606,7 @@ void test_reftable_stack__iterator(void) }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, NULL, 0), 0); + &arg, NULL), 0); } reftable_stack_init_ref_iterator(st, &it); @@ -685,11 +678,11 @@ void test_reftable_stack__log_normalize(void) input.value.update.message = (char *) "one\ntwo"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, NULL, 0), REFTABLE_API_ERROR); + &arg, NULL), REFTABLE_API_ERROR); input.value.update.message = (char *) "one"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, NULL, 0), 0); + &arg, NULL), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "one\n"); @@ -697,7 +690,7 @@ void test_reftable_stack__log_normalize(void) input.value.update.message = (char *) "two\n"; arg.update_index = 2; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, NULL, 0), 0); + &arg, NULL), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "two\n"); @@ -747,7 +740,7 @@ void test_reftable_stack__tombstone(void) } for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], NULL, 0), 0); + &refs[i], NULL), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -755,7 +748,7 @@ void test_reftable_stack__tombstone(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, NULL, 0), 0); + &arg, NULL), 0); } cl_assert_equal_i(reftable_stack_read_ref(st, "branch", @@ -801,7 +794,7 @@ void test_reftable_stack__hash_id(void) cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, NULL, 0), 0); + &ref, NULL), 0); /* can't read it with the wrong hash ID. */ cl_assert_equal_i(reftable_new_stack(&st32, dir, @@ -869,7 +862,7 @@ void test_reftable_stack__reflog_expire(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, NULL, 0), 0); + &arg, NULL), 0); } cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0); @@ -908,7 +901,7 @@ void test_reftable_stack__empty_add(void) cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_nothing, - NULL, NULL, 0), 0); + NULL, NULL), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); clear_dir(dir); reftable_stack_destroy(st); @@ -947,7 +940,7 @@ void test_reftable_stack__auto_compaction(void) }; snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0); + err = reftable_stack_add(st, write_test_ref, &ref, &opts); cl_assert(!err); err = reftable_stack_auto_compact(st, &opts); @@ -983,7 +976,7 @@ void test_reftable_stack__auto_compaction_factor(void) }; xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, &write_test_ref, &ref, &opts, 0); + err = reftable_stack_add(st, &write_test_ref, &ref, &opts); cl_assert(!err); cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5)); @@ -1064,7 +1057,7 @@ void test_reftable_stack__add_performs_auto_compaction(void) ref.refname = buf; cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, &write_opts, 0), 0); + &ref, &write_opts), 0); /* * The stack length should grow continuously for all runs where @@ -1303,7 +1296,7 @@ void test_reftable_stack__invalid_limit_updates(void) reftable_addition_destroy(add); - cl_assert_equal_i(reftable_stack_new_addition(&add, st, &opts, 0), 0); + cl_assert_equal_i(reftable_stack_addition_new(&add, st, &opts), 0); /* * write_limits_after_ref also updates the update indexes after adding @@ -1317,3 +1310,31 @@ void test_reftable_stack__invalid_limit_updates(void) reftable_stack_destroy(st); clear_dir(dir); } + +void test_reftable_stack__two_additions(void) +{ + struct reftable_stack *st = NULL; + char *dir = get_tmp_dir(__LINE__); + struct reftable_addition *add1 = NULL; + struct reftable_addition *add2 = NULL; + + struct reftable_ref_record ref = { + .refname = (char *) "HEAD", + .update_index = 1, + .value_type = REFTABLE_REF_SYMREF, + .value.symref = (char *) "master", + }; + + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); + + cl_assert_equal_i(reftable_stack_addition_new(&add1, st, NULL), 0); + cl_assert_equal_i(reftable_stack_addition_new(&add2, st, NULL), REFTABLE_LOCK_ERROR); + + cl_assert_equal_i(reftable_addition_add(add1, write_test_ref, &ref), 0); + + cl_assert_equal_i(reftable_addition_commit(add1), 0); + + reftable_addition_destroy(add1); + reftable_stack_destroy(st); + clear_dir(dir); +} diff --git a/usage.c b/usage.c index 3f0118ab2a0411..4449046c1a7fb4 100644 --- a/usage.c +++ b/usage.c @@ -386,21 +386,19 @@ NORETURN void you_still_use_that(const char *command_name, const char *hint) STRBUF_ENCODE_SLASH); fprintf(stderr, - _("'%s' is nominated for removal.\n"), command_name); + _("'%s' will be removed soon.\n"), command_name); if (hint) fputs(hint, stderr); fprintf(stderr, - _("If you still use this command, here's what you can do:\n" + _("If you need a replacement:\n" "\n" - "- read https://git-scm.com/docs/BreakingChanges.html\n" - "- check if anyone has discussed this on the mailing\n" - " list and if they came up with something that can\n" - " help you: https://lore.kernel.org/git/?q=%s\n" - "- send an email to to let us\n" - " know that you still use this command and were unable\n" - " to determine a suitable replacement\n" + "- Read https://git-scm.com/docs/BreakingChanges.html.\n\n" + "- Check what others on the mailing list suggest as a replacement:\n" + " https://lore.kernel.org/git/?q=%s\n\n" + "- Send an email to asking for help, only if\n" + " suggestions by others do not work for you.\n" "\n"), percent_encoded.buf); strbuf_release(&percent_encoded); diff --git a/worktree.c b/worktree.c index cbf95328a331ad..8cb8637b189ab8 100644 --- a/worktree.c +++ b/worktree.c @@ -649,7 +649,8 @@ static void repair_gitfile(struct worktree *wt, struct strbuf gitdir = STRBUF_INIT; struct strbuf repo = STRBUF_INIT; struct strbuf backlink = STRBUF_INIT; - char *dotgit_contents = NULL; + struct strbuf contents = STRBUF_INIT; + const char *dotgit_contents = NULL; const char *repair = NULL; char *path = NULL; int err; @@ -667,7 +668,9 @@ static void repair_gitfile(struct worktree *wt, strbuf_realpath(&repo, path, 1); strbuf_addf(&dotgit, "%s/.git", wt->path); strbuf_addf(&gitdir, "%s/gitdir", repo.buf); - dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); + err = read_gitfile_raw(&contents, dotgit.buf); + if (!err) + dotgit_contents = contents.buf; if (dotgit_contents) { if (is_absolute_path(dotgit_contents)) { @@ -681,7 +684,7 @@ static void repair_gitfile(struct worktree *wt, if (err == READ_GITFILE_ERR_NOT_A_FILE || err == READ_GITFILE_ERR_IS_A_DIR) fn(1, wt->path, _(".git is not a file"), cb_data); - else if (err) + else if (err || !is_git_directory(backlink.buf)) repair = _(".git file broken"); else if (fspathcmp(backlink.buf, repo.buf)) repair = _(".git file incorrect"); @@ -695,12 +698,12 @@ static void repair_gitfile(struct worktree *wt, } done: - free(dotgit_contents); free(path); strbuf_release(&repo); strbuf_release(&dotgit); strbuf_release(&gitdir); strbuf_release(&backlink); + strbuf_release(&contents); } static void repair_noop(int iserr UNUSED, @@ -857,14 +860,7 @@ void repair_worktree_at_path(struct repository *repo, strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0); dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); if (dotgit_contents) { - if (is_absolute_path(dotgit_contents)) { - strbuf_addstr(&backlink, dotgit_contents); - } else { - strbuf_addbuf(&backlink, &dotgit); - strbuf_strip_suffix(&backlink, ".git"); - strbuf_addstr(&backlink, dotgit_contents); - strbuf_realpath_forgiving(&backlink, backlink.buf, 0); - } + strbuf_addstr(&backlink, dotgit_contents); } else if (err == READ_GITFILE_ERR_NOT_A_FILE || err == READ_GITFILE_ERR_IS_A_DIR) { fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);