From: "Alan Braithwaite via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: ps@pks.im, christian.couder@gmail.com, jonathantanmy@google.com,
me@ttaylorr.com, gitster@pobox.com, Jeff King <peff@peff.net>,
"brian m. carlson" <sandals@crustytoothpaste.net>,
Alan Braithwaite <alan@braithwaite.dev>,
Alan Braithwaite <alan@braithwaite.dev>
Subject: [PATCH v5] clone: add clone.<url>.defaultObjectFilter config
Date: Sat, 07 Mar 2026 01:33:56 +0000 [thread overview]
Message-ID: <pull.2058.v5.git.1772847236966.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2058.v4.git.1772833649843.gitgitgadget@gmail.com>
From: Alan Braithwaite <alan@braithwaite.dev>
Add a new configuration option that lets users specify a default
partial clone filter, optionally scoped by URL pattern. When
cloning a repository whose URL matches a configured pattern,
git-clone automatically applies the filter, equivalent to passing
--filter on the command line.
[clone]
defaultObjectFilter = blob:limit=1m
[clone "https://github.com/"]
defaultObjectFilter = blob:limit=5m
[clone "https://internal.corp.com/large-project/"]
defaultObjectFilter = blob:none
The bare clone.defaultObjectFilter applies to all clones. The
URL-qualified form clone.<url>.defaultObjectFilter restricts the
setting to matching URLs. URL matching uses the existing
urlmatch_config_entry() infrastructure, following the same rules as
http.<url>.* — a domain, namespace, or specific project can be
matched, and the most specific match wins.
The config only affects the initial clone. Once the clone completes,
the filter is recorded in remote.<name>.partialCloneFilter, so
subsequent fetches inherit it automatically. An explicit --filter
on the command line takes precedence, and --no-filter defeats the
configured default entirely.
Signed-off-by: Alan Braithwaite <alan@braithwaite.dev>
---
fetch, clone: add fetch.blobSizeLimit config
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2058%2Fabraithwaite%2Falan%2Ffetch-blob-size-limit-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2058/abraithwaite/alan/fetch-blob-size-limit-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/2058
Range-diff vs v4:
1: 4bf3e1ec63 ! 1: fa1ea69bdb clone: add clone.<url>.defaultObjectFilter config
@@ Commit message
clone: add clone.<url>.defaultObjectFilter config
Add a new configuration option that lets users specify a default
- partial clone filter per URL pattern. When cloning a repository
- whose URL matches a configured pattern, git-clone automatically
- applies the filter, equivalent to passing --filter on the command
- line.
+ partial clone filter, optionally scoped by URL pattern. When
+ cloning a repository whose URL matches a configured pattern,
+ git-clone automatically applies the filter, equivalent to passing
+ --filter on the command line.
+
+ [clone]
+ defaultObjectFilter = blob:limit=1m
[clone "https://github.com/"]
defaultObjectFilter = blob:limit=5m
@@ Commit message
[clone "https://internal.corp.com/large-project/"]
defaultObjectFilter = blob:none
- URL matching uses the existing urlmatch_config_entry() infrastructure,
- following the same rules as http.<url>.* — you can match a domain,
- a namespace path, or a specific project, and the most specific match
- wins.
+ The bare clone.defaultObjectFilter applies to all clones. The
+ URL-qualified form clone.<url>.defaultObjectFilter restricts the
+ setting to matching URLs. URL matching uses the existing
+ urlmatch_config_entry() infrastructure, following the same rules as
+ http.<url>.* — a domain, namespace, or specific project can be
+ matched, and the most specific match wins.
The config only affects the initial clone. Once the clone completes,
the filter is recorded in remote.<name>.partialCloneFilter, so
subsequent fetches inherit it automatically. An explicit --filter
- flag on the command line takes precedence.
-
- Only the URL-qualified form (clone.<url>.defaultObjectFilter) is
- honored; a bare clone.defaultObjectFilter without a URL subsection
- is ignored.
+ on the command line takes precedence, and --no-filter defeats the
+ configured default entirely.
Signed-off-by: Alan Braithwaite <alan@braithwaite.dev>
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:limit=1k" clone \
+ "$SERVER_URL" default-filter-clone &&
+
-+ test "$(git -C default-filter-clone config --local remote.origin.promisor)" = "true" &&
-+ test "$(git -C default-filter-clone config --local remote.origin.partialclonefilter)" = "blob:limit=1024"
++ echo true >expect &&
++ git -C default-filter-clone config --local remote.origin.promisor >actual &&
++ test_cmp expect actual &&
++
++ echo "blob:limit=1024" >expect &&
++ git -C default-filter-clone config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success 'clone with --filter overrides clone.<url>.defaultObjectFilter' '
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:limit=1k" \
+ clone --filter=blob:none "$SERVER_URL" default-filter-override &&
+
-+ test "$(git -C default-filter-override config --local remote.origin.partialclonefilter)" = "blob:none"
++ echo "blob:none" >expect &&
++ git -C default-filter-override config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success 'clone with clone.<url>.defaultObjectFilter=blob:none works' '
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" clone \
+ "$SERVER_URL" default-filter-blobnone &&
+
-+ test "$(git -C default-filter-blobnone config --local remote.origin.promisor)" = "true" &&
-+ test "$(git -C default-filter-blobnone config --local remote.origin.partialclonefilter)" = "blob:none"
++ echo true >expect &&
++ git -C default-filter-blobnone config --local remote.origin.promisor >actual &&
++ test_cmp expect actual &&
++
++ echo "blob:none" >expect &&
++ git -C default-filter-blobnone config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success 'clone.<url>.defaultObjectFilter with tree:0 works' '
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ git -c "clone.$SERVER_URL.defaultObjectFilter=tree:0" clone \
+ "$SERVER_URL" default-filter-tree0 &&
+
-+ test "$(git -C default-filter-tree0 config --local remote.origin.promisor)" = "true" &&
-+ test "$(git -C default-filter-tree0 config --local remote.origin.partialclonefilter)" = "tree:0"
++ echo true >expect &&
++ git -C default-filter-tree0 config --local remote.origin.promisor >actual &&
++ test_cmp expect actual &&
++
++ echo "tree:0" >expect &&
++ git -C default-filter-tree0 config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success 'most specific URL match wins for clone.defaultObjectFilter' '
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" \
+ clone "$SERVER_URL" default-filter-url-specific &&
+
-+ test "$(git -C default-filter-url-specific config --local remote.origin.partialclonefilter)" = "blob:none"
++ echo "blob:none" >expect &&
++ git -C default-filter-url-specific config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success 'non-matching URL does not apply clone.defaultObjectFilter' '
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ git -c clone.defaultObjectFilter=blob:none \
+ clone "file://$(pwd)/default-filter-srv.bare" default-filter-bare-key &&
+
-+ test "$(git -C default-filter-bare-key config --local remote.origin.promisor)" = "true" &&
-+ test "$(git -C default-filter-bare-key config --local remote.origin.partialclonefilter)" = "blob:none"
++ echo true >expect &&
++ git -C default-filter-bare-key config --local remote.origin.promisor >actual &&
++ test_cmp expect actual &&
++
++ echo "blob:none" >expect &&
++ git -C default-filter-bare-key config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success 'URL-specific clone.defaultObjectFilter overrides bare form' '
@@ t/t5616-partial-clone.sh: test_expect_success 'after fetching descendants of non
+ -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" \
+ clone "$SERVER_URL" default-filter-url-over-bare &&
+
-+ test "$(git -C default-filter-url-over-bare config --local remote.origin.partialclonefilter)" = "blob:none"
++ echo "blob:none" >expect &&
++ git -C default-filter-url-over-bare config --local remote.origin.partialclonefilter >actual &&
++ test_cmp expect actual
+'
+
+test_expect_success '--no-filter defeats clone.defaultObjectFilter' '
Documentation/config/clone.adoc | 34 +++++++++
builtin/clone.c | 50 ++++++++++++++
t/t5616-partial-clone.sh | 118 ++++++++++++++++++++++++++++++++
3 files changed, 202 insertions(+)
diff --git a/Documentation/config/clone.adoc b/Documentation/config/clone.adoc
index 0a10efd174..1d6c0957a0 100644
--- a/Documentation/config/clone.adoc
+++ b/Documentation/config/clone.adoc
@@ -21,3 +21,37 @@ endif::[]
If a partial clone filter is provided (see `--filter` in
linkgit:git-rev-list[1]) and `--recurse-submodules` is used, also apply
the filter to submodules.
+
+`clone.defaultObjectFilter`::
+`clone.<url>.defaultObjectFilter`::
+ When set to a filter spec string (e.g., `blob:limit=1m`,
+ `blob:none`, `tree:0`), linkgit:git-clone[1] will automatically
+ use `--filter=<value>` to enable partial clone behavior.
+ Objects matching the filter are excluded from the initial
+ transfer and lazily fetched on demand (e.g., during checkout).
+ Subsequent fetches inherit the filter via the per-remote config
+ that is written during the clone.
++
+The bare `clone.defaultObjectFilter` applies to all clones. The
+URL-qualified form `clone.<url>.defaultObjectFilter` restricts the
+setting to clones whose URL matches `<url>`, following the same
+rules as `http.<url>.*` (see linkgit:git-config[1]). The most
+specific URL match wins. You can match a domain, a namespace, or a
+specific project:
++
+----
+[clone]
+ defaultObjectFilter = blob:limit=1m
+
+[clone "https://github.com/"]
+ defaultObjectFilter = blob:limit=5m
+
+[clone "https://internal.corp.com/large-project/"]
+ defaultObjectFilter = blob:none
+----
++
+An explicit `--filter` option on the command line takes precedence
+over this config, and `--no-filter` defeats it entirely to force a
+full clone. Only affects the initial clone; it has no effect on
+later fetches into an existing repository. If the server does not
+support object filtering, the setting is silently ignored.
diff --git a/builtin/clone.c b/builtin/clone.c
index 45d8fa0eed..1207655815 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -44,6 +44,7 @@
#include "path.h"
#include "pkt-line.h"
#include "list-objects-filter-options.h"
+#include "urlmatch.h"
#include "hook.h"
#include "bundle.h"
#include "bundle-uri.h"
@@ -757,6 +758,47 @@ static int git_clone_config(const char *k, const char *v,
return git_default_config(k, v, ctx, cb);
}
+static int clone_filter_collect(const char *var, const char *value,
+ const struct config_context *ctx UNUSED,
+ void *cb)
+{
+ char **filter_spec_p = cb;
+
+ if (!strcmp(var, "clone.defaultobjectfilter")) {
+ if (!value)
+ return config_error_nonbool(var);
+ free(*filter_spec_p);
+ *filter_spec_p = xstrdup(value);
+ }
+ return 0;
+}
+
+/*
+ * Look up clone.defaultObjectFilter or clone.<url>.defaultObjectFilter
+ * using the urlmatch infrastructure. A URL-qualified entry that matches
+ * the clone URL takes precedence over the bare form, following the same
+ * rules as http.<url>.* configuration variables.
+ */
+static char *get_default_object_filter(const char *url)
+{
+ struct urlmatch_config config = URLMATCH_CONFIG_INIT;
+ char *filter_spec = NULL;
+ char *normalized_url;
+
+ config.section = "clone";
+ config.key = "defaultobjectfilter";
+ config.collect_fn = clone_filter_collect;
+ config.cb = &filter_spec;
+
+ normalized_url = url_normalize(url, &config.url);
+
+ repo_config(the_repository, urlmatch_config_entry, &config);
+ free(normalized_url);
+ urlmatch_config_release(&config);
+
+ return filter_spec;
+}
+
static int write_one_config(const char *key, const char *value,
const struct config_context *ctx,
void *data)
@@ -1057,6 +1099,14 @@ int cmd_clone(int argc,
} else
die(_("repository '%s' does not exist"), repo_name);
+ if (!filter_options.choice && !filter_options.no_filter) {
+ char *config_filter = get_default_object_filter(repo);
+ if (config_filter) {
+ parse_list_objects_filter(&filter_options, config_filter);
+ free(config_filter);
+ }
+ }
+
/* no need to be strict, transport_set_option() will validate it again */
if (option_depth && atoi(option_depth) < 1)
die(_("depth %s is not a positive number"), option_depth);
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 1e354e057f..1254901f3e 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -722,6 +722,124 @@ test_expect_success 'after fetching descendants of non-promisor commits, gc work
git -C partial gc --prune=now
'
+# Test clone.<url>.defaultObjectFilter config
+
+test_expect_success 'setup for clone.defaultObjectFilter tests' '
+ git init default-filter-src &&
+ echo "small" >default-filter-src/small.txt &&
+ dd if=/dev/zero of=default-filter-src/large.bin bs=1024 count=100 2>/dev/null &&
+ git -C default-filter-src add . &&
+ git -C default-filter-src commit -m "initial" &&
+
+ git clone --bare "file://$(pwd)/default-filter-src" default-filter-srv.bare &&
+ git -C default-filter-srv.bare config --local uploadpack.allowfilter 1 &&
+ git -C default-filter-srv.bare config --local uploadpack.allowanysha1inwant 1
+'
+
+test_expect_success 'clone with clone.<url>.defaultObjectFilter applies filter' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:limit=1k" clone \
+ "$SERVER_URL" default-filter-clone &&
+
+ echo true >expect &&
+ git -C default-filter-clone config --local remote.origin.promisor >actual &&
+ test_cmp expect actual &&
+
+ echo "blob:limit=1024" >expect &&
+ git -C default-filter-clone config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone with --filter overrides clone.<url>.defaultObjectFilter' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:limit=1k" \
+ clone --filter=blob:none "$SERVER_URL" default-filter-override &&
+
+ echo "blob:none" >expect &&
+ git -C default-filter-override config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone with clone.<url>.defaultObjectFilter=blob:none works' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" clone \
+ "$SERVER_URL" default-filter-blobnone &&
+
+ echo true >expect &&
+ git -C default-filter-blobnone config --local remote.origin.promisor >actual &&
+ test_cmp expect actual &&
+
+ echo "blob:none" >expect &&
+ git -C default-filter-blobnone config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone.<url>.defaultObjectFilter with tree:0 works' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git -c "clone.$SERVER_URL.defaultObjectFilter=tree:0" clone \
+ "$SERVER_URL" default-filter-tree0 &&
+
+ echo true >expect &&
+ git -C default-filter-tree0 config --local remote.origin.promisor >actual &&
+ test_cmp expect actual &&
+
+ echo "tree:0" >expect &&
+ git -C default-filter-tree0 config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'most specific URL match wins for clone.defaultObjectFilter' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git \
+ -c "clone.file://.defaultObjectFilter=blob:limit=1k" \
+ -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" \
+ clone "$SERVER_URL" default-filter-url-specific &&
+
+ echo "blob:none" >expect &&
+ git -C default-filter-url-specific config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'non-matching URL does not apply clone.defaultObjectFilter' '
+ git \
+ -c "clone.https://other.example.com/.defaultObjectFilter=blob:none" \
+ clone "file://$(pwd)/default-filter-srv.bare" default-filter-url-nomatch &&
+
+ test_must_fail git -C default-filter-url-nomatch config --local remote.origin.promisor
+'
+
+test_expect_success 'bare clone.defaultObjectFilter applies to all clones' '
+ git -c clone.defaultObjectFilter=blob:none \
+ clone "file://$(pwd)/default-filter-srv.bare" default-filter-bare-key &&
+
+ echo true >expect &&
+ git -C default-filter-bare-key config --local remote.origin.promisor >actual &&
+ test_cmp expect actual &&
+
+ echo "blob:none" >expect &&
+ git -C default-filter-bare-key config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'URL-specific clone.defaultObjectFilter overrides bare form' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git \
+ -c clone.defaultObjectFilter=blob:limit=1k \
+ -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" \
+ clone "$SERVER_URL" default-filter-url-over-bare &&
+
+ echo "blob:none" >expect &&
+ git -C default-filter-url-over-bare config --local remote.origin.partialclonefilter >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-filter defeats clone.defaultObjectFilter' '
+ SERVER_URL="file://$(pwd)/default-filter-srv.bare" &&
+ git -c "clone.$SERVER_URL.defaultObjectFilter=blob:none" \
+ clone --no-filter "$SERVER_URL" default-filter-no-filter &&
+
+ test_must_fail git -C default-filter-no-filter config --local remote.origin.promisor
+'
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
base-commit: 7b2bccb0d58d4f24705bf985de1f4612e4cf06e5
--
gitgitgadget
next prev parent reply other threads:[~2026-03-07 1:33 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-01 16:44 [PATCH] fetch, clone: add fetch.blobSizeLimit config Alan Braithwaite via GitGitGadget
2026-03-02 11:53 ` Patrick Steinhardt
2026-03-02 18:28 ` Jeff King
2026-03-02 18:57 ` Junio C Hamano
2026-03-02 21:36 ` Alan Braithwaite
2026-03-03 6:30 ` Patrick Steinhardt
2026-03-03 14:00 ` Alan Braithwaite
2026-03-03 15:08 ` Patrick Steinhardt
2026-03-03 17:58 ` Junio C Hamano
2026-03-04 5:07 ` Patrick Steinhardt
2026-03-03 17:05 ` Junio C Hamano
2026-03-03 14:34 ` Jeff King
2026-03-05 0:57 ` [PATCH v2] clone: add clone.<url>.defaultObjectFilter config Alan Braithwaite via GitGitGadget
2026-03-05 19:01 ` Junio C Hamano
2026-03-05 23:11 ` Alan Braithwaite
2026-03-06 6:55 ` [PATCH v3] " Alan Braithwaite via GitGitGadget
2026-03-06 10:39 ` brian m. carlson
2026-03-06 19:33 ` Junio C Hamano
2026-03-06 21:50 ` Alan Braithwaite
2026-03-06 21:47 ` [PATCH v4] " Alan Braithwaite via GitGitGadget
2026-03-06 22:18 ` Junio C Hamano
2026-03-07 1:04 ` Alan Braithwaite
2026-03-07 1:33 ` Alan Braithwaite via GitGitGadget [this message]
2026-03-11 7:44 ` [PATCH v5] " Patrick Steinhardt
2026-03-15 1:33 ` Alan Braithwaite
2026-03-15 5:37 ` [PATCH v6] " Alan Braithwaite via GitGitGadget
2026-03-15 21:32 ` Junio C Hamano
2026-03-16 7:47 ` Patrick Steinhardt
2026-05-11 2:38 ` Junio C Hamano
2026-05-11 7:30 ` Patrick Steinhardt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2058.v5.git.1772847236966.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=alan@braithwaite.dev \
--cc=christian.couder@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=jonathantanmy@google.com \
--cc=me@ttaylorr.com \
--cc=peff@peff.net \
--cc=ps@pks.im \
--cc=sandals@crustytoothpaste.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.