diff --git a/Documentation/RelNotes/2.55.0.adoc b/Documentation/RelNotes/2.55.0.adoc index 2d3871cf34712e..2b588a5b45fa37 100644 --- a/Documentation/RelNotes/2.55.0.adoc +++ b/Documentation/RelNotes/2.55.0.adoc @@ -48,6 +48,9 @@ UI, Workflows & Features * "git cat-file --batch" learns an in-line command "mailmap" that lets the user toggle use of mailmap. + * The "git pack-objects --path-walk" traversal has been integrated + with several object filters, including blobless and sparse filters. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -115,6 +118,10 @@ Performance, Internal Implementation, Development Support etc. more robust by falling back to reading the commit object when the commit-graph is no longer available. + * The "name" argument in git_connect() and related functions has been + converted to a "service" enum to improve type safety and clarify its + purpose. + Fixes since v2.54 ----------------- @@ -230,6 +237,10 @@ Fixes since v2.54 * Update GitLab CI jobs that exercise macOS. (merge 62319b49bb ps/gitlab-ci-macOS-improvements later to maint). + * "Friday noon" asked in the morning on Sunday was parsed to be one + day before the specified time, which has been corrected. + (merge b809304101 ta/approxidate-noon-fix later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 80f4b802e9 ja/doc-difftool-synopsis-style later to maint). (merge b96490241e jc/doc-timestamps-in-stat later to maint). diff --git a/Documentation/config.adoc b/Documentation/config.adoc index dcea3c0c15e2a9..a80e7db46d9697 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc @@ -451,6 +451,8 @@ include::config/guitool.adoc[] include::config/help.adoc[] +include::config/hook.adoc[] + include::config/http.adoc[] include::config/i18n.adoc[] diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc index a9dc0063c12102..083dc60a1325f2 100644 --- a/Documentation/config/hook.adoc +++ b/Documentation/config/hook.adoc @@ -1,10 +1,17 @@ +ifdef::git-hook[] +:see-git-hook: +endif::git-hook[] +ifndef::git-hook[] +:see-git-hook: See linkgit:git-hook[1]. +endif::git-hook[] + hook..command:: The command to execute for `hook.`. `` is a unique name that identifies this hook. The hook events that trigger the command are configured with `hook..event`. The value can be an executable path or a shell oneliner. If more than one value is specified for the same ``, only the last - value parsed is used. See linkgit:git-hook[1]. + value parsed is used. {see-git-hook} hook..event:: The hook events that trigger `hook.`. The value is the @@ -14,7 +21,7 @@ hook..event:: This is a multi-valued key. To run `hook.` on multiple events, specify the key more than once. An empty value resets the list of events, clearing any previously defined events for - `hook.`. See linkgit:git-hook[1]. + `hook.`. {see-git-hook} + The `` must not be the same as a known hook event name (e.g. do not use `hook.pre-commit.event`). Using a known event name as @@ -27,7 +34,7 @@ hook..enabled:: Set to `false` to disable the hook without removing its configuration. This is particularly useful when a hook is defined in a system or global config file and needs to be disabled for a - specific repository. See linkgit:git-hook[1]. + specific repository. {see-git-hook} hook..parallel:: Whether the hook `hook.` may run in parallel with other hooks @@ -37,13 +44,13 @@ hook..parallel:: all hooks for that event run sequentially regardless of `hook.jobs`. Only configured (named) hooks need to declare this. Traditional hooks found in the hooks directory do not need to, and run in parallel when - the effective job count is greater than 1. See linkgit:git-hook[1]. + the effective job count is greater than 1. {see-git-hook} hook..enabled:: Switch to enable or disable all hooks for the `` hook event. When set to `false`, no hooks fire for that event, regardless of any per-hook `hook..enabled` settings. Defaults to `true`. - See linkgit:git-hook[1]. + {see-git-hook} + Note on naming: `` must be the event name (e.g. `pre-commit`), not a hook friendly-name. Since using a known event name as a @@ -60,7 +67,7 @@ hook..jobs:: setting has no effect unless all configured hooks for the event have `hook..parallel` set to `true`. Set to `-1` to use the number of available CPU cores. Must be a positive integer or `-1`; - zero is rejected with a warning. See linkgit:git-hook[1]. + zero is rejected with a warning. {see-git-hook} + Note on naming: although this key resembles `hook..*` (a per-hook setting), `` must be the event name, not a hook diff --git a/Documentation/git-backfill.adoc b/Documentation/git-backfill.adoc index c0a3b80615e034..82d6a1969d0542 100644 --- a/Documentation/git-backfill.adoc +++ b/Documentation/git-backfill.adoc @@ -80,6 +80,10 @@ OPTIONS + You may also use commit-limiting options understood by linkgit:git-rev-list[1] such as `--first-parent`, `--since`, or pathspecs. ++ +Most `--filter=` options don't work with the purpose of +`git backfill`, but the `sparse:` filter is integrated to provide a +focused set of paths to download, distinct from the `--sparse` option. SEE ALSO -------- diff --git a/Documentation/git-hook.adoc b/Documentation/git-hook.adoc index 46ea52db55f268..4868852aa0b728 100644 --- a/Documentation/git-hook.adoc +++ b/Documentation/git-hook.adoc @@ -3,7 +3,7 @@ git-hook(1) NAME ---- -git-hook - Run git hooks +git-hook - Run Git hooks SYNOPSIS -------- @@ -15,8 +15,8 @@ SYNOPSIS DESCRIPTION ----------- -A command interface for running git hooks (see linkgit:githooks[5]), -for use by other scripted git commands. +A command interface for running Git hooks (see linkgit:githooks[5]), +for use by other scripted Git commands. This command parses the default configuration files for sets of configs like so: @@ -41,7 +41,7 @@ spell-checker for your commit messages, you would write a configuration like so: With this config, when you run 'git commit', first `~/bin/linter --cpp20` will have a chance to check your files to be committed (during the `pre-commit` hook -event`), and then `~/bin/spellchecker` will have a chance to check your commit +event), and then `~/bin/spellchecker` will have a chance to check your commit message (during the `commit-msg` hook event). Commands are run in the order Git encounters their associated @@ -161,7 +161,7 @@ setting, allowing all hooks for the event to run concurrently, even if they are not individually marked as parallel. + Some hooks always run sequentially regardless of this flag or the -`hook.jobs` config, because git knows they cannot safely run in parallel: +`hook.jobs` config, because Git knows they cannot safely run in parallel: `applypatch-msg`, `pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`, `post-checkout`, and `push-to-checkout`. @@ -204,6 +204,7 @@ unintended and unsupported ways. CONFIGURATION ------------- +:git-hook: 1 include::config/hook.adoc[] SEE ALSO diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index b78175fbe1b97b..8a27aa19fd3f1f 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -402,9 +402,11 @@ will be automatically changed to version `1`. of filenames that cause collisions in Git's default name-hash algorithm. + -Incompatible with `--delta-islands`, `--shallow`, or `--filter`. The -`--use-bitmap-index` option will be ignored in the presence of -`--path-walk.` +Incompatible with `--delta-islands`. The `--use-bitmap-index` option is +ignored in the presence of `--path-walk`. The `--path-walk` option +supports the `--filter=` forms `blob:none`, `blob:limit=`, +`tree:0`, `object:type=`, and `sparse:`. These supported filter +types can be combined with the `combine:+` form. DELTA ISLANDS diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 94a7b1c065dba8..9e666b9f10bc25 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -23,7 +23,8 @@ ordering and formatting options, such as `--reverse`. `--since=`:: `--after=`:: - Show commits more recent than __. + Show commits more recent than __. As a special case, + 'today' means the last midnight. `--since-as-filter=`:: Show all commits more recent than __. This visits diff --git a/Documentation/technical/api-path-walk.adoc b/Documentation/technical/api-path-walk.adoc index a67de1b143ab5b..6e17b13d61b969 100644 --- a/Documentation/technical/api-path-walk.adoc +++ b/Documentation/technical/api-path-walk.adoc @@ -48,6 +48,13 @@ commits. applications could disable some options to make it simpler to walk the objects or to have fewer calls to `path_fn`. + +Note that objects directly requested as pending objects (such as targets +of lightweight tags or other ref tips) are always emitted to `path_fn`, +even when the corresponding type flag is disabled. Only objects +discovered during the tree walk are subject to these type filters. This +ensures that objects specifically requested through the revision input +are never silently dropped. ++ While it is possible to walk only commits in this way, consumers would be better off using the revision walk API instead. diff --git a/builtin/archive.c b/builtin/archive.c index 13ea7308c8b8b9..3c1288a1231ab7 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -31,7 +31,7 @@ static int run_remote_archiver(int argc, const char **argv, _remote = remote_get(remote); transport = transport_get(_remote, _remote->url.v[0]); - transport_connect(transport, "git-upload-archive", exec, fd); + transport_connect(transport, GIT_CONNECT_UPLOAD_ARCHIVE, exec, fd); /* * Inject a fake --format field at the beginning of the diff --git a/builtin/backfill.c b/builtin/backfill.c index 7ffab2ea74f5cc..e71e0f4742c506 100644 --- a/builtin/backfill.c +++ b/builtin/backfill.c @@ -96,9 +96,10 @@ static void reject_unsupported_rev_list_options(struct rev_info *revs) if (revs->explicit_diff_merges) die(_("'%s' cannot be used with 'git backfill'"), "--diff-merges"); - if (revs->filter.choice) - die(_("'%s' cannot be used with 'git backfill'"), - "--filter"); + if (!path_walk_filter_compatible(&revs->filter)) + die(_("cannot backfill with these filter options")); + if (revs->filter.blob_limit_value) + die(_("cannot backfill with blob size limits")); } static int do_backfill(struct backfill_context *ctx) @@ -108,6 +109,7 @@ static int do_backfill(struct backfill_context *ctx) if (ctx->sparse) { CALLOC_ARRAY(info.pl, 1); + info.pl_sparse_trees = 1; if (get_sparse_checkout_patterns(info.pl)) { path_walk_info_clear(&info); return error(_("problem loading sparse-checkout")); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index d9e42bad58430d..316badd969613f 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -223,7 +223,7 @@ int cmd_fetch_pack(int argc, int flags = args.verbose ? CONNECT_VERBOSE : 0; if (args.diag_url) flags |= CONNECT_DIAG_URL; - conn = git_connect(fd, dest, "git-upload-pack", + conn = git_connect(fd, dest, GIT_CONNECT_UPLOAD_PACK, args.uploadpack, flags); if (!conn) return args.diag_url ? 0 : 1; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 480cc0bd8c8d22..fe9fbecb30e100 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -4764,7 +4764,7 @@ static int add_objects_by_path(const char *path, return 0; } -static void get_object_list_path_walk(struct rev_info *revs) +static int get_object_list_path_walk(struct rev_info *revs) { struct path_walk_info info = PATH_WALK_INFO_INIT; unsigned int processed = 0; @@ -4787,8 +4787,9 @@ static void get_object_list_path_walk(struct rev_info *revs) result = walk_objects_by_path(&info); trace2_region_leave("pack-objects", "path-walk", revs->repo); - if (result) - die(_("failed to pack objects via path-walk")); + path_walk_info_clear(&info); + + return result; } static void get_object_list(struct rev_info *revs, struct strvec *argv) @@ -4851,8 +4852,13 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) fn_show_object = show_object; if (path_walk) { - get_object_list_path_walk(revs); - } else { + if (get_object_list_path_walk(revs)) { + warning(_("failed to pack objects via path-walk")); + path_walk = 0; + } + } + + if (!path_walk) { if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); mark_edges_uninteresting(revs, show_edge, sparse); @@ -5187,7 +5193,7 @@ int cmd_pack_objects(int argc, if (path_walk) { const char *option = NULL; - if (filter_options.choice) + if (!path_walk_filter_compatible(&filter_options)) option = "--filter"; else if (use_delta_islands) option = "--delta-islands"; @@ -5200,10 +5206,7 @@ int cmd_pack_objects(int argc, } if (path_walk) { strvec_push(&rp, "--boundary"); - /* - * We must disable the bitmaps because we are removing - * the --objects / --objects-edge[-aggressive] options. - */ + strvec_push(&rp, "--objects"); use_bitmap_index = 0; } else if (thin) { use_internal_rev_list = 1; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8b81c8a84863eb..1412b49bc845b1 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -273,8 +273,9 @@ int cmd_send_pack(int argc, fd[0] = 0; fd[1] = 1; } else { - conn = git_connect(fd, dest, "git-receive-pack", receivepack, - args.verbose ? CONNECT_VERBOSE : 0); + conn = git_connect(fd, dest, GIT_CONNECT_RECEIVE_PACK, + receivepack, + args.verbose ? CONNECT_VERBOSE : 0); } packet_reader_init(&reader, fd[0], NULL, 0, diff --git a/connect.c b/connect.c index 60e4237470f795..47e39d2a7316ae 100644 --- a/connect.c +++ b/connect.c @@ -1399,7 +1399,7 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host, * the connection failed). */ struct child_process *git_connect(int fd[2], const char *url, - const char *name, + enum git_connect_service service, const char *prog, int flags) { char *hostandport, *path; @@ -1413,7 +1413,7 @@ struct child_process *git_connect(int fd[2], const char *url, * fetch, ls-remote, etc), then fallback to v0 since we don't know how * to do anything else (like push or remote archive) via v2. */ - if (version == protocol_v2 && strcmp("git-upload-pack", name)) + if (version == protocol_v2 && service != GIT_CONNECT_UPLOAD_PACK) version = protocol_v0; /* Without this we cannot rely on waitpid() to tell diff --git a/connect.h b/connect.h index 8d84f6656b1a2a..aa482a37fb4da4 100644 --- a/connect.h +++ b/connect.h @@ -7,7 +7,12 @@ #define CONNECT_DIAG_URL (1u << 1) #define CONNECT_IPV4 (1u << 2) #define CONNECT_IPV6 (1u << 3) -struct child_process *git_connect(int fd[2], const char *url, const char *name, const char *prog, int flags); +enum git_connect_service { + GIT_CONNECT_UPLOAD_PACK, + GIT_CONNECT_RECEIVE_PACK, + GIT_CONNECT_UPLOAD_ARCHIVE, +}; +struct child_process *git_connect(int fd[2], const char *url, enum git_connect_service, const char *prog, int flags); int finish_connect(struct child_process *conn); int git_connection_is_socket(struct child_process *conn); int server_supports(const char *feature); diff --git a/date.c b/date.c index 17a95077cf5450..05b78d852f0705 100644 --- a/date.c +++ b/date.c @@ -1071,13 +1071,22 @@ void datestamp(struct strbuf *out) /* * Relative time update (eg "2 days ago"). If we haven't set the time * yet, we need to set it from current time. + * + * The tm->tm_mday field has an additional logic of using negative values + * for date adjustments: -2 means yesterday and -3 the day before that, + * and so on. The idea is to deref such adjustments until we are sure + * there's no explicit mday specification in the approxidate string. */ static time_t update_tm(struct tm *tm, struct tm *now, time_t sec) { time_t n; - if (tm->tm_mday < 0) + if (tm->tm_mday < 0) { + int offset = tm->tm_mday + 1; + if (sec == 0 && offset < 0) + sec = -offset * 24*60*60; tm->tm_mday = now->tm_mday; + } if (tm->tm_mon < 0) tm->tm_mon = now->tm_mon; if (tm->tm_year < 0) { @@ -1127,34 +1136,39 @@ static void date_now(struct tm *tm, struct tm *now, int *num) static void date_yesterday(struct tm *tm, struct tm *now, int *num) { *num = 0; + tm->tm_mday = -1; update_tm(tm, now, 24*60*60); } -static void date_time(struct tm *tm, struct tm *now, int hour) +static void date_time(struct tm *tm, int hour) { - if (tm->tm_hour < hour) - update_tm(tm, now, 24*60*60); + /* + * If we do not yet have a specified day, we'll use the most recent + * version of "hour" relative to now. But that may be yesterday. + */ + if (tm->tm_mday < 0 && tm->tm_hour < hour) + tm->tm_mday = -2; /* eventually handled by update_tm() */ tm->tm_hour = hour; tm->tm_min = 0; tm->tm_sec = 0; } -static void date_midnight(struct tm *tm, struct tm *now, int *num) +static void date_midnight(struct tm *tm, struct tm *now UNUSED, int *num) { pending_number(tm, num); - date_time(tm, now, 0); + date_time(tm, 0); } -static void date_noon(struct tm *tm, struct tm *now, int *num) +static void date_noon(struct tm *tm, struct tm *now UNUSED, int *num) { pending_number(tm, num); - date_time(tm, now, 12); + date_time(tm, 12); } -static void date_tea(struct tm *tm, struct tm *now, int *num) +static void date_tea(struct tm *tm, struct tm *now UNUSED, int *num) { pending_number(tm, num); - date_time(tm, now, 17); + date_time(tm, 17); } static void date_pm(struct tm *tm, struct tm *now UNUSED, int *num) @@ -1192,6 +1206,17 @@ static void date_never(struct tm *tm, struct tm *now UNUSED, int *num) *num = 0; } +static void date_today(struct tm *tm, struct tm *now, int *num) +{ + if (tm->tm_hour == now->tm_hour && + tm->tm_min == now->tm_min && + tm->tm_sec == now->tm_sec) + date_time(tm, 0); + *num = 0; + tm->tm_mday = -1; + update_tm(tm, now, 0); +} + static const struct special { const char *name; void (*fn)(struct tm *, struct tm *, int *); @@ -1204,6 +1229,7 @@ static const struct special { { "AM", date_am }, { "never", date_never }, { "now", date_now }, + { "today", date_today }, { NULL } }; diff --git a/path-walk.c b/path-walk.c index 6e426af4330893..94ff90bd1566b6 100644 --- a/path-walk.c +++ b/path-walk.c @@ -9,6 +9,9 @@ #include "hashmap.h" #include "hex.h" #include "list-objects.h" +#include "list-objects-filter-options.h" +#include "object-name.h" +#include "odb.h" #include "object.h" #include "oid-array.h" #include "path.h" @@ -178,11 +181,6 @@ static int add_tree_entries(struct path_walk_context *ctx, return -1; } - /* Skip this object if already seen. */ - if (o->flags & SEEN) - continue; - o->flags |= SEEN; - strbuf_setlen(&path, base_len); strbuf_add(&path, entry.path, entry.pathlen); @@ -193,6 +191,40 @@ static int add_tree_entries(struct path_walk_context *ctx, if (type == OBJ_TREE) strbuf_addch(&path, '/'); + if (o->flags & SEEN) { + /* + * A tree with a shared OID may appear at multiple + * paths. Even though we already added this tree to + * the output at some other path, we still need to + * walk into it at this in-cone path to discover + * blobs that were not found at the earlier + * out-of-cone path. + * + * Only do this for paths not yet in our map, to + * avoid duplicate entries when the same tree OID + * appears at the same path across multiple commits. + */ + if (type == OBJ_TREE && ctx->info->pl && + ctx->info->pl->use_cone_patterns && + !ctx->info->pl_sparse_trees && + !strmap_contains(&ctx->paths_to_lists, path.buf)) { + int dtype; + enum pattern_match_result m; + m = path_matches_pattern_list(path.buf, path.len, + path.buf + base_len, + &dtype, + ctx->info->pl, + ctx->repo->index); + if (m != NOT_MATCHED) { + add_path_to_list(ctx, path.buf, type, + &entry.oid, + !(o->flags & UNINTERESTING)); + push_to_stack(ctx, path.buf); + } + } + continue; + } + if (ctx->info->pl) { int dtype; enum pattern_match_result match; @@ -202,7 +234,8 @@ static int add_tree_entries(struct path_walk_context *ctx, ctx->repo->index); if (ctx->info->pl->use_cone_patterns && - match == NOT_MATCHED) + match == NOT_MATCHED && + (type == OBJ_BLOB || ctx->info->pl_sparse_trees)) continue; else if (!ctx->info->pl->use_cone_patterns && type == OBJ_BLOB && @@ -237,6 +270,7 @@ static int add_tree_entries(struct path_walk_context *ctx, continue; } + o->flags |= SEEN; add_path_to_list(ctx, path.buf, type, &entry.oid, !(o->flags & UNINTERESTING)); @@ -248,6 +282,17 @@ static int add_tree_entries(struct path_walk_context *ctx, return 0; } +/* + * Paths starting with '/' (e.g., "/tags", "/tagged-blobs") hold objects that + * were directly requested by 'pending' objects rather than discovered during + * tree traversal. + */ +static int path_is_for_direct_objects(const char *path) +{ + ASSERT(path); + return path[0] == '/'; +} + /* * For each path in paths_to_explore, walk the trees another level * and add any found blobs to the batch (but only if they exist and @@ -306,23 +351,57 @@ static int walk_path(struct path_walk_context *ctx, if (list->type == OBJ_BLOB && ctx->revs->prune_data.nr && + !path_is_for_direct_objects(path) && !match_pathspec(ctx->repo->index, &ctx->revs->prune_data, path, strlen(path), 0, NULL, 0)) return 0; - /* Evaluate function pointer on this data, if requested. */ - if ((list->type == OBJ_TREE && ctx->info->trees) || - (list->type == OBJ_BLOB && ctx->info->blobs) || - (list->type == OBJ_TAG && ctx->info->tags)) + /* + * Evaluate function pointer on this data, if requested. + * Ignore object type filters for tagged objects (path starts + * with `/`), first for blobs and then other types. + */ + if (list->type == OBJ_BLOB && + ctx->info->blob_limit && + !path_is_for_direct_objects(path)) { + struct oid_array filtered = OID_ARRAY_INIT; + + for (size_t i = 0; i < list->oids.nr; i++) { + unsigned long size; + + if (odb_read_object_info(ctx->repo->objects, + &list->oids.oid[i], + &size) != OBJ_BLOB || + size < ctx->info->blob_limit) + oid_array_append(&filtered, + &list->oids.oid[i]); + } + + if (filtered.nr) + ret = ctx->info->path_fn(path, &filtered, list->type, + ctx->info->path_fn_data); + oid_array_clear(&filtered); + } else if ((!ctx->info->strict_types && path_is_for_direct_objects(path)) || + (list->type == OBJ_TREE && ctx->info->trees) || + (list->type == OBJ_BLOB && ctx->info->blobs) || + (list->type == OBJ_TAG && ctx->info->tags)) { ret = ctx->info->path_fn(path, &list->oids, list->type, ctx->info->path_fn_data); + } - /* Expand data for children. */ - if (list->type == OBJ_TREE) { + /* + * Expand tree children, except when the set is directly requested + * _and_ we are otherwise filtering out trees. + */ + if (list->type == OBJ_TREE && + (!path_is_for_direct_objects(path) || ctx->info->trees)) { + /* Use root path if expanding from tagged/direct trees. */ + const char *expand_path = !strcmp(path, "/tagged-trees") + ? root_path : path; for (size_t i = 0; i < list->oids.nr; i++) { ret |= add_tree_entries(ctx, - path, + expand_path, &list->oids.oid[i]); } } @@ -370,14 +449,12 @@ static int setup_pending_objects(struct path_walk_info *info, { struct type_and_oid_list *tags = NULL; struct type_and_oid_list *tagged_blobs = NULL; - struct type_and_oid_list *root_tree_list = NULL; + struct type_and_oid_list *tagged_trees = NULL; if (info->tags) CALLOC_ARRAY(tags, 1); - if (info->blobs) - CALLOC_ARRAY(tagged_blobs, 1); - if (info->trees) - root_tree_list = strmap_get(&ctx->paths_to_lists, root_path); + CALLOC_ARRAY(tagged_blobs, 1); + CALLOC_ARRAY(tagged_trees, 1); /* * Pending objects include: @@ -421,22 +498,19 @@ static int setup_pending_objects(struct path_walk_info *info, switch (obj->type) { case OBJ_TREE: - if (!info->trees) - continue; - if (pending->path) { - char *path = *pending->path ? xstrfmt("%s/", pending->path) - : xstrdup(""); + if (pending->path && *pending->path) { + char *path = xstrfmt("%s/", pending->path); add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1); free(path); + } else if (!pending->path || !info->trees) { + oid_array_append(&tagged_trees->oids, &obj->oid); } else { - /* assume a root tree, such as a lightweight tag. */ - oid_array_append(&root_tree_list->oids, &obj->oid); + add_path_to_list(ctx, root_path, OBJ_TREE, + &obj->oid, 1); } break; case OBJ_BLOB: - if (!info->blobs) - continue; if (pending->path) add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1); else @@ -469,6 +543,18 @@ static int setup_pending_objects(struct path_walk_info *info, free(tagged_blobs); } } + if (tagged_trees) { + if (tagged_trees->oids.nr) { + const char *tagged_tree_path = "/tagged-trees"; + tagged_trees->type = OBJ_TREE; + tagged_trees->maybe_interesting = 1; + strmap_put(&ctx->paths_to_lists, tagged_tree_path, tagged_trees); + push_to_stack(ctx, tagged_tree_path); + } else { + oid_array_clear(&tagged_trees->oids); + free(tagged_trees); + } + } if (tags) { if (tags->oids.nr) { const char *tag_path = "/tags"; @@ -485,6 +571,123 @@ static int setup_pending_objects(struct path_walk_info *info, return 0; } +static int prepare_filters_one(struct path_walk_info *info, + struct list_objects_filter_options *options) +{ + switch (options->choice) { + case LOFC_DISABLED: + return 1; + + case LOFC_BLOB_NONE: + if (info) { + info->blobs = 0; + list_objects_filter_release(options); + } + return 1; + + case LOFC_BLOB_LIMIT: + if (info) { + if (!options->blob_limit_value) + info->blobs = 0; + else if (!info->blob_limit || + info->blob_limit > options->blob_limit_value) + info->blob_limit = options->blob_limit_value; + list_objects_filter_release(options); + } + return 1; + + case LOFC_TREE_DEPTH: + if (options->tree_exclude_depth) { + error(_("tree:%lu filter not supported by the path-walk API"), + options->tree_exclude_depth); + return 0; + } + if (info) { + info->trees = 0; + info->blobs = 0; + } + return 1; + + case LOFC_OBJECT_TYPE: + if (info) { + info->commits &= options->object_type == OBJ_COMMIT; + info->tags &= options->object_type == OBJ_TAG; + info->trees &= options->object_type == OBJ_TREE; + info->blobs &= options->object_type == OBJ_BLOB; + info->strict_types = 1; + list_objects_filter_release(options); + } + return 1; + + case LOFC_SPARSE_OID: + if (info) { + struct object_id sparse_oid; + struct repository *repo = info->revs->repo; + + if (info->pl) { + warning(_("sparse filter cannot be combined with existing sparse patterns")); + return 0; + } + + if (repo_get_oid_with_flags(repo, + options->sparse_oid_name, + &sparse_oid, + GET_OID_BLOB)) { + error(_("unable to access sparse blob in '%s'"), + options->sparse_oid_name); + return 0; + } + + CALLOC_ARRAY(info->pl, 1); + info->pl->use_cone_patterns = 1; + + if (add_patterns_from_blob_to_list(&sparse_oid, "", 0, + info->pl) < 0) { + clear_pattern_list(info->pl); + FREE_AND_NULL(info->pl); + error(_("unable to parse sparse filter data in '%s'"), + oid_to_hex(&sparse_oid)); + return 0; + } + + if (!info->pl->use_cone_patterns) { + clear_pattern_list(info->pl); + FREE_AND_NULL(info->pl); + warning(_("sparse filter is not cone-mode compatible")); + return 0; + } + } + return 1; + + case LOFC_COMBINE: + for (size_t i = 0; i < options->sub_nr; i++) { + if (!prepare_filters_one(info, &options->sub[i])) + return 0; + } + return 1; + + default: + error(_("object filter '%s' not supported by the path-walk API"), + list_objects_filter_spec(options)); + return 0; + } +} + +static int prepare_filters(struct path_walk_info *info, + struct list_objects_filter_options *options) +{ + if (!prepare_filters_one(info, options)) + return 0; + if (info) + list_objects_filter_release(options); + return 1; +} + +int path_walk_filter_compatible(struct list_objects_filter_options *options) +{ + return prepare_filters(NULL, options); +} + /** * Given the configuration of 'info', walk the commits based on 'info->revs' and * call 'info->path_fn' on each discovered path. @@ -512,6 +715,9 @@ int walk_objects_by_path(struct path_walk_info *info) trace2_region_enter("path-walk", "commit-walk", info->revs->repo); + if (!prepare_filters(info, &info->revs->filter)) + return -1; + CALLOC_ARRAY(commit_list, 1); commit_list->type = OBJ_COMMIT; @@ -532,15 +738,17 @@ int walk_objects_by_path(struct path_walk_info *info) push_to_stack(&ctx, root_path); /* - * Set these values before preparing the walk to catch - * lightweight tags pointing to non-commits and indexed objects. + * Ensure that prepare_revision_walk() keeps all pending objects + * even through an object type filter. */ - info->revs->blob_objects = info->blobs; - info->revs->tree_objects = info->trees; + info->revs->blob_objects = info->revs->tree_objects = 1; if (prepare_revision_walk(info->revs)) die(_("failed to setup revision walk")); + info->revs->blob_objects = info->blobs; + info->revs->tree_objects = info->trees; + /* * Walk trees to mark them as UNINTERESTING. * This is particularly important when 'edge_aggressive' is set. diff --git a/path-walk.h b/path-walk.h index 5ef5a8440e6b5e..a2652b2d465edf 100644 --- a/path-walk.h +++ b/path-walk.h @@ -36,12 +36,30 @@ struct path_walk_info { /** * Initialize which object types the path_fn should be called on. This * could also limit the walk to skip blobs if not set. + * + * Note: even when 'blobs' or 'trees' is disabled, objects that are + * directly requested as pending objects will still be emitted to + * path_fn. Only objects discovered during the tree walk are filtered by + * these flags. */ int commits; int trees; int blobs; int tags; + /** + * If 'strict_types' is 0, then direct object requests will no longer + * override the object type restrictions. + */ + int strict_types; + + /** + * If non-zero, specifies a maximum blob size. Blobs with a + * size equal to or greater than this limit will not be + * emitted unless included in 'pending'. + */ + unsigned long blob_limit; + /** * When 'prune_all_uninteresting' is set and a path has all objects * marked as UNINTERESTING, then the path-walk will not visit those @@ -64,8 +82,14 @@ struct path_walk_info { * of the cone. If not in cone mode, then all tree paths will be * explored but the path_fn will only be called when the path matches * the sparse-checkout patterns. + * + * When 'pl_sparse_trees' is zero, the sparse patterns only restrict + * blobs and all trees are included in the walk output. This matches + * the behavior of the sparse:oid object filter. When nonzero, trees + * are also pruned by the sparse patterns (as used by backfill). */ struct pattern_list *pl; + int pl_sparse_trees; }; #define PATH_WALK_INFO_INIT { \ @@ -85,3 +109,10 @@ void path_walk_info_clear(struct path_walk_info *info); * Returns nonzero on an error. */ int walk_objects_by_path(struct path_walk_info *info); + +struct list_objects_filter_options; +/** + * Given a set of options for filtering objects, return 1 if the options + * are compatible with the path-walk API and 0 otherwise. + */ +int path_walk_filter_compatible(struct list_objects_filter_options *options); diff --git a/t/helper/test-path-walk.c b/t/helper/test-path-walk.c index 69676b15a53f73..4233badb58dc0d 100644 --- a/t/helper/test-path-walk.c +++ b/t/helper/test-path-walk.c @@ -4,6 +4,7 @@ #include "dir.h" #include "environment.h" #include "hex.h" +#include "list-objects-filter-options.h" #include "object-name.h" #include "object.h" #include "pretty.h" @@ -67,10 +68,12 @@ static int emit_block(const char *path, struct oid_array *oids, int cmd__path_walk(int argc, const char **argv) { - int res, stdin_pl = 0; + int res, stdin_pl = 0, pl_sparse_trees = -1; struct rev_info revs = REV_INFO_INIT; struct path_walk_info info = PATH_WALK_INFO_INIT; struct path_walk_test_data data = { 0 }; + struct list_objects_filter_options filter_options = + LIST_OBJECTS_FILTER_INIT; struct option options[] = { OPT_BOOL(0, "blobs", &info.blobs, N_("toggle inclusion of blob objects")), @@ -86,11 +89,14 @@ int cmd__path_walk(int argc, const char **argv) N_("toggle aggressive edge walk")), OPT_BOOL(0, "stdin-pl", &stdin_pl, N_("read a pattern list over stdin")), + OPT_BOOL(0, "pl-sparse-trees", &pl_sparse_trees, + N_("toggle pruning of trees by sparse patterns")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END(), }; setup_git_directory(the_repository); - revs.repo = the_repository; + repo_init_revisions(the_repository, &revs, NULL); argc = parse_options(argc, argv, NULL, options, path_walk_usage, @@ -101,6 +107,10 @@ int cmd__path_walk(int argc, const char **argv) else usage(path_walk_usage[0]); + /* Apply the filter after setup_revisions to avoid the --objects check. */ + if (filter_options.choice) + list_objects_filter_copy(&revs.filter, &filter_options); + info.revs = &revs; info.path_fn = emit_block; info.path_fn_data = &data; @@ -108,6 +118,8 @@ int cmd__path_walk(int argc, const char **argv) if (stdin_pl) { struct strbuf in = STRBUF_INIT; CALLOC_ARRAY(info.pl, 1); + info.pl_sparse_trees = (pl_sparse_trees >= 0) ? + pl_sparse_trees : 1; info.pl->use_cone_patterns = 1; @@ -129,6 +141,7 @@ int cmd__path_walk(int argc, const char **argv) free(info.pl); } + list_objects_filter_release(&filter_options); release_revisions(&revs); return res; } diff --git a/t/perf/p5315-pack-objects-filter.sh b/t/perf/p5315-pack-objects-filter.sh new file mode 100755 index 00000000000000..445ff25be232b9 --- /dev/null +++ b/t/perf/p5315-pack-objects-filter.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='Tests pack-objects performance with filters and --path-walk' +. ./perf-lib.sh + +test_perf_large_repo + +test_expect_success 'setup filter inputs' ' + # Sample a few depth-2 directories from the test repo to build + # a cone-mode sparse-checkout definition. The sampling picks + # directories at evenly-spaced positions so the choice is stable + # and scales to repos of any shape. + + git ls-tree -d HEAD >top-entries && + grep "^040000" top-entries | + awk "{print \$4;}" >top-dirs && + top_nr=$(wc -l depth2-dirs && + + d2_nr=$(wc -l sparse-patterns && + + git hash-object -w sparse-patterns >sparse-oid && + echo "Sparse cone: $first $mid" && + cat sparse-patterns && + test_set_prereq SPARSE_OID + elif test "$top_nr" -ge 1 + then + # Fallback: use a single top-level directory. + first=$(sed -n "1p" top-dirs) && + { + echo "/*" && + echo "!/*/" && + echo "/$first/" + } >sparse-patterns && + + git hash-object -w sparse-patterns >sparse-oid && + echo "Sparse cone: $first" && + cat sparse-patterns && + test_set_prereq SPARSE_OID + fi +' + +test_perf 'repack (no filter)' ' + git pack-objects --stdout --no-reuse-delta --revs --all pk +' + +test_size 'repack size (no filter)' ' + test_file_size pk +' + +test_perf 'repack (no filter, --path-walk)' ' + git pack-objects --stdout --no-reuse-delta --revs --all --path-walk pk +' + +test_size 'repack size (no filter, --path-walk)' ' + test_file_size pk +' + +test_perf 'repack (blob:none)' ' + git pack-objects --stdout --no-reuse-delta --revs --all --filter=blob:none pk +' + +test_size 'repack size (blob:none)' ' + test_file_size pk +' + +test_perf 'repack (blob:none, --path-walk)' ' + git pack-objects --stdout --no-reuse-delta --revs --all --path-walk \ + --filter=blob:none pk +' + +test_size 'repack size (blob:none, --path-walk)' ' + test_file_size pk +' + +test_perf 'repack (sparse:oid)' \ + --prereq SPARSE_OID ' + git pack-objects --stdout --no-reuse-delta --revs --all \ + --filter=sparse:oid=$(cat sparse-oid) pk +' + +test_size 'repack size (sparse:oid)' \ + --prereq SPARSE_OID ' + test_file_size pk +' + +test_perf 'repack (sparse:oid, --path-walk)' \ + --prereq SPARSE_OID ' + git pack-objects --stdout --no-reuse-delta --revs --all --path-walk \ + --filter=sparse:oid=$(cat sparse-oid) pk +' + +test_size 'repack size (sparse:oid, --path-walk)' \ + --prereq SPARSE_OID ' + test_file_size pk +' + +test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 53ced36df448f1..9a76b84ed9bf1a 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -155,15 +155,45 @@ check_parse '2100-00-00 00:00:00 -11' bad check_parse '2100-00-00 00:00:00 +11' bad REQUIRE_64BIT_TIME= +add_time_offset() { + case "$3" in + hours) + unit=$(( 60*60 )) + ;; + days) + unit=$(( 24*60*60 )) + ;; + esac + offset=$(( $2 * unit )) + echo $(( $1 + offset )) +} + check_approxidate() { + old_date=$GIT_TEST_DATE_NOW + if test "$3" = "failure" + then + expection="$3" + else + expection=${4:-success} + offset="$3" + fi + if test -n "$offset" + then + GIT_TEST_DATE_NOW=$(add_time_offset $old_date $offset) + caption="$1; offset $offset" + else + caption=$1 + fi echo "$1 -> $2 +0000" >expect - test_expect_${3:-success} "parse approxidate ($1)" " + test_expect_$expection "parse approxidate ($caption)" " test-tool date approxidate '$1' >actual && test_cmp expect actual " + GIT_TEST_DATE_NOW=$old_date } check_approxidate now '2009-08-30 19:20:00' +check_approxidate today '2009-08-30 00:00:00' check_approxidate '5 seconds ago' '2009-08-30 19:19:55' check_approxidate 5.seconds.ago '2009-08-30 19:19:55' check_approxidate 10.minutes.ago '2009-08-30 19:10:00' @@ -179,14 +209,26 @@ check_approxidate '6pm yesterday' '2009-08-29 18:00:00' check_approxidate '3:00' '2009-08-30 03:00:00' check_approxidate '15:00' '2009-08-30 15:00:00' check_approxidate 'noon today' '2009-08-30 12:00:00' +check_approxidate 'today at noon' '2009-08-30 12:00:00' '-12 hours' +check_approxidate 'noon today' '2009-09-01 12:00:00' '+36 hours' check_approxidate 'noon yesterday' '2009-08-29 12:00:00' +check_approxidate 'noon yesterday' '2009-08-29 12:00:00' '-12 hours' +check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' +check_approxidate 'last Friday at noon' '2009-08-28 12:00:00' '-12 hours' +check_approxidate 'tea last saturday' '2009-08-29 17:00:00' +check_approxidate 'tea last saturday' '2009-08-29 17:00:00' '-12 hours' check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' +check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' '-12 hours' +check_approxidate 'January 5th today pm' '2009-01-30 12:00:00' check_approxidate '10am noon' '2009-08-29 12:00:00' +check_approxidate 'January 5th yesterday' '2009-01-29 19:20:00' +check_approxidate 'January 5th yesterday' '2008-12-31 19:20:00' '+2 days' check_approxidate 'last tuesday' '2009-08-25 19:20:00' check_approxidate 'July 5th' '2009-07-05 19:20:00' check_approxidate '06/05/2009' '2009-06-05 19:20:00' check_approxidate '06.05.2009' '2009-05-06 19:20:00' +check_approxidate 'Jan 5 today' '2009-01-30 00:00:00' check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00' check_approxidate '5AM Jun 6' '2009-06-06 05:00:00' diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 501d715b9a16b7..dddb79ba627036 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -478,4 +478,129 @@ test_expect_success 'verify pack-objects w/ --missing=allow-any' ' EOF ' +# Test that --path-walk produces the same object set as standard traversal +# when using sparse:oid filters with cone-mode patterns. +# +# The sparse:oid filter restricts only blobs, not trees. Both standard +# and path-walk should produce identical sets of blobs, commits, and trees. + +test_expect_success 'setup pw_sparse for path-walk comparison' ' + git init pw_sparse && + mkdir -p pw_sparse/inc/sub pw_sparse/exc/sub && + + for n in 1 2 + do + echo "inc $n" >pw_sparse/inc/file$n && + echo "inc sub $n" >pw_sparse/inc/sub/file$n && + echo "exc $n" >pw_sparse/exc/file$n && + echo "exc sub $n" >pw_sparse/exc/sub/file$n && + echo "root $n" >pw_sparse/root$n || return 1 + done && + + git -C pw_sparse add . && + git -C pw_sparse commit -m "first" && + + echo "inc 1 modified" >pw_sparse/inc/file1 && + echo "exc 1 modified" >pw_sparse/exc/file1 && + echo "root 1 modified" >pw_sparse/root1 && + git -C pw_sparse add . && + git -C pw_sparse commit -m "second" && + + # Cone-mode sparse pattern: include root + inc/ + printf "/*\n!/*/\n/inc/\n" | + git -C pw_sparse hash-object -w --stdin >sparse_oid +' + +test_expect_success 'sparse:oid with --path-walk produces same blobs' ' + oid=$(cat sparse_oid) && + + git -C pw_sparse pack-objects --revs --stdout \ + --filter=sparse:oid=$oid >standard.pack <<-EOF && + HEAD + EOF + git -C pw_sparse index-pack ../standard.pack && + git -C pw_sparse verify-pack -v ../standard.pack >standard_verify && + + git -C pw_sparse pack-objects --revs --stdout \ + --path-walk --filter=sparse:oid=$oid >pathwalk.pack <<-EOF && + HEAD + EOF + git -C pw_sparse index-pack ../pathwalk.pack && + git -C pw_sparse verify-pack -v ../pathwalk.pack >pathwalk_verify && + + # Blobs must match exactly + grep -E "^[0-9a-f]{40} blob" standard_verify | + awk "{print \$1}" | sort >standard_blobs && + grep -E "^[0-9a-f]{40} blob" pathwalk_verify | + awk "{print \$1}" | sort >pathwalk_blobs && + test_cmp standard_blobs pathwalk_blobs && + + # Commits must match exactly + grep -E "^[0-9a-f]{40} commit" standard_verify | + awk "{print \$1}" | sort >standard_commits && + grep -E "^[0-9a-f]{40} commit" pathwalk_verify | + awk "{print \$1}" | sort >pathwalk_commits && + test_cmp standard_commits pathwalk_commits +' + +test_expect_success 'sparse:oid with --path-walk includes all trees' ' + # The sparse:oid filter restricts only blobs, not trees. + # Both standard and path-walk should include the same trees. + grep -E "^[0-9a-f]{40} tree" standard_verify | + awk "{print \$1}" | sort >standard_trees && + grep -E "^[0-9a-f]{40} tree" pathwalk_verify | + awk "{print \$1}" | sort >pathwalk_trees && + + test_cmp standard_trees pathwalk_trees +' + +# Test the edge case where the same tree/blob OID appears at both an +# in-cone and out-of-cone path. When sibling directories have identical +# contents, they share a tree OID. The path-walk defers marking objects +# SEEN until after checking sparse patterns, so an object at an out-of-cone +# path can still be discovered at an in-cone path. + +test_expect_success 'setup pw_shared for shared OID across cone boundary' ' + git init pw_shared && + mkdir pw_shared/aaa pw_shared/zzz && + echo "shared content" >pw_shared/aaa/file && + echo "shared content" >pw_shared/zzz/file && + echo "root file" >pw_shared/rootfile && + git -C pw_shared add . && + git -C pw_shared commit -m "aaa and zzz share tree OID" && + + # Verify they share a tree OID + aaa_tree=$(git -C pw_shared rev-parse HEAD:aaa) && + zzz_tree=$(git -C pw_shared rev-parse HEAD:zzz) && + test "$aaa_tree" = "$zzz_tree" && + + # Cone pattern: include root + zzz/ (not aaa/) + printf "/*\n!/*/\n/zzz/\n" | + git -C pw_shared hash-object -w --stdin >shared_sparse_oid +' + +test_expect_success 'shared tree OID: --path-walk blobs match standard' ' + oid=$(cat shared_sparse_oid) && + + git -C pw_shared pack-objects --revs --stdout \ + --filter=sparse:oid=$oid >shared_std.pack <<-EOF && + HEAD + EOF + git -C pw_shared index-pack ../shared_std.pack && + git -C pw_shared verify-pack -v ../shared_std.pack >shared_std_verify && + + git -C pw_shared pack-objects --revs --stdout \ + --path-walk --filter=sparse:oid=$oid >shared_pw.pack <<-EOF && + HEAD + EOF + git -C pw_shared index-pack ../shared_pw.pack && + git -C pw_shared verify-pack -v ../shared_pw.pack >shared_pw_verify && + + grep -E "^[0-9a-f]{40} blob" shared_std_verify | + awk "{print \$1}" | sort >shared_std_blobs && + grep -E "^[0-9a-f]{40} blob" shared_pw_verify | + awk "{print \$1}" | sort >shared_pw_blobs && + test_cmp shared_std_blobs shared_pw_blobs +' + test_done diff --git a/t/t5620-backfill.sh b/t/t5620-backfill.sh index 94f35ce1901671..d2ea68e065304d 100755 --- a/t/t5620-backfill.sh +++ b/t/t5620-backfill.sh @@ -15,6 +15,14 @@ test_expect_success 'backfill rejects unexpected arguments' ' test_grep "unrecognized argument: --unexpected-arg" err ' +test_expect_success 'backfill rejects incompatible filter options' ' + test_must_fail git backfill --objects --filter=tree:1 2>err && + test_grep "cannot backfill with these filter options" err && + + test_must_fail git backfill --objects --filter=blob:limit=10m 2>err && + test_grep "cannot backfill with blob size limits" err +' + # We create objects in the 'src' repo. test_expect_success 'setup repo for object creation' ' echo "{print \$1}" >print_1.awk && diff --git a/t/t6601-path-walk.sh b/t/t6601-path-walk.sh index 56bd1e3c5bec97..e9fcd85e7520bf 100755 --- a/t/t6601-path-walk.sh +++ b/t/t6601-path-walk.sh @@ -7,17 +7,15 @@ test_description='direct path-walk API tests' test_expect_success 'setup test repository' ' git checkout -b base && - # Make some objects that will only be reachable - # via non-commit tags. - mkdir child && - echo file >child/file && - git add child && - git commit -m "will abandon" && - git tag -a -m "tree" tree-tag HEAD^{tree} && - echo file2 >file2 && - git add file2 && - git commit --amend -m "will abandon" && - git tag tree-tag2 HEAD^{tree} && + # Create tree objects that are only reachable via tags, + # not from any commit in the history. + child_blob_oid=$(echo "child blob content" | git hash-object -t blob -w --stdin) && + child_tree_oid=$(printf "100644 blob %s\tfile\n" "$child_blob_oid" | git mktree) && + tree_tag_oid=$(printf "040000 tree %s\tchild\n" "$child_tree_oid" | git mktree) && + git tag -a -m "tree" tree-tag "$tree_tag_oid" && + file2_blob_oid=$(echo "tagged tree file2" | git hash-object -t blob -w --stdin) && + tree_tag2_oid=$(printf "040000 tree %s\tchild\n100644 blob %s\tfile2\n" "$child_tree_oid" "$file2_blob_oid" | git mktree) && + git tag tree-tag2 "$tree_tag2_oid" && echo blob >file && blob_oid=$(git hash-object -t blob -w --stdin left/b && echo c >right/c && git add . && - git commit --amend -m "first" && + git commit -m "first" && git tag -m "first" first HEAD && echo d >right/d && @@ -79,23 +77,23 @@ test_expect_success 'all' ' 3:tree::$(git rev-parse base^{tree}) 3:tree::$(git rev-parse base~1^{tree}) 3:tree::$(git rev-parse base~2^{tree}) - 3:tree::$(git rev-parse refs/tags/tree-tag^{}) - 3:tree::$(git rev-parse refs/tags/tree-tag2^{}) 4:blob:a:$(git rev-parse base~2:a) - 5:blob:file2:$(git rev-parse refs/tags/tree-tag2^{}:file2) - 6:tree:a/:$(git rev-parse base:a) - 7:tree:child/:$(git rev-parse refs/tags/tree-tag:child) - 8:blob:child/file:$(git rev-parse refs/tags/tree-tag:child/file) - 9:tree:left/:$(git rev-parse base:left) - 9:tree:left/:$(git rev-parse base~2:left) - 10:blob:left/b:$(git rev-parse base~2:left/b) - 10:blob:left/b:$(git rev-parse base:left/b) - 11:tree:right/:$(git rev-parse topic:right) - 11:tree:right/:$(git rev-parse base~1:right) - 11:tree:right/:$(git rev-parse base~2:right) - 12:blob:right/c:$(git rev-parse base~2:right/c) - 12:blob:right/c:$(git rev-parse topic:right/c) - 13:blob:right/d:$(git rev-parse base~1:right/d) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 6:blob:file2:$(git rev-parse refs/tags/tree-tag2^{}:file2) + 7:tree:a/:$(git rev-parse base:a) + 8:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 9:blob:child/file:$(git rev-parse refs/tags/tree-tag:child/file) + 10:tree:left/:$(git rev-parse base:left) + 10:tree:left/:$(git rev-parse base~2:left) + 11:blob:left/b:$(git rev-parse base~2:left/b) + 11:blob:left/b:$(git rev-parse base:left/b) + 12:tree:right/:$(git rev-parse topic:right) + 12:tree:right/:$(git rev-parse base~1:right) + 12:tree:right/:$(git rev-parse base~2:right) + 13:blob:right/c:$(git rev-parse base~2:right/c) + 13:blob:right/c:$(git rev-parse topic:right/c) + 14:blob:right/d:$(git rev-parse base~1:right/d) blobs:10 commits:4 tags:7 @@ -206,6 +204,43 @@ test_expect_success 'base & topic, sparse' ' test_cmp_sorted expect out ' +test_expect_success 'base & topic, sparse, no tree pruning' ' + cat >patterns <<-EOF && + /* + !/*/ + /left/ + EOF + + test-tool path-walk --stdin-pl --no-pl-sparse-trees \ + -- base topic out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tree::$(git rev-parse topic^{tree}) + 1:tree::$(git rev-parse base^{tree}) + 1:tree::$(git rev-parse base~1^{tree}) + 1:tree::$(git rev-parse base~2^{tree}) + 2:blob:a:$(git rev-parse base~2:a) + 3:tree:a/:$(git rev-parse base:a) + 4:tree:left/:$(git rev-parse base:left) + 4:tree:left/:$(git rev-parse base~2:left) + 5:blob:left/b:$(git rev-parse base~2:left/b) + 5:blob:left/b:$(git rev-parse base:left/b) + 6:tree:right/:$(git rev-parse topic:right) + 6:tree:right/:$(git rev-parse base~1:right) + 6:tree:right/:$(git rev-parse base~2:right) + blobs:3 + commits:4 + tags:0 + trees:10 + EOF + + test_cmp_sorted expect out +' + test_expect_success 'topic only' ' test-tool path-walk -- topic >out && @@ -415,4 +450,483 @@ test_expect_success 'trees are reported exactly once' ' test_line_count = 1 out-filtered ' +test_expect_success 'all, blob:none filter' ' + test-tool path-walk --filter=blob:none -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 5:tree:a/:$(git rev-parse base:a) + 6:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 7:tree:left/:$(git rev-parse base:left) + 7:tree:left/:$(git rev-parse base~2:left) + 8:tree:right/:$(git rev-parse topic:right) + 8:tree:right/:$(git rev-parse base~1:right) + 8:tree:right/:$(git rev-parse base~2:right) + blobs:2 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'topic only, blob:none filter' ' + test-tool path-walk --filter=blob:none -- topic >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tree::$(git rev-parse topic^{tree}) + 1:tree::$(git rev-parse base~1^{tree}) + 1:tree::$(git rev-parse base~2^{tree}) + 2:tree:left/:$(git rev-parse base~2:left) + 3:tree:right/:$(git rev-parse topic:right) + 3:tree:right/:$(git rev-parse base~1:right) + 3:tree:right/:$(git rev-parse base~2:right) + blobs:0 + commits:3 + tags:0 + trees:7 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, blob:limit=0 filter' ' + test-tool path-walk --filter=blob:limit=0 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 4:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 5:tree:a/:$(git rev-parse base:a) + 6:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 7:tree:left/:$(git rev-parse base:left) + 7:tree:left/:$(git rev-parse base~2:left) + 8:tree:right/:$(git rev-parse topic:right) + 8:tree:right/:$(git rev-parse base~1:right) + 8:tree:right/:$(git rev-parse base~2:right) + blobs:2 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, blob:limit=3 filter' ' + test-tool path-walk --filter=blob:limit=3 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:blob:a:$(git rev-parse base~2:a) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 6:tree:a/:$(git rev-parse base:a) + 7:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 8:tree:left/:$(git rev-parse base:left) + 8:tree:left/:$(git rev-parse base~2:left) + 9:blob:left/b:$(git rev-parse base~2:left/b) + 10:tree:right/:$(git rev-parse topic:right) + 10:tree:right/:$(git rev-parse base~1:right) + 10:tree:right/:$(git rev-parse base~2:right) + 11:blob:right/c:$(git rev-parse base~2:right/c) + 12:blob:right/d:$(git rev-parse base~1:right/d) + blobs:6 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, tree:0 filter' ' + test-tool path-walk --filter=tree:0 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{tree}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2) + blobs:2 + commits:4 + tags:7 + trees:2 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'topic only, tree:0 filter' ' + test-tool path-walk --filter=tree:0 -- topic >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + blobs:0 + commits:3 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'tree:1 filter is rejected' ' + test_must_fail test-tool path-walk --filter=tree:1 -- --all 2>err && + test_grep "tree:1 filter not supported by the path-walk API" err +' + +test_expect_success 'all, object:type=commit filter' ' + test-tool path-walk --filter=object:type=commit -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + blobs:0 + commits:4 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, object:type=tag filter' ' + test-tool path-walk --filter=object:type=tag -- --all >out && + + cat >expect <<-EOF && + 0:tag:/tags:$(git rev-parse refs/tags/first) + 0:tag:/tags:$(git rev-parse refs/tags/second.1) + 0:tag:/tags:$(git rev-parse refs/tags/second.2) + 0:tag:/tags:$(git rev-parse refs/tags/third) + 0:tag:/tags:$(git rev-parse refs/tags/fourth) + 0:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 0:tag:/tags:$(git rev-parse refs/tags/blob-tag) + blobs:0 + commits:0 + tags:7 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, object:type=tree filter' ' + test-tool path-walk --filter=object:type=tree -- --all >out && + + cat >expect <<-EOF && + 0:tree::$(git rev-parse topic^{tree}) + 0:tree::$(git rev-parse base^{tree}) + 0:tree::$(git rev-parse base~1^{tree}) + 0:tree::$(git rev-parse base~2^{tree}) + 1:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 1:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 2:tree:a/:$(git rev-parse base:a) + 3:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 4:tree:left/:$(git rev-parse base:left) + 4:tree:left/:$(git rev-parse base~2:left) + 5:tree:right/:$(git rev-parse topic:right) + 5:tree:right/:$(git rev-parse base~1:right) + 5:tree:right/:$(git rev-parse base~2:right) + blobs:0 + commits:0 + tags:0 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, object:type=blob filter' ' + test-tool path-walk --filter=object:type=blob -- --all >out && + + cat >expect <<-EOF && + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 1:blob:a:$(git rev-parse base~2:a) + 2:blob:left/b:$(git rev-parse base:left/b) + 2:blob:left/b:$(git rev-parse base~2:left/b) + 3:blob:right/c:$(git rev-parse base~2:right/c) + 3:blob:right/c:$(git rev-parse topic:right/c) + 4:blob:right/d:$(git rev-parse base~1:right/d) + blobs:8 + commits:0 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, combine:blob:none+tree:0 filter' ' + test-tool path-walk \ + --filter=combine:blob:none+tree:0 -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{tree}) + 3:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2) + blobs:2 + commits:4 + tags:7 + trees:2 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, combine:object:type=blob+blob:limit=3 filter' ' + test-tool path-walk \ + --filter=combine:object:type=blob+blob:limit=3 \ + -- --all >out && + + cat >expect <<-EOF && + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 0:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 1:blob:a:$(git rev-parse base~2:a) + 2:blob:left/b:$(git rev-parse base~2:left/b) + 3:blob:right/c:$(git rev-parse base~2:right/c) + 4:blob:right/d:$(git rev-parse base~1:right/d) + blobs:6 + commits:0 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'all, combine of disjoint object:types is empty' ' + test-tool path-walk \ + --filter=combine:object:type=blob+object:type=tree \ + -- --all >out && + + cat >expect <<-EOF && + blobs:0 + commits:0 + tags:0 + trees:0 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'combine: rejects unsupported subfilters' ' + test_must_fail test-tool path-walk \ + --filter=combine:tree:1+blob:none -- --all 2>err && + test_grep "tree:1 filter not supported by the path-walk API" err +' + +test_expect_success 'setup sparse filter blob' ' + # Cone-mode patterns: include root, exclude all dirs, include left/ + cat >patterns <<-\EOF && + /* + !/*/ + /left/ + EOF + sparse_oid=$(git hash-object -w -t blob patterns) +' + +test_expect_success 'all, sparse:oid filter' ' + test-tool path-walk --filter=sparse:oid=$sparse_oid -- --all >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tag:/tags:$(git rev-parse refs/tags/first) + 1:tag:/tags:$(git rev-parse refs/tags/second.1) + 1:tag:/tags:$(git rev-parse refs/tags/second.2) + 1:tag:/tags:$(git rev-parse refs/tags/third) + 1:tag:/tags:$(git rev-parse refs/tags/fourth) + 1:tag:/tags:$(git rev-parse refs/tags/tree-tag) + 1:tag:/tags:$(git rev-parse refs/tags/blob-tag) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag^{}) + 2:blob:/tagged-blobs:$(git rev-parse refs/tags/blob-tag2^{}) + 3:tree::$(git rev-parse topic^{tree}) + 3:tree::$(git rev-parse base^{tree}) + 3:tree::$(git rev-parse base~1^{tree}) + 3:tree::$(git rev-parse base~2^{tree}) + 4:blob:a:$(git rev-parse base~2:a) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag^{}) + 5:tree:/tagged-trees:$(git rev-parse refs/tags/tree-tag2^{}) + 6:blob:file2:$(git rev-parse refs/tags/tree-tag2^{}:file2) + 7:tree:a/:$(git rev-parse base:a) + 8:tree:child/:$(git rev-parse refs/tags/tree-tag:child) + 9:tree:left/:$(git rev-parse base:left) + 9:tree:left/:$(git rev-parse base~2:left) + 10:blob:left/b:$(git rev-parse base~2:left/b) + 10:blob:left/b:$(git rev-parse base:left/b) + 11:tree:right/:$(git rev-parse topic:right) + 11:tree:right/:$(git rev-parse base~1:right) + 11:tree:right/:$(git rev-parse base~2:right) + blobs:6 + commits:4 + tags:7 + trees:13 + EOF + + test_cmp_sorted expect out +' + +test_expect_success 'topic only, sparse:oid filter' ' + test-tool path-walk --filter=sparse:oid=$sparse_oid -- topic >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse topic) + 0:commit::$(git rev-parse base~1) + 0:commit::$(git rev-parse base~2) + 1:tree::$(git rev-parse topic^{tree}) + 1:tree::$(git rev-parse base~1^{tree}) + 1:tree::$(git rev-parse base~2^{tree}) + 2:blob:a:$(git rev-parse base~2:a) + 3:tree:left/:$(git rev-parse base~2:left) + 4:blob:left/b:$(git rev-parse base~2:left/b) + 5:tree:right/:$(git rev-parse topic:right) + 5:tree:right/:$(git rev-parse base~1:right) + 5:tree:right/:$(git rev-parse base~2:right) + blobs:2 + commits:3 + tags:0 + trees:7 + EOF + + test_cmp_sorted expect out +' + +# Demonstrate the SEEN flag ordering issue: when the same tree/blob OID +# appears at two sibling paths where one is in-cone and the other is +# out-of-cone, the path-walk must still discover blobs at the in-cone +# path even when the shared tree OID was first encountered out-of-cone. +# Since sparse:oid includes all trees, the out-of-cone tree (aaa/) is +# walked first, and its blob is skipped. The path-walk then re-walks +# the same tree OID at the in-cone path (zzz/) to find the blob there. + +test_expect_success 'setup shared tree OID across cone boundary' ' + git checkout --orphan shared-tree && + git rm -rf . && + mkdir aaa zzz && + echo "shared content" >aaa/file && + echo "shared content" >zzz/file && + echo "root file" >rootfile && + git add aaa zzz rootfile && + git commit -m "aaa and zzz have same tree OID" && + + # Verify they really share a tree OID + aaa_tree=$(git rev-parse HEAD:aaa) && + zzz_tree=$(git rev-parse HEAD:zzz) && + test "$aaa_tree" = "$zzz_tree" && + + # Cone pattern: include root + zzz/ (not aaa/) + cat >shared-patterns <<-\EOF && + /* + !/*/ + /zzz/ + EOF + shared_sparse_oid=$(git hash-object -w -t blob shared-patterns) +' + +test_expect_success 'sparse:oid with shared tree OID across cone boundary' ' + test-tool path-walk \ + --filter=sparse:oid=$shared_sparse_oid \ + -- shared-tree >out && + + cat >expect <<-EOF && + 0:commit::$(git rev-parse shared-tree) + 1:tree::$(git rev-parse shared-tree^{tree}) + 2:blob:rootfile:$(git rev-parse shared-tree:rootfile) + 3:tree:aaa/:$(git rev-parse shared-tree:aaa) + 4:tree:zzz/:$(git rev-parse shared-tree:zzz) + 5:blob:zzz/file:$(git rev-parse shared-tree:zzz/file) + blobs:2 + commits:1 + tags:0 + trees:3 + EOF + + test_cmp_sorted expect out +' + test_done diff --git a/transport-helper.c b/transport-helper.c index d776651d71f7c3..04d55572a9b371 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -620,8 +620,22 @@ static int run_connect(struct transport *transport, struct strbuf *cmdbuf) return ret; } +static const char *connect_service_cmd(enum git_connect_service service) +{ + switch (service) { + case GIT_CONNECT_UPLOAD_PACK: + return "git-upload-pack"; + case GIT_CONNECT_RECEIVE_PACK: + return "git-receive-pack"; + case GIT_CONNECT_UPLOAD_ARCHIVE: + return "git-upload-archive"; + } + BUG("unknown git_connect_service: %d", service); +} + static int process_connect_service(struct transport *transport, - const char *name, const char *exec) + enum git_connect_service service, + const char *exec) { struct helper_data *data = transport->data; struct strbuf cmdbuf = STRBUF_INIT; @@ -631,7 +645,7 @@ static int process_connect_service(struct transport *transport, * Handle --upload-pack and friends. This is fire and forget... * just warn if it fails. */ - if (strcmp(name, exec)) { + if (strcmp(connect_service_cmd(service), exec)) { int r = set_helper_option(transport, "servpath", exec); if (r > 0) warning(_("setting remote service path not supported by protocol")); @@ -640,13 +654,15 @@ static int process_connect_service(struct transport *transport, } if (data->connect) { - strbuf_addf(&cmdbuf, "connect %s\n", name); + strbuf_addf(&cmdbuf, "connect %s\n", + connect_service_cmd(service)); ret = run_connect(transport, &cmdbuf); } else if (data->stateless_connect && (get_protocol_version_config() == protocol_v2) && - (!strcmp("git-upload-pack", name) || - !strcmp("git-upload-archive", name))) { - strbuf_addf(&cmdbuf, "stateless-connect %s\n", name); + (service == GIT_CONNECT_UPLOAD_PACK || + service == GIT_CONNECT_UPLOAD_ARCHIVE)) { + strbuf_addf(&cmdbuf, "stateless-connect %s\n", + connect_service_cmd(service)); ret = run_connect(transport, &cmdbuf); if (ret) transport->stateless_rpc = 1; @@ -660,32 +676,33 @@ static int process_connect(struct transport *transport, int for_push) { struct helper_data *data = transport->data; - const char *name; + enum git_connect_service service; const char *exec; int ret; - name = for_push ? "git-receive-pack" : "git-upload-pack"; + service = for_push ? GIT_CONNECT_RECEIVE_PACK : GIT_CONNECT_UPLOAD_PACK; if (for_push) exec = data->transport_options.receivepack; else exec = data->transport_options.uploadpack; - ret = process_connect_service(transport, name, exec); + ret = process_connect_service(transport, service, exec); if (ret) do_take_over(transport); return ret; } -static int connect_helper(struct transport *transport, const char *name, - const char *exec, int fd[2]) +static int connect_helper(struct transport *transport, enum git_connect_service service, + const char *exec, int fd[2]) { struct helper_data *data = transport->data; /* Get_helper so connect is inited. */ get_helper(transport); - if (!process_connect_service(transport, name, exec)) - die(_("can't connect to subservice %s"), name); + if (!process_connect_service(transport, service, exec)) + die(_("can't connect to subservice %s"), + connect_service_cmd(service)); fd[0] = data->helper->out; fd[1] = data->helper->in; diff --git a/transport-internal.h b/transport-internal.h index 90ea749e5cfbf7..051f3ab0dc95ec 100644 --- a/transport-internal.h +++ b/transport-internal.h @@ -1,6 +1,8 @@ #ifndef TRANSPORT_INTERNAL_H #define TRANSPORT_INTERNAL_H +#include "connect.h" + struct ref; struct transport; struct strvec; @@ -58,7 +60,8 @@ struct transport_vtable { * process involved generating new commits. **/ int (*push_refs)(struct transport *transport, struct ref *refs, int flags); - int (*connect)(struct transport *connection, const char *name, + int (*connect)(struct transport *connection, + enum git_connect_service service, const char *executable, int fd[2]); /** get_refs_list(), fetch(), and push_refs() can keep diff --git a/transport.c b/transport.c index cb1f12a818a809..0f5ec302472d53 100644 --- a/transport.c +++ b/transport.c @@ -309,8 +309,8 @@ static int connect_setup(struct transport *transport, int for_push) data->conn = git_connect(data->fd, transport->url, for_push ? - "git-receive-pack" : - "git-upload-pack", + GIT_CONNECT_RECEIVE_PACK : + GIT_CONNECT_UPLOAD_PACK, for_push ? data->options.receivepack : data->options.uploadpack, @@ -961,12 +961,13 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re return ret; } -static int connect_git(struct transport *transport, const char *name, +static int connect_git(struct transport *transport, + enum git_connect_service service, const char *executable, int fd[2]) { struct git_transport_data *data = transport->data; data->conn = git_connect(data->fd, transport->url, - name, executable, 0); + service, executable, 0); fd[0] = data->fd[0]; fd[1] = data->fd[1]; return 0; @@ -1664,11 +1665,12 @@ void transport_unlock_pack(struct transport *transport, unsigned int flags) string_list_clear(&transport->pack_lockfiles, 0); } -int transport_connect(struct transport *transport, const char *name, +int transport_connect(struct transport *transport, + enum git_connect_service service, const char *exec, int fd[2]) { if (transport->vtable->connect) - return transport->vtable->connect(transport, name, exec, fd); + return transport->vtable->connect(transport, service, exec, fd); else die(_("operation not supported by protocol")); } diff --git a/transport.h b/transport.h index 97d905ecc03dc4..7e5867cffaaa4a 100644 --- a/transport.h +++ b/transport.h @@ -5,6 +5,7 @@ #include "remote.h" #include "list-objects-filter-options.h" #include "string-list.h" +#include "connect.h" struct git_transport_options { unsigned thin : 1; @@ -325,7 +326,8 @@ char *transport_anonymize_url(const char *url); void transport_take_over(struct transport *transport, struct child_process *child); -int transport_connect(struct transport *transport, const char *name, +int transport_connect(struct transport *transport, + enum git_connect_service service, const char *exec, int fd[2]); /* Transport methods defined outside transport.c */