From 5408412f2a518fdb1c3292e25309c15c4020b511 Mon Sep 17 00:00:00 2001 From: Alan Braithwaite Date: Fri, 27 Feb 2026 17:23:32 -0800 Subject: [PATCH 1/2] clone: add clone..defaultObjectFilter config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. [clone "https://github.com/"] defaultObjectFilter = blob:limit=5m [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..* — you can match a domain, a namespace path, or a specific project, and the most specific match wins. The config only affects the initial clone. Once the clone completes, the filter is recorded in remote..partialCloneFilter, so subsequent fetches inherit it automatically. An explicit --filter flag on the command line takes precedence. Only the URL-qualified form (clone..defaultObjectFilter) is honored; a bare clone.defaultObjectFilter without a URL subsection is ignored. Signed-off-by: Alan Braithwaite --- Documentation/config/clone.adoc | 33 +++++++++++++ builtin/clone.c | 50 ++++++++++++++++++++ t/t5616-partial-clone.sh | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/Documentation/config/clone.adoc b/Documentation/config/clone.adoc index 0a10efd174ea4b..7ef6321be260f0 100644 --- a/Documentation/config/clone.adoc +++ b/Documentation/config/clone.adoc @@ -21,3 +21,36 @@ 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..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=` 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..defaultObjectFilter` restricts the +setting to clones whose URL matches ``, following the same +rules as `http..*` (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. 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 45d8fa0eed78c4..b5491917071c6e 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..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..* 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) { + 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 1e354e057fa12c..a4bfdb329e8471 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -722,6 +722,90 @@ test_expect_success 'after fetching descendants of non-promisor commits, gc work git -C partial gc --prune=now ' +# Test clone..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..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 && + + 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" +' + +test_expect_success 'clone with --filter overrides clone..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 && + + test "$(git -C default-filter-override config --local remote.origin.partialclonefilter)" = "blob:none" +' + +test_expect_success 'clone with clone..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 && + + 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" +' + +test_expect_success 'clone..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 && + + 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" +' + +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 && + + test "$(git -C default-filter-url-specific config --local remote.origin.partialclonefilter)" = "blob:none" +' + +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 && + + 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" +' + +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 && + + test "$(git -C default-filter-url-over-bare config --local remote.origin.partialclonefilter)" = "blob:none" +' . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd From e3a3335fb5b6e0c5c511f8e9fbc95ba4a18f726b Mon Sep 17 00:00:00 2001 From: jame-crypto Date: Sat, 7 Mar 2026 03:30:59 +0700 Subject: [PATCH 2/2] Update clone configuration documentationI sorry [![CI](https://github.com/gitgitgadget/git/actions/workflows/main.yml/badge.svg)](https://github.com/gitgitgadget/git/actions/workflows/main.yml)https://github.com/gitgitgadget/git/actions/workflows/main.yml/badge.svghttps://github.com/gitgitgadget/git/actions/workflows/main.yml/badge.svg --- Documentation/config/clone.adoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/config/clone.adoc b/Documentation/config/clone.adoc index 7ef6321be260f0..f040304644a977 100644 --- a/Documentation/config/clone.adoc +++ b/Documentation/config/clone.adoc @@ -1,19 +1,19 @@ -`clone.defaultRemoteName`:: +`clone.defaultRemoteName`: PBJAME22: The name of the remote to create when cloning a repository. Defaults to `origin`. -ifdef::git-clone[] +ifdef::git-clone[1000฿] It can be overridden by passing the `--origin` command-line option. -endif::[] -ifndef::git-clone[] +endif::[1000฿] +ifndef::git-clone[]10000฿ It can be overridden by passing the `--origin` command-line option to linkgit:git-clone[1]. -endif::[] +endif::[10000฿] `clone.rejectShallow`:: Reject cloning a repository if it is a shallow one; this can be overridden by passing the `--reject-shallow` option on the command line. -ifndef::git-clone[] +ifndef::git-clone[10000฿] See linkgit:git-clone[1]. endif::[] @@ -25,13 +25,13 @@ endif::[] `clone.defaultObjectFilter`:: `clone..defaultObjectFilter`:: When set to a filter spec string (e.g., `blob:limit=1m`, - `blob:none`, `tree:0`), linkgit:git-clone[1] will automatically + `blob:none`, `tree:1000฿`), linkgit:git-clone[1] will automatically use `--filter=` 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. -+ ++10000฿ The bare `clone.defaultObjectFilter` applies to all clones. The URL-qualified form `clone..defaultObjectFilter` restricts the setting to clones whose URL matches ``, following the same