From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dy1-f174.google.com (mail-dy1-f174.google.com [74.125.82.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 706853B0AE1 for ; Wed, 13 May 2026 21:19:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.174 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778707164; cv=none; b=KNVjob1Q1tswb5TRNgJOpCQkjW4LGIEb2fG+VYN/ZuLcmq3RkaEX0MvA/CExFVwSN6+u18qK9L4HoCs3GuWpMBwgVnDtdtENFqodXxBFQKQxyMzb/hwTgBYSKXwRDTnJ0i5cDMHjhQ17IiC3ex9+GnZzcKX0aqw+T+ii2sZorYI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778707164; c=relaxed/simple; bh=tYpwveQognsNt3Pk3RcOlt2i/pdnG84c3Msvg8DyEpE=; h=Message-Id:In-Reply-To:References:From:Date:Subject:Content-Type: MIME-Version:To:Cc; b=ZK4KF9Ks9IYkhAoNXzL9qUWyGcGwsQrrarn1i4YpcTNckDeTIpSoVn1rOVCPKhdXEjF8J3TNYzysauthLJHSLZxvMIa0LHzdiGKDaSX3P38BL0sNlJAO+TLiOxXcSKEyiIB/B/PkazzTayyYAFi0z6lwLCEqiWKrJHV0hxGKiKk= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=SJJJ9QQF; arc=none smtp.client-ip=74.125.82.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SJJJ9QQF" Received: by mail-dy1-f174.google.com with SMTP id 5a478bee46e88-2f68f3b075fso10270206eec.0 for ; Wed, 13 May 2026 14:19:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778707156; x=1779311956; darn=vger.kernel.org; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:from:to:cc:subject:date :message-id:reply-to; bh=wACVFlOjM+bnRgbybkb7aUoI1nRHejKCjSzwm0Ghhzo=; b=SJJJ9QQFYaFJHA1C2bhDyBHPsSgDv4p/lP8wBVI9TUcEdLcLe+CQHFPuBdS41WC2Q7 q67kXwKpfe7wqUtxeFkFQvSmpAu9OmHus2pPlNNd8rPu9kORsjikEZSXxPY0p8Ot3lEF AuzGMfyTmPHpkbfarWRCuPisnzCs61DWDkLAaMf8OfFnkpTtrrDIIJT/S/I411ZAbSiC DKg/4xEyE2vZD8/y0sf4PW0aSzillBnhZpPQgEVawajqY50JVngGJoGvSDy0LBTVyM/i dhmnkf94Bj6fOMfGsXoJpi3Bc+E8o6mSMqPP74zLxbLsY12WKeAAcrVDshfFDQ8cadTs FeWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778707156; x=1779311956; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=wACVFlOjM+bnRgbybkb7aUoI1nRHejKCjSzwm0Ghhzo=; b=XDv5O3QRIO/imf2t+y2+8kbuYGeH0tH+6Ld4UYUGh/u4dlT6jBxaGZ1t6BJxyXqzyj ZyF8i6rUf2q7k6ePvM1gIYF+VpnnXY4dVN5OjJI50gHuhjpR8o46LctW4xqqH8JFArn2 JgGbAAlBfawRYBD7J4h5yHS6QKdAdRDefHKNWDkOVJ0WPACSN6X4oRY+gfYjmF3Tn4KK NdE2yDsfooIf351dyHo2LYqZVMUEG/2NEIvJ6mzgP90YGvDcotGbnYieyVMP542w3mJQ G0jkvrqKhtdRotqXCJO0PCi6Ap+0PJHqrPfH899xJWEZKY62S+2pXxcx9gIqkCdL2sZM TgZw== X-Gm-Message-State: AOJu0Yzrh+CaJh1NpUkeybKY5fZ5TlVq+LR8GrRdjp1hH37xckvSVvGO CHhMX4rn+Lv0Ry8YLk45k0xqSqwpsLSR10rlKQ/1OocJqJjPNQwJQSD707sWf77M X-Gm-Gg: Acq92OHMlrgnA//da6I8oqvduFLcRVccwumceQyNEGzBLrvLDFUNJ5joLN0y9w1StBM FlGXPRu4CtJ+i7OEv4fyA9X3OCGiZg4i8mcUm2b9TTi/JmK0FWb7AI9CjqInT10t7MeDEMvw4hs Zb1w8weqs0cMdln0w1eMrNhY6F/1wdCaJzxWtOONPXGWXTwAIidJ+lVKrkHIaLYayG0X8OyxRhw LD3997GtxF8RQdZ/i1dt/rxcE+uHQf1tbwyzlBwe37zztSUUiVZ4TcvAv3EGhpZR5l/q/pWDfVV vcqcVwKhMMLI6057wM+KMJq5fOFxjojOkg1Ho9gBbBjqnw738V+M5L3NFfSYsncsoaqTphi31tb oml+BQh3+q84HJPq8ognh87ddX5JCe/p3uLrR7GCldEv3GxdtlOdVGAE/j+Omc1gf7TsdKjR/ep iwgO/z/hnesmsx4fb+pLJbyevxiw== X-Received: by 2002:a05:7300:fb8b:b0:2d9:6373:ad1d with SMTP id 5a478bee46e88-30118e9ff8dmr3523448eec.20.1778707155740; Wed, 13 May 2026 14:19:15 -0700 (PDT) Received: from [127.0.0.1] ([20.169.77.168]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-30293e2e69esm666785eec.1.2026.05.13.14.19.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 13 May 2026 14:19:15 -0700 (PDT) Message-Id: In-Reply-To: References: From: "Taylor Blau via GitGitGadget" Date: Wed, 13 May 2026 21:18:53 +0000 Subject: [PATCH v4 11/13] path-walk: support `tree:0` filter Fcc: Sent Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 To: git@vger.kernel.org Cc: christian.couder@gmail.com, gitster@pobox.com, johannes.schindelin@gmx.de, johncai86@gmail.com, karthik.188@gmail.com, kristofferhaugsbakk@fastmail.com, me@ttaylorr.com, newren@gmail.com, peff@peff.net, ps@pks.im, Taylor Blau , Derrick Stolee , Taylor Blau From: Taylor Blau The `tree:0` object filter omits all trees and blobs from the result, keeping only commits and tags. Consequently, this filter type should has a fairly straightforward integration with path-walk, as the decision to include an object depends only on its type and does not depend on any path-sensitive state. Mapping it onto `path_walk_info` is direct: set `info->trees = 0` and `info->blobs = 0` in `prepare_filters()` when the `LOFC_TREE_DEPTH` choice is requested with depth zero. The existing code already plumbs those flags through the rest of the walk: - 'walk_objects_by_path()' sets `revs->blob_objects = info->blobs` and `revs->tree_objects = info->trees` before `prepare_revision_walk()`, so the revision walk doesn't try to enumerate trees or blobs itself. - The commit-walk loop short-circuits the root-tree fetch with "if (!info->trees && !info->blobs) continue;", so we never even look up the root tree, let alone descend into it. - `setup_pending_objects()` skips pending trees and blobs based on the same flags. This means the path-walk doesn't allocate or expand any tree structures at all under `tree:0`, which matches the intended behavior of the filter. However, this requires first fixing some issues with how the path-walk API handles directly-requested trees _and_ trees requested through lightweight tags. These changes create substantial updates to t6601-path-walk.sh, which the previous change highlighted as a problem by tagging otherwise-unreachable trees and having them not appear in the output. Non-zero tree-depth filters are not supported. Those depend on the depth at which a tree is visited, which is a path-walk concept the filter machinery doesn't currently share with the path-walk API. Reject them in `prepare_filters()` with a helpful error and let pack-objects fall back to the regular traversal, the same way it already does for unsupported filters. Add coverage in t6601 for both `--all` and a single-branch case to confirm that no trees or blobs are emitted, and a separate test that `tree:1` is rejected with the expected error message. Place the new tests before "setup sparse filter blob" so they run on the original set of refs, before the orphan branch that the sparse-tree tests create. Signed-off-by: Taylor Blau Signed-off-by: Derrick Stolee --- Documentation/git-pack-objects.adoc | 4 +- path-walk.c | 53 +++++++-- t/t6601-path-walk.sh | 165 ++++++++++++++++++---------- 3 files changed, 152 insertions(+), 70 deletions(-) diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index e38853391b..c86219be91 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -404,8 +404,8 @@ will be automatically changed to version `1`. + 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=`, and -`sparse:`. +supports the `--filter=` forms `blob:none`, `blob:limit=`, +`tree:0`, and `sparse:`. DELTA ISLANDS diff --git a/path-walk.c b/path-walk.c index ce38dcf1e9..cb67b8ce86 100644 --- a/path-walk.c +++ b/path-walk.c @@ -390,11 +390,18 @@ static int walk_path(struct path_walk_context *ctx, 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]); } } @@ -442,12 +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); CALLOC_ARRAY(tagged_blobs, 1); - root_tree_list = strmap_get(&ctx->paths_to_lists, root_path); + CALLOC_ARRAY(tagged_trees, 1); /* * Pending objects include: @@ -491,14 +498,15 @@ static int setup_pending_objects(struct path_walk_info *info, switch (obj->type) { case OBJ_TREE: - 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; @@ -535,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"; @@ -575,6 +595,19 @@ static int prepare_filters(struct path_walk_info *info, } 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; + list_objects_filter_release(options); + } + return 1; + case LOFC_SPARSE_OID: if (info) { struct object_id sparse_oid; diff --git a/t/t6601-path-walk.sh b/t/t6601-path-walk.sh index 92c524d145..566db7c7e3 100755 --- a/t/t6601-path-walk.sh +++ b/t/t6601-path-walk.sh @@ -77,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 @@ -471,15 +471,15 @@ test_expect_success 'all, blob:none filter' ' 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:tree:a/:$(git rev-parse base:a) - 5:tree:child/:$(git rev-parse refs/tags/tree-tag:child) - 6:tree:left/:$(git rev-parse base:left) - 6:tree:left/:$(git rev-parse base~2:left) - 7:tree:right/:$(git rev-parse topic:right) - 7:tree:right/:$(git rev-parse base~1:right) - 7:tree:right/:$(git rev-parse base~2:right) + 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 @@ -533,15 +533,15 @@ test_expect_success 'all, blob:limit=0 filter' ' 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:tree:a/:$(git rev-parse base:a) - 5:tree:child/:$(git rev-parse refs/tags/tree-tag:child) - 6:tree:left/:$(git rev-parse base:left) - 6:tree:left/:$(git rev-parse base~2:left) - 7:tree:right/:$(git rev-parse topic:right) - 7:tree:right/:$(git rev-parse base~1:right) - 7:tree:right/:$(git rev-parse base~2:right) + 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 @@ -572,19 +572,19 @@ test_expect_success 'all, blob:limit=3 filter' ' 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: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:blob:left/b:$(git rev-parse base~2:left/b) - 9:tree:right/:$(git rev-parse topic:right) - 9:tree:right/:$(git rev-parse base~1:right) - 9:tree:right/:$(git rev-parse base~2:right) - 10:blob:right/c:$(git rev-parse base~2:right/c) - 11: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: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 @@ -594,6 +594,55 @@ test_expect_success 'all, blob:limit=3 filter' ' 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 'setup sparse filter blob' ' # Cone-mode patterns: include root, exclude all dirs, include left/ cat >patterns <<-\EOF && @@ -625,19 +674,19 @@ test_expect_success 'all, sparse:oid filter' ' 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: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) - 9:blob:left/b:$(git rev-parse base: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) + 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 -- gitgitgadget