From: Junio C Hamano Date: Tue, 13 Nov 2018 13:37:26 +0000 (+0900) Subject: Merge branch 'nd/per-worktree-ref-iteration' X-Git-Tag: v2.20.0-rc0~35 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/e146cc97be4c054c60d38e9f4edcdc33205bf563?ds=inline;hp=-c Merge branch 'nd/per-worktree-ref-iteration' The code to traverse objects for reachability, used to decide what objects are unreferenced and expendable, have been taught to also consider per-worktree refs of other worktrees as starting points to prevent data loss. * nd/per-worktree-ref-iteration: git-worktree.txt: correct linkgit command name reflog expire: cover reflog from all worktrees fsck: check HEAD and reflog from other worktrees fsck: move fsck_head_link() to get_default_heads() to avoid some globals revision.c: better error reporting on ref from different worktrees revision.c: correct a parameter name refs: new ref types to make per-worktree refs visible to all worktrees Add a place for (not) sharing stuff between worktrees refs.c: indent with tabs, not spaces --- e146cc97be4c054c60d38e9f4edcdc33205bf563 diff --combined Documentation/git-worktree.txt index 5e986ce8aa,69d55f1350..cb86318f3e --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@@ -204,36 -204,35 +204,65 @@@ working trees, it can be used to identi you only have two working trees, at "/abc/def/ghi" and "/abc/def/ggg", then "ghi" or "def/ghi" is enough to point to the former working tree. + REFS + ---- + In multiple working trees, some refs may be shared between all working + trees, some refs are local. One example is HEAD is different for all + working trees. This section is about the sharing rules and how to access + refs of one working tree from another. + + In general, all pseudo refs are per working tree and all refs starting + with "refs/" are shared. Pseudo refs are ones like HEAD which are + directly under GIT_DIR instead of inside GIT_DIR/refs. There are one + exception to this: refs inside refs/bisect and refs/worktree is not + shared. + + Refs that are per working tree can still be accessed from another + working tree via two special paths, main-worktree and worktrees. The + former gives access to per-worktree refs of the main working tree, + while the latter to all linked working trees. + + For example, main-worktree/HEAD or main-worktree/refs/bisect/good + resolve to the same value as the main working tree's HEAD and + refs/bisect/good respectively. Similarly, worktrees/foo/HEAD or + worktrees/bar/refs/bisect/bad are the same as + GIT_COMMON_DIR/worktrees/foo/HEAD and + GIT_COMMON_DIR/worktrees/bar/refs/bisect/bad. + + To access refs, it's best not to look inside GIT_DIR directly. Instead + use commands such as linkgit:git-rev-parse[1] or linkgit:git-update-ref[1] + which will handle refs correctly. + +CONFIGURATION FILE +------------------ +By default, the repository "config" file is shared across all working +trees. If the config variables `core.bare` or `core.worktree` are +already present in the config file, they will be applied to the main +working trees only. + +In order to have configuration specific to working trees, you can turn +on "worktreeConfig" extension, e.g.: + +------------ +$ git config extensions.worktreeConfig true +------------ + +In this mode, specific configuration stays in the path pointed by `git +rev-parse --git-path config.worktree`. You can add or update +configuration in this file with `git config --worktree`. Older Git +versions will refuse to access repositories with this extension. + +Note that in this file, the exception for `core.bare` and `core.worktree` +is gone. If you have them in $GIT_DIR/config before, you must move +them to the `config.worktree` of the main working tree. You may also +take this opportunity to review and move other configuration that you +do not want to share to all working trees: + + - `core.worktree` and `core.bare` should never be shared + + - `core.sparseCheckout` is recommended per working tree, unless you + are sure you always use sparse checkout for all working trees. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's @@@ -258,7 -257,8 +287,8 @@@ linked working tree `git rev-parse --gi `/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git rev-parse --git-path refs/heads/master` uses $GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`, - since refs are shared across all working trees. + since refs are shared across all working trees, except refs/bisect and + refs/worktree. See linkgit:gitrepository-layout[5] for more information. The rule of thumb is do not make any assumption about whether a path belongs to @@@ -283,9 -283,6 +313,9 @@@ to `/path/main/.git/worktrees/test-next `test-next` entry from being pruned. See linkgit:gitrepository-layout[5] for details. +When extensions.worktreeConfig is enabled, the config file +`.git/worktrees//config.worktree` is read after `.git/config` is. + LIST OUTPUT FORMAT ------------------ The worktree list command has two output formats. The default format shows the @@@ -303,8 -300,8 +333,8 @@@ Porcelain Forma The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like 'bare' and 'detached') are listed as a label only, and are only present if and only -if the value is true. An empty line indicates the end of a worktree. For -example: +if the value is true. The first attribute of a worktree is always `worktree`, +an empty line indicates the end of the record. For example: ------------ $ git worktree list --porcelain diff --combined Documentation/gitrepository-layout.txt index 36fcca8087,89b616e049..d501af9d77 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@@ -95,8 -95,10 +95,10 @@@ refs: References are stored in subdirectories of this directory. The 'git prune' command knows to preserve objects reachable from refs found in this directory and - its subdirectories. This directory is ignored if $GIT_COMMON_DIR - is set and "$GIT_COMMON_DIR/refs" will be used instead. + its subdirectories. + This directory is ignored (except refs/bisect and + refs/worktree) if $GIT_COMMON_DIR is set and + "$GIT_COMMON_DIR/refs" will be used instead. refs/heads/`name`:: records tip-of-the-tree commit objects of branch `name` @@@ -143,11 -145,6 +145,11 @@@ config: if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be used instead. +config.worktree:: + Working directory specific configuration file for the main + working directory in multiple working directory setup (see + linkgit:git-worktree[1]). + branches:: A slightly deprecated way to store shorthands to be used to specify a URL to 'git fetch', 'git pull' and 'git push'. @@@ -170,6 -167,11 +172,11 @@@ hooks: each hook. This directory is ignored if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/hooks" will be used instead. + common:: + When multiple working trees are used, most of files in + $GIT_DIR are per-worktree with a few known exceptions. All + files under 'common' however will be shared between all + working trees. index:: The current index file for the repository. It is @@@ -280,9 -282,6 +287,9 @@@ worktrees//locked: or manually by `git worktree prune`. The file may contain a string explaining why the repository is locked. +worktrees//config.worktree:: + Working directory specific configuration file. + SEE ALSO -------- linkgit:git-init[1], diff --combined builtin/fsck.c index 06eb421720,71492c158d..3c3e0f06e7 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@@ -19,6 -19,7 +19,7 @@@ #include "packfile.h" #include "object-store.h" #include "run-command.h" + #include "worktree.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@@ -36,8 -37,6 +37,6 @@@ static int check_strict static int keep_cache_objects; static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT; static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT; - static struct object_id head_oid; - static const char *head_points_at; static int errors_found; static int write_lost_and_found; static int verbose; @@@ -446,7 -445,11 +445,11 @@@ static int fsck_handle_reflog_ent(struc static int fsck_handle_reflog(const char *logname, const struct object_id *oid, int flag, void *cb_data) { - for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname); + struct strbuf refname = STRBUF_INIT; + + strbuf_worktree_ref(cb_data, &refname, logname); + for_each_reflog_ent(refname.buf, fsck_handle_reflog_ent, refname.buf); + strbuf_release(&refname); return 0; } @@@ -484,13 -487,34 +487,34 @@@ static int fsck_handle_ref(const char * return 0; } + static int fsck_head_link(const char *head_ref_name, + const char **head_points_at, + struct object_id *head_oid); + static void get_default_heads(void) { - if (head_points_at && !is_null_oid(&head_oid)) - fsck_handle_ref("HEAD", &head_oid, 0, NULL); + struct worktree **worktrees, **p; + const char *head_points_at; + struct object_id head_oid; + for_each_rawref(fsck_handle_ref, NULL); - if (include_reflogs) - for_each_reflog(fsck_handle_reflog, NULL); + + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + struct strbuf ref = STRBUF_INIT; + + strbuf_worktree_ref(wt, &ref, "HEAD"); + fsck_head_link(ref.buf, &head_points_at, &head_oid); + if (head_points_at && !is_null_oid(&head_oid)) + fsck_handle_ref(ref.buf, &head_oid, 0, NULL); + strbuf_release(&ref); + + if (include_reflogs) + refs_for_each_reflog(get_worktree_ref_store(wt), + fsck_handle_reflog, wt); + } + free_worktrees(worktrees); /* * Not having any default heads isn't really fatal, but @@@ -579,33 -603,36 +603,36 @@@ static void fsck_object_dir(const char stop_progress(&progress); } - static int fsck_head_link(void) + static int fsck_head_link(const char *head_ref_name, + const char **head_points_at, + struct object_id *head_oid) { int null_is_error = 0; if (verbose) - fprintf(stderr, "Checking HEAD link\n"); + fprintf(stderr, "Checking %s link\n", head_ref_name); - head_points_at = resolve_ref_unsafe("HEAD", 0, &head_oid, NULL); - if (!head_points_at) { + *head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL); + if (!*head_points_at) { errors_found |= ERROR_REFS; - return error("Invalid HEAD"); + return error("Invalid %s", head_ref_name); } - if (!strcmp(head_points_at, "HEAD")) + if (!strcmp(*head_points_at, head_ref_name)) /* detached HEAD */ null_is_error = 1; - else if (!starts_with(head_points_at, "refs/heads/")) { + else if (!starts_with(*head_points_at, "refs/heads/")) { errors_found |= ERROR_REFS; - return error("HEAD points to something strange (%s)", - head_points_at); + return error("%s points to something strange (%s)", + head_ref_name, *head_points_at); } - if (is_null_oid(&head_oid)) { + if (is_null_oid(head_oid)) { if (null_is_error) { errors_found |= ERROR_REFS; - return error("HEAD: detached HEAD points at nothing"); + return error("%s: detached HEAD points at nothing", + head_ref_name); } - fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", - head_points_at + 11); + fprintf(stderr, "notice: %s points to an unborn branch (%s)\n", + head_ref_name, *head_points_at + 11); } return 0; } @@@ -720,7 -747,6 +747,6 @@@ int cmd_fsck(int argc, const char **arg git_config(fsck_config, NULL); - fsck_head_link(); if (connectivity_only) { for_each_loose_object(mark_loose_for_connectivity, NULL, 0); for_each_packed_object(mark_packed_for_connectivity, NULL, 0); @@@ -848,23 -874,5 +874,23 @@@ } } + if (!git_config_get_bool("core.multipackindex", &i) && i) { + struct child_process midx_verify = CHILD_PROCESS_INIT; + const char *midx_argv[] = { "multi-pack-index", "verify", NULL, NULL, NULL }; + + midx_verify.argv = midx_argv; + midx_verify.git_cmd = 1; + if (run_command(&midx_verify)) + errors_found |= ERROR_COMMIT_GRAPH; + + prepare_alt_odb(the_repository); + for (alt = the_repository->objects->alt_odb_list; alt; alt = alt->next) { + midx_argv[2] = "--object-dir"; + midx_argv[3] = alt->path; + if (run_command(&midx_verify)) + errors_found |= ERROR_COMMIT_GRAPH; + } + } + return errors_found; } diff --combined builtin/reflog.c index b5941c1ff3,ba36d086e9..7a85e4b164 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@@ -10,6 -10,7 +10,7 @@@ #include "diff.h" #include "revision.h" #include "reachable.h" + #include "worktree.h" /* NEEDSWORK: switch to using parse_options */ static const char reflog_expire_usage[] = @@@ -52,6 -53,7 +53,7 @@@ struct collect_reflog_cb struct collected_reflog **e; int alloc; int nr; + struct worktree *wt; }; /* Remember to update object flag allocation in object.h */ @@@ -330,13 -332,27 +332,27 @@@ static int push_tip_to_list(const char return 0; } + static int is_head(const char *refname) + { + switch (ref_type(refname)) { + case REF_TYPE_OTHER_PSEUDOREF: + case REF_TYPE_MAIN_PSEUDOREF: + if (parse_worktree_ref(refname, NULL, NULL, &refname)) + BUG("not a worktree ref: %s", refname); + break; + default: + break; + } + return !strcmp(refname, "HEAD"); + } + static void reflog_expiry_prepare(const char *refname, const struct object_id *oid, void *cb_data) { struct expire_reflog_policy_cb *cb = cb_data; - if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) { + if (!cb->cmd.expire_unreachable || is_head(refname)) { cb->tip_commit = NULL; cb->unreachable_expire_kind = UE_HEAD; } else { @@@ -388,8 -404,19 +404,19 @@@ static int collect_reflog(const char *r { struct collected_reflog *e; struct collect_reflog_cb *cb = cb_data; + struct strbuf newref = STRBUF_INIT; + + /* + * Avoid collecting the same shared ref multiple times because + * they are available via all worktrees. + */ + if (!cb->wt->is_current && ref_type(ref) == REF_TYPE_NORMAL) + return 0; + + strbuf_worktree_ref(cb->wt, &newref, ref); + FLEX_ALLOC_STR(e, reflog, newref.buf); + strbuf_release(&newref); - FLEX_ALLOC_STR(e, reflog, ref); oidcpy(&e->oid, oid); ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); cb->e[cb->nr++] = e; @@@ -512,7 -539,7 +539,7 @@@ static int cmd_reflog_expire(int argc, { struct expire_reflog_policy_cb cb; timestamp_t now = time(NULL); - int i, status, do_all; + int i, status, do_all, all_worktrees = 1; int explicit_expiry = 0; unsigned int flags = 0; @@@ -549,6 -576,8 +576,8 @@@ flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--all")) do_all = 1; + else if (!strcmp(arg, "--single-worktree")) + all_worktrees = 0; else if (!strcmp(arg, "--verbose")) flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { @@@ -567,7 -596,7 +596,7 @@@ * from reflog if the repository was pruned with older git. */ if (cb.cmd.stalefix) { - init_revisions(&cb.cmd.revs, prefix); + repo_init_revisions(the_repository, &cb.cmd.revs, prefix); if (flags & EXPIRE_REFLOGS_VERBOSE) printf("Marking reachable objects..."); mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); @@@ -577,10 -606,19 +606,19 @@@ if (do_all) { struct collect_reflog_cb collected; + struct worktree **worktrees, **p; int i; memset(&collected, 0, sizeof(collected)); - for_each_reflog(collect_reflog, &collected); + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + if (!all_worktrees && !(*p)->is_current) + continue; + collected.wt = *p; + refs_for_each_reflog(get_worktree_ref_store(*p), + collect_reflog, &collected); + } + free_worktrees(worktrees); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); diff --combined path.c index ba06ec5b2d,bf4bb02a27..dc3294c71e --- a/path.c +++ b/path.c @@@ -108,6 -108,7 +108,7 @@@ struct common_dir static struct common_dir common_list[] = { { 0, 1, 0, "branches" }, + { 0, 1, 0, "common" }, { 0, 1, 0, "hooks" }, { 0, 1, 0, "info" }, { 0, 0, 1, "info/sparse-checkout" }, @@@ -118,6 -119,7 +119,7 @@@ { 0, 1, 0, "objects" }, { 0, 1, 0, "refs" }, { 0, 1, 1, "refs/bisect" }, + { 0, 1, 1, "refs/worktree" }, { 0, 1, 0, "remotes" }, { 0, 1, 0, "worktrees" }, { 0, 1, 0, "rr-cache" }, @@@ -1369,7 -1371,7 +1371,7 @@@ only_spaces_and_periods saw_tilde = 1; } else if (i >= 6) return 0; - else if (name[i] < 0) { + else if (name[i] & 0x80) { /* * We know our needles contain only ASCII, so we clamp * here to make the results of tolower() sane. diff --combined refs.c index bbcac921b6,2378b2e7fc..17e4307f31 --- a/refs.c +++ b/refs.c @@@ -624,6 -624,7 +624,7 @@@ int dwim_log(const char *str, int len, static int is_per_worktree_ref(const char *refname) { return !strcmp(refname, "HEAD") || + starts_with(refname, "refs/worktree/") || starts_with(refname, "refs/bisect/") || starts_with(refname, "refs/rewritten/"); } @@@ -640,13 -641,34 +641,34 @@@ static int is_pseudoref_syntax(const ch return 1; } + static int is_main_pseudoref_syntax(const char *refname) + { + return skip_prefix(refname, "main-worktree/", &refname) && + *refname && + is_pseudoref_syntax(refname); + } + + static int is_other_pseudoref_syntax(const char *refname) + { + if (!skip_prefix(refname, "worktrees/", &refname)) + return 0; + refname = strchr(refname, '/'); + if (!refname || !refname[1]) + return 0; + return is_pseudoref_syntax(refname + 1); + } + enum ref_type ref_type(const char *refname) { if (is_per_worktree_ref(refname)) return REF_TYPE_PER_WORKTREE; if (is_pseudoref_syntax(refname)) return REF_TYPE_PSEUDOREF; - return REF_TYPE_NORMAL; + if (is_main_pseudoref_syntax(refname)) + return REF_TYPE_MAIN_PSEUDOREF; + if (is_other_pseudoref_syntax(refname)) + return REF_TYPE_OTHER_PSEUDOREF; + return REF_TYPE_NORMAL; } long get_files_ref_lock_timeout_ms(void) @@@ -1394,50 -1416,17 +1416,50 @@@ struct ref_iterator *refs_ref_iterator_ * non-zero value, stop the iteration and return that value; * otherwise, return 0. */ +static int do_for_each_repo_ref(struct repository *r, const char *prefix, + each_repo_ref_fn fn, int trim, int flags, + void *cb_data) +{ + struct ref_iterator *iter; + struct ref_store *refs = get_main_ref_store(r); + + if (!refs) + return 0; + + iter = refs_ref_iterator_begin(refs, prefix, trim, flags); + + return do_for_each_repo_ref_iterator(r, iter, fn, cb_data); +} + +struct do_for_each_ref_help { + each_ref_fn *fn; + void *cb_data; +}; + +static int do_for_each_ref_helper(struct repository *r, + const char *refname, + const struct object_id *oid, + int flags, + void *cb_data) +{ + struct do_for_each_ref_help *hp = cb_data; + + return hp->fn(refname, oid, flags, hp->cb_data); +} + static int do_for_each_ref(struct ref_store *refs, const char *prefix, each_ref_fn fn, int trim, int flags, void *cb_data) { struct ref_iterator *iter; + struct do_for_each_ref_help hp = { fn, cb_data }; if (!refs) return 0; iter = refs_ref_iterator_begin(refs, prefix, trim, flags); - return do_for_each_ref_iterator(iter, fn, cb_data); + return do_for_each_repo_ref_iterator(the_repository, iter, + do_for_each_ref_helper, &hp); } int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) @@@ -1482,11 -1471,12 +1504,11 @@@ int refs_for_each_fullref_in(struct ref return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data); } -int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data) +int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data) { - return do_for_each_ref(get_main_ref_store(r), - git_replace_ref_base, fn, - strlen(git_replace_ref_base), - DO_FOR_EACH_INCLUDE_BROKEN, cb_data); + return do_for_each_repo_ref(r, git_replace_ref_base, fn, + strlen(git_replace_ref_base), + DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } int for_each_namespaced_ref(each_ref_fn fn, void *cb_data) @@@ -2065,12 -2055,10 +2087,12 @@@ cleanup int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data) { struct ref_iterator *iter; + struct do_for_each_ref_help hp = { fn, cb_data }; iter = refs->be->reflog_iterator_begin(refs); - return do_for_each_ref_iterator(iter, fn, cb_data); + return do_for_each_repo_ref_iterator(the_repository, iter, + do_for_each_ref_helper, &hp); } int for_each_reflog(each_ref_fn fn, void *cb_data) diff --combined refs.h index 6cc0397679,9b53dbeae8..308fa1f03b --- a/refs.h +++ b/refs.h @@@ -276,16 -276,6 +276,16 @@@ struct ref_transaction typedef int each_ref_fn(const char *refname, const struct object_id *oid, int flags, void *cb_data); +/* + * The same as each_ref_fn, but also with a repository argument that + * contains the repository associated with the callback. + */ +typedef int each_repo_ref_fn(struct repository *r, + const char *refname, + const struct object_id *oid, + int flags, + void *cb_data); + /* * The following functions invoke the specified callback function for * each reference indicated. If the function ever returns a nonzero @@@ -319,7 -309,7 +319,7 @@@ int for_each_fullref_in(const char *pre int for_each_tag_ref(each_ref_fn fn, void *cb_data); int for_each_branch_ref(each_ref_fn fn, void *cb_data); int for_each_remote_ref(each_ref_fn fn, void *cb_data); -int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data); +int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data); int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data); int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data); @@@ -714,9 -704,11 +714,11 @@@ int parse_hide_refs_config(const char * int ref_is_hidden(const char *, const char *); enum ref_type { - REF_TYPE_PER_WORKTREE, - REF_TYPE_PSEUDOREF, - REF_TYPE_NORMAL, + REF_TYPE_PER_WORKTREE, /* refs inside refs/ but not shared */ + REF_TYPE_PSEUDOREF, /* refs outside refs/ in current worktree */ + REF_TYPE_MAIN_PSEUDOREF, /* pseudo refs from the main worktree */ + REF_TYPE_OTHER_PSEUDOREF, /* pseudo refs from other worktrees */ + REF_TYPE_NORMAL, /* normal/shared refs inside refs/ */ }; enum ref_type ref_type(const char *refname); diff --combined revision.c index 28fb2a70cd,8ce660e3b1..59a3c22b8a --- a/revision.c +++ b/revision.c @@@ -52,8 -52,7 +52,8 @@@ static void mark_blob_uninteresting(str blob->object.flags |= UNINTERESTING; } -static void mark_tree_contents_uninteresting(struct tree *tree) +static void mark_tree_contents_uninteresting(struct repository *r, + struct tree *tree) { struct tree_desc desc; struct name_entry entry; @@@ -65,10 -64,10 +65,10 @@@ while (tree_entry(&desc, &entry)) { switch (object_type(entry.mode)) { case OBJ_TREE: - mark_tree_uninteresting(lookup_tree(the_repository, entry.oid)); + mark_tree_uninteresting(r, lookup_tree(r, entry.oid)); break; case OBJ_BLOB: - mark_blob_uninteresting(lookup_blob(the_repository, entry.oid)); + mark_blob_uninteresting(lookup_blob(r, entry.oid)); break; default: /* Subproject commit - not in this repository */ @@@ -83,7 -82,7 +83,7 @@@ free_tree_buffer(tree); } -void mark_tree_uninteresting(struct tree *tree) +void mark_tree_uninteresting(struct repository *r, struct tree *tree) { struct object *obj; @@@ -94,7 -93,7 +94,7 @@@ if (obj->flags & UNINTERESTING) return; obj->flags |= UNINTERESTING; - mark_tree_contents_uninteresting(tree); + mark_tree_contents_uninteresting(r, tree); } struct commit_stack { @@@ -177,6 -176,7 +177,6 @@@ static void add_pending_object_with_pat strbuf_release(&buf); return; /* do not add the commit itself */ } - obj->flags |= USER_GIVEN; add_object_array_with_path(obj, name, &revs->pending, mode, path); } @@@ -199,7 -199,7 +199,7 @@@ void add_head_to_pending(struct rev_inf struct object *obj; if (get_oid("HEAD", &oid)) return; - obj = parse_object(the_repository, &oid); + obj = parse_object(revs->repo, &oid); if (!obj) return; add_pending_object(revs, obj, "HEAD"); @@@ -211,7 -211,7 +211,7 @@@ static struct object *get_reference(str { struct object *object; - object = parse_object(the_repository, oid); + object = parse_object(revs->repo, oid); if (!object) { if (revs->ignore_missing) return object; @@@ -248,7 -248,7 +248,7 @@@ static struct commit *handle_commit(str add_pending_object(revs, object, tag->tag); if (!tag->tagged) die("bad tag"); - object = parse_object(the_repository, &tag->tagged->oid); + object = parse_object(revs->repo, &tag->tagged->oid); if (!object) { if (revs->ignore_missing_links || (flags & UNINTERESTING)) return NULL; @@@ -298,7 -298,7 +298,7 @@@ if (!revs->tree_objects) return NULL; if (flags & UNINTERESTING) { - mark_tree_contents_uninteresting(tree); + mark_tree_contents_uninteresting(revs->repo, tree); return NULL; } add_pending_object_with_path(revs, object, name, mode, path); @@@ -878,7 -878,7 +878,7 @@@ static void cherry_pick_list(struct com return; left_first = left_count < right_count; - init_patch_ids(&ids); + init_patch_ids(revs->repo, &ids); ids.diffopts.pathspec = revs->diffopt.pathspec; /* Compute patch-ids for one side */ @@@ -1177,7 -1177,7 +1177,7 @@@ struct all_refs_cb int warned_bad_reflog; struct rev_info *all_revs; const char *name_for_errormsg; - struct ref_store *refs; + struct worktree *wt; }; int ref_excluded(struct string_list *ref_excludes, const char *path) @@@ -1214,7 -1214,7 +1214,7 @@@ static void init_all_refs_cb(struct all cb->all_revs = revs; cb->all_flags = flags; revs->rev_input_given = 1; - cb->refs = NULL; + cb->wt = NULL; } void clear_ref_exclusion(struct string_list **ref_excludes_p) @@@ -1254,7 -1254,7 +1254,7 @@@ static void handle_one_reflog_commit(st { struct all_refs_cb *cb = cb_data; if (!is_null_oid(oid)) { - struct object *o = parse_object(the_repository, oid); + struct object *o = parse_object(cb->all_revs->repo, oid); if (o) { o->flags |= cb->all_flags; /* ??? CMDLINEFLAGS ??? */ @@@ -1277,14 -1277,20 +1277,20 @@@ static int handle_one_reflog_ent(struc return 0; } - static int handle_one_reflog(const char *path, const struct object_id *oid, + static int handle_one_reflog(const char *refname_in_wt, + const struct object_id *oid, int flag, void *cb_data) { struct all_refs_cb *cb = cb_data; + struct strbuf refname = STRBUF_INIT; + cb->warned_bad_reflog = 0; - cb->name_for_errormsg = path; - refs_for_each_reflog_ent(cb->refs, path, + strbuf_worktree_ref(cb->wt, &refname, refname_in_wt); + cb->name_for_errormsg = refname.buf; + refs_for_each_reflog_ent(get_main_ref_store(the_repository), + refname.buf, handle_one_reflog_ent, cb_data); + strbuf_release(&refname); return 0; } @@@ -1299,8 -1305,8 +1305,8 @@@ static void add_other_reflogs_to_pendin if (wt->is_current) continue; - cb->refs = get_worktree_ref_store(wt); - refs_for_each_reflog(cb->refs, + cb->wt = wt; + refs_for_each_reflog(get_worktree_ref_store(wt), handle_one_reflog, cb); } @@@ -1313,7 -1319,7 +1319,7 @@@ void add_reflogs_to_pending(struct rev_ cb.all_revs = revs; cb.all_flags = flags; - cb.refs = get_main_ref_store(revs->repo); + cb.wt = NULL; for_each_reflog(handle_one_reflog, &cb); if (!revs->single_worktree) @@@ -1327,7 -1333,7 +1333,7 @@@ static void add_cache_tree(struct cache int i; if (it->entry_count >= 0) { - struct tree *tree = lookup_tree(the_repository, &it->oid); + struct tree *tree = lookup_tree(revs->repo, &it->oid); add_pending_object_with_path(revs, &tree->object, "", 040000, path->buf); } @@@ -1353,7 -1359,7 +1359,7 @@@ static void do_add_index_objects_to_pen if (S_ISGITLINK(ce->ce_mode)) continue; - blob = lookup_blob(the_repository, &ce->oid); + blob = lookup_blob(revs->repo, &ce->oid); if (!blob) die("unable to add index blob to traversal"); add_pending_object_with_path(revs, &blob->object, "", @@@ -1371,8 -1377,8 +1377,8 @@@ void add_index_objects_to_pending(struc { struct worktree **worktrees, **p; - read_cache(); - do_add_index_objects_to_pending(revs, &the_index); + read_index(revs->repo->index); + do_add_index_objects_to_pending(revs, revs->repo->index); if (revs->single_worktree) return; @@@ -1440,13 -1446,10 +1446,13 @@@ static int add_parents_only(struct rev_ return 1; } -void init_revisions(struct rev_info *revs, const char *prefix) +void repo_init_revisions(struct repository *r, + struct rev_info *revs, + const char *prefix) { memset(revs, 0, sizeof(*revs)); + revs->repo = r; revs->abbrev = DEFAULT_ABBREV; revs->ignore_merges = 1; revs->simplify_history = 1; @@@ -1468,11 -1471,11 +1474,11 @@@ revs->commit_format = CMIT_FMT_DEFAULT; revs->expand_tabs_in_log_default = 8; - init_grep_defaults(); - grep_init(&revs->grep_filter, prefix); + init_grep_defaults(revs->repo); + grep_init(&revs->grep_filter, revs->repo, prefix); revs->grep_filter.status_only = 1; - diff_setup(&revs->diffopt); + repo_diff_setup(revs->repo, &revs->diffopt); if (prefix && !revs->diffopt.prefix) { revs->diffopt.prefix = prefix; revs->diffopt.prefix_length = strlen(prefix); @@@ -1500,7 -1503,6 +1506,7 @@@ static void prepare_show_merge(struct r struct object_id oid; const char **prune = NULL; int i, prune_num = 1; /* counting terminating NULL */ + struct index_state *istate = revs->repo->index; if (get_oid("HEAD", &oid)) die("--merge without HEAD?"); @@@ -1516,20 -1518,20 +1522,20 @@@ free_commit_list(bases); head->object.flags |= SYMMETRIC_LEFT; - if (!active_nr) - read_cache(); - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + if (!istate->cache_nr) + read_index(istate); + for (i = 0; i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; if (!ce_stage(ce)) continue; - if (ce_path_match(&the_index, ce, &revs->prune_data, NULL)) { + if (ce_path_match(istate, ce, &revs->prune_data, NULL)) { prune_num++; REALLOC_ARRAY(prune, prune_num); prune[prune_num-2] = ce->name; prune[prune_num-1] = NULL; } - while ((i+1 < active_nr) && - ce_same_name(ce, active_cache[i+1])) + while ((i+1 < istate->cache_nr) && + ce_same_name(ce, istate->cache[i+1])) i++; } clear_pathspec(&revs->prune_data); @@@ -1586,8 -1588,8 +1592,8 @@@ static int handle_dotdot_1(const char * *dotdot = '\0'; } - a_obj = parse_object(the_repository, &a_oid); - b_obj = parse_object(the_repository, &b_oid); + a_obj = parse_object(revs->repo, &a_oid); + b_obj = parse_object(revs->repo, &b_oid); if (!a_obj || !b_obj) return dotdot_missing(arg, dotdot, revs, symmetric); @@@ -1600,8 -1602,8 +1606,8 @@@ struct commit *a, *b; struct commit_list *exclude; - a = lookup_commit_reference(the_repository, &a_obj->oid); - b = lookup_commit_reference(the_repository, &b_obj->oid); + a = lookup_commit_reference(revs->repo, &a_obj->oid); + b = lookup_commit_reference(revs->repo, &b_obj->oid); if (!a || !b) return dotdot_missing(arg, dotdot, revs, symmetric); @@@ -2138,8 -2140,7 +2144,8 @@@ static int handle_revision_opt(struct r revs->limited = 1; } else if (!strcmp(arg, "--ignore-missing")) { revs->ignore_missing = 1; - } else if (!strcmp(arg, "--exclude-promisor-objects")) { + } else if (revs->allow_exclude_promisor_objects_opt && + !strcmp(arg, "--exclude-promisor-objects")) { if (fetch_if_missing) BUG("exclude_promisor_objects can only be used when fetch_if_missing is 0"); revs->exclude_promisor_objects = 1; @@@ -2210,7 -2211,7 +2216,7 @@@ static int handle_revision_pseudo_opt(c BUG("--single-worktree cannot be used together with submodule"); refs = get_submodule_ref_store(submodule); } else - refs = get_main_ref_store(the_repository); + refs = get_main_ref_store(revs->repo); /* * NOTE! @@@ -2890,10 -2891,9 +2896,10 @@@ void reset_revision_walk(void static int mark_uninteresting(const struct object_id *oid, struct packed_git *pack, uint32_t pos, - void *unused) + void *cb) { - struct object *o = parse_object(the_repository, oid); + struct rev_info *revs = cb; + struct object *o = parse_object(revs->repo, oid); o->flags |= UNINTERESTING | SEEN; return 0; } @@@ -2926,7 -2926,7 +2932,7 @@@ int prepare_revision_walk(struct rev_in revs->treesame.name = "treesame"; if (revs->exclude_promisor_objects) { - for_each_packed_object(mark_uninteresting, NULL, + for_each_packed_object(mark_uninteresting, revs, FOR_EACH_OBJECT_PROMISOR_ONLY); } diff --combined t/t1450-fsck.sh index b5677d26a4,28201677d5..e20e8fa830 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@@ -101,6 -101,41 +101,41 @@@ test_expect_success 'HEAD link pointin grep "HEAD points to something strange" out ' + test_expect_success 'HEAD link pointing at a funny object (from different wt)' ' + test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && + test_when_finished "rm -rf .git/worktrees wt" && + git worktree add wt && + mv .git/HEAD .git/SAVED_HEAD && + echo $ZERO_OID >.git/HEAD && + # avoid corrupt/broken HEAD from interfering with repo discovery + test_must_fail git -C wt fsck 2>out && + grep "main-worktree/HEAD: detached HEAD points" out + ' + + test_expect_success 'other worktree HEAD link pointing at a funny object' ' + test_when_finished "rm -rf .git/worktrees other" && + git worktree add other && + echo $ZERO_OID >.git/worktrees/other/HEAD && + test_must_fail git fsck 2>out && + grep "worktrees/other/HEAD: detached HEAD points" out + ' + + test_expect_success 'other worktree HEAD link pointing at missing object' ' + test_when_finished "rm -rf .git/worktrees other" && + git worktree add other && + echo "Contents missing from repo" | git hash-object --stdin >.git/worktrees/other/HEAD && + test_must_fail git fsck 2>out && + grep "worktrees/other/HEAD: invalid sha1 pointer" out + ' + + test_expect_success 'other worktree HEAD link pointing at a funny place' ' + test_when_finished "rm -rf .git/worktrees other" && + git worktree add other && + echo "ref: refs/funny/place" >.git/worktrees/other/HEAD && + test_must_fail git fsck 2>out && + grep "worktrees/other/HEAD points to something strange" out + ' + test_expect_success 'email without @ is okay' ' git cat-file commit HEAD >basis && sed "s/@/AT/" basis >okay && @@@ -673,35 -708,16 +708,35 @@@ test_expect_success 'fsck detects trail test_i18ngrep "garbage.*$commit" out ' -test_expect_success 'fsck detects trailing loose garbage (blob)' ' +test_expect_success 'fsck detects trailing loose garbage (large blob)' ' blob=$(echo trailing | git hash-object -w --stdin) && file=$(sha1_file $blob) && test_when_finished "remove_object $blob" && chmod +w "$file" && echo garbage >>"$file" && - test_must_fail git fsck 2>out && + test_must_fail git -c core.bigfilethreshold=5 fsck 2>out && test_i18ngrep "garbage.*$blob" out ' +test_expect_success 'fsck detects truncated loose object' ' + # make it big enough that we know we will truncate in the data + # portion, not the header + test-tool genrandom truncate 4096 >file && + blob=$(git hash-object -w file) && + file=$(sha1_file $blob) && + test_when_finished "remove_object $blob" && + test_copy_bytes 1024 <"$file" >tmp && + rm "$file" && + mv -f tmp "$file" && + + # check both regular and streaming code paths + test_must_fail git fsck 2>out && + test_i18ngrep corrupt.*$blob out && + + test_must_fail git -c core.bigfilethreshold=128 fsck 2>out && + test_i18ngrep corrupt.*$blob out +' + # for each of type, we have one version which is referenced by another object # (and so while unreachable, not dangling), and another variant which really is # dangling. diff --combined worktree.c index befdbe7fae,e6a65ec684..d6a0ee7f73 --- a/worktree.c +++ b/worktree.c @@@ -235,7 -235,7 +235,7 @@@ int is_main_worktree(const struct workt return !wt->id; } -const char *is_worktree_locked(struct worktree *wt) +const char *worktree_lock_reason(struct worktree *wt) { assert(!is_main_worktree(wt)); @@@ -487,6 -487,75 +487,75 @@@ int submodule_uses_worktrees(const cha return ret; } + int parse_worktree_ref(const char *worktree_ref, const char **name, + int *name_length, const char **ref) + { + if (skip_prefix(worktree_ref, "main-worktree/", &worktree_ref)) { + if (!*worktree_ref) + return -1; + if (name) + *name = NULL; + if (name_length) + *name_length = 0; + if (ref) + *ref = worktree_ref; + return 0; + } + if (skip_prefix(worktree_ref, "worktrees/", &worktree_ref)) { + const char *slash = strchr(worktree_ref, '/'); + + if (!slash || slash == worktree_ref || !slash[1]) + return -1; + if (name) + *name = worktree_ref; + if (name_length) + *name_length = slash - worktree_ref; + if (ref) + *ref = slash + 1; + return 0; + } + return -1; + } + + void strbuf_worktree_ref(const struct worktree *wt, + struct strbuf *sb, + const char *refname) + { + switch (ref_type(refname)) { + case REF_TYPE_PSEUDOREF: + case REF_TYPE_PER_WORKTREE: + if (wt && !wt->is_current) { + if (is_main_worktree(wt)) + strbuf_addstr(sb, "main-worktree/"); + else + strbuf_addf(sb, "worktrees/%s/", wt->id); + } + break; + + case REF_TYPE_MAIN_PSEUDOREF: + case REF_TYPE_OTHER_PSEUDOREF: + break; + + case REF_TYPE_NORMAL: + /* + * For shared refs, don't prefix worktrees/ or + * main-worktree/. It's not necessary and + * files-backend.c can't handle it anyway. + */ + break; + } + strbuf_addstr(sb, refname); + } + + const char *worktree_ref(const struct worktree *wt, const char *refname) + { + static struct strbuf sb = STRBUF_INIT; + + strbuf_reset(&sb); + strbuf_worktree_ref(wt, &sb, refname); + return sb.buf; + } + int other_head_refs(each_ref_fn fn, void *cb_data) { struct worktree **worktrees, **p; @@@ -495,13 -564,17 +564,17 @@@ worktrees = get_worktrees(0); for (p = worktrees; *p; p++) { struct worktree *wt = *p; - struct ref_store *refs; + struct object_id oid; + int flag; if (wt->is_current) continue; - refs = get_worktree_ref_store(wt); - ret = refs_head_ref(refs, fn, cb_data); + if (!refs_read_ref_full(get_main_ref_store(the_repository), + worktree_ref(wt, "HEAD"), + RESOLVE_REF_READING, + &oid, &flag)) + ret = fn(worktree_ref(wt, "HEAD"), &oid, flag, cb_data); if (ret) break; } diff --combined worktree.h index 55d449b6a9,1164ca396f..9e3b0b7b6f --- a/worktree.h +++ b/worktree.h @@@ -10,12 -10,12 +10,12 @@@ struct worktree char *path; char *id; char *head_ref; /* NULL if HEAD is broken or detached */ - char *lock_reason; /* internal use */ + char *lock_reason; /* private - use worktree_lock_reason */ struct object_id head_oid; int is_detached; int is_bare; int is_current; - int lock_reason_valid; + int lock_reason_valid; /* private */ }; /* Functions for acting on the information about worktrees. */ @@@ -60,7 -60,7 +60,7 @@@ extern int is_main_worktree(const struc * Return the reason string if the given worktree is locked or NULL * otherwise. */ -extern const char *is_worktree_locked(struct worktree *wt); +extern const char *worktree_lock_reason(struct worktree *wt); #define WT_VALIDATE_WORKTREE_MISSING_OK (1 << 0) @@@ -108,4 -108,28 +108,28 @@@ extern const char *worktree_git_path(co const char *fmt, ...) __attribute__((format (printf, 2, 3))); + /* + * Parse a worktree ref (i.e. with prefix main-worktree/ or + * worktrees/) and return the position of the worktree's name and + * length (or NULL and zero if it's main worktree), and ref. + * + * All name, name_length and ref arguments could be NULL. + */ + int parse_worktree_ref(const char *worktree_ref, const char **name, + int *name_length, const char **ref); + + /* + * Return a refname suitable for access from the current ref store. + */ + void strbuf_worktree_ref(const struct worktree *wt, + struct strbuf *sb, + const char *refname); + + /* + * Return a refname suitable for access from the current ref + * store. The result will be destroyed at the next call. + */ + const char *worktree_ref(const struct worktree *wt, + const char *refname); + #endif