Skip to content

Commit 4212733

Browse files
dschoGit for Windows Build Agent
authored and
Git for Windows Build Agent
committed
Introduce 'git backfill' to get missing blobs in a partial clone (#5172)
This change introduces the `git backfill` command which uses the path walk API to download missing blobs in a blobless partial clone. By downloading blobs that correspond to the same file path at the same time, we hope to maximize the potential benefits of delta compression against multiple versions. These downloads occur in a configurable batch size, presenting a mechanism to perform "resumable" clones: `git clone --filter=blob:none` gets the commits and trees, then `git backfill` will download all missing blobs. If `git backfill` is interrupted partway through, it can be restarted and will redownload only the missing objects. When combining blobless partial clones with sparse-checkout, `git backfill` will assume its `--sparse` option and download only the blobs within the sparse-checkout. Users may want to do this as the repo size will still be smaller than the full repo size, but commands like `git blame` or `git log -L` will not suffer from many one-by-one blob downloads. Future directions should consider adding a pathspec or file prefix to further focus which paths are being downloaded in a batch.
2 parents 96ee454 + c3c4c42 commit 4212733

18 files changed

+499
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
/git-apply
2020
/git-archimport
2121
/git-archive
22+
/git-backfill
2223
/git-bisect
2324
/git-blame
2425
/git-branch

Documentation/git-backfill.txt

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
git-backfill(1)
2+
===============
3+
4+
NAME
5+
----
6+
git-backfill - Download missing objects in a partial clone
7+
8+
9+
SYNOPSIS
10+
--------
11+
[verse]
12+
(EXPERIMENTAL) 'git backfill' [--batch-size=<n>] [--[no-]sparse]
13+
14+
DESCRIPTION
15+
-----------
16+
17+
Blobless partial clones are created using `git clone --filter=blob:none`
18+
and then configure the local repository such that the Git client avoids
19+
downloading blob objects unless they are required for a local operation.
20+
This initially means that the clone and later fetches download reachable
21+
commits and trees but no blobs. Later operations that change the `HEAD`
22+
pointer, such as `git checkout` or `git merge`, may need to download
23+
missing blobs in order to complete their operation.
24+
25+
In the worst cases, commands that compute blob diffs, such as `git blame`,
26+
become very slow as they download the missing blobs in single-blob
27+
requests to satisfy the missing object as the Git command needs it. This
28+
leads to multiple download requests and no ability for the Git server to
29+
provide delta compression across those objects.
30+
31+
The `git backfill` command provides a way for the user to request that
32+
Git downloads the missing blobs (with optional filters) such that the
33+
missing blobs representing historical versions of files can be downloaded
34+
in batches. The `backfill` command attempts to optimize the request by
35+
grouping blobs that appear at the same path, hopefully leading to good
36+
delta compression in the packfile sent by the server.
37+
38+
By default, `git backfill` downloads all blobs reachable from the `HEAD`
39+
commit. This set can be restricted or expanded using various options.
40+
41+
OPTIONS
42+
-------
43+
44+
--batch-size=<n>::
45+
Specify a minimum size for a batch of missing objects to request
46+
from the server. This size may be exceeded by the last set of
47+
blobs seen at a given path. Default batch size is 16,000.
48+
49+
--[no-]sparse::
50+
Only download objects if they appear at a path that matches the
51+
current sparse-checkout. If the sparse-checkout feature is enabled,
52+
then `--sparse` is assumed and can be disabled with `--no-sparse`.
53+
54+
SEE ALSO
55+
--------
56+
linkgit:git-clone[1].
57+
58+
GIT
59+
---
60+
Part of the linkgit:git[1] suite

Documentation/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ manpages = {
66
'git-apply.txt' : 1,
77
'git-archimport.txt' : 1,
88
'git-archive.txt' : 1,
9+
'git-backfill.txt' : 1,
910
'git-bisect.txt' : 1,
1011
'git-blame.txt' : 1,
1112
'git-branch.txt' : 1,

Documentation/technical/api-path-walk.txt

+9
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,18 @@ better off using the revision walk API instead.
6565
the revision walk so that the walk emits commits marked with the
6666
`UNINTERESTING` flag.
6767

68+
`pl`::
69+
This pattern list pointer allows focusing the path-walk search to
70+
a set of patterns, only emitting paths that match the given
71+
patterns. See linkgit:gitignore[5] or
72+
linkgit:git-sparse-checkout[1] for details about pattern lists.
73+
When the pattern list uses cone-mode patterns, then the path-walk
74+
API can prune the set of paths it walks to improve performance.
75+
6876
Examples
6977
--------
7078

7179
See example usages in:
7280
`t/helper/test-path-walk.c`,
81+
`builtin/backfill.c`,
7382
`builtin/pack-objects.c`

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@ BUILTIN_OBJS += builtin/am.o
12091209
BUILTIN_OBJS += builtin/annotate.o
12101210
BUILTIN_OBJS += builtin/apply.o
12111211
BUILTIN_OBJS += builtin/archive.o
1212+
BUILTIN_OBJS += builtin/backfill.o
12121213
BUILTIN_OBJS += builtin/bisect.o
12131214
BUILTIN_OBJS += builtin/blame.o
12141215
BUILTIN_OBJS += builtin/branch.o

builtin.h

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ int cmd_am(int argc, const char **argv, const char *prefix, struct repository *r
120120
int cmd_annotate(int argc, const char **argv, const char *prefix, struct repository *repo);
121121
int cmd_apply(int argc, const char **argv, const char *prefix, struct repository *repo);
122122
int cmd_archive(int argc, const char **argv, const char *prefix, struct repository *repo);
123+
int cmd_backfill(int argc, const char **argv, const char *prefix, struct repository *repo);
123124
int cmd_bisect(int argc, const char **argv, const char *prefix, struct repository *repo);
124125
int cmd_blame(int argc, const char **argv, const char *prefix, struct repository *repo);
125126
int cmd_branch(int argc, const char **argv, const char *prefix, struct repository *repo);

builtin/backfill.c

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#define USE_THE_REPOSITORY_VARIABLE /* for core_apply_sparse_checkout */
2+
3+
#include "builtin.h"
4+
#include "git-compat-util.h"
5+
#include "config.h"
6+
#include "parse-options.h"
7+
#include "repository.h"
8+
#include "commit.h"
9+
#include "dir.h"
10+
#include "environment.h"
11+
#include "hex.h"
12+
#include "tree.h"
13+
#include "tree-walk.h"
14+
#include "object.h"
15+
#include "object-store-ll.h"
16+
#include "oid-array.h"
17+
#include "oidset.h"
18+
#include "promisor-remote.h"
19+
#include "strmap.h"
20+
#include "string-list.h"
21+
#include "revision.h"
22+
#include "trace2.h"
23+
#include "progress.h"
24+
#include "packfile.h"
25+
#include "path-walk.h"
26+
27+
static const char * const builtin_backfill_usage[] = {
28+
N_("(EXPERIMENTAL) git backfill [--batch-size=<n>] [--[no-]sparse]"),
29+
NULL
30+
};
31+
32+
struct backfill_context {
33+
struct repository *repo;
34+
struct oid_array current_batch;
35+
size_t batch_size;
36+
int sparse;
37+
};
38+
39+
static void clear_backfill_context(struct backfill_context *ctx)
40+
{
41+
oid_array_clear(&ctx->current_batch);
42+
}
43+
44+
static void download_batch(struct backfill_context *ctx)
45+
{
46+
promisor_remote_get_direct(ctx->repo,
47+
ctx->current_batch.oid,
48+
ctx->current_batch.nr);
49+
oid_array_clear(&ctx->current_batch);
50+
51+
/*
52+
* We likely have a new packfile. Add it to the packed list to
53+
* avoid possible duplicate downloads of the same objects.
54+
*/
55+
reprepare_packed_git(ctx->repo);
56+
}
57+
58+
static int fill_missing_blobs(const char *path UNUSED,
59+
struct oid_array *list,
60+
enum object_type type,
61+
void *data)
62+
{
63+
struct backfill_context *ctx = data;
64+
65+
if (type != OBJ_BLOB)
66+
return 0;
67+
68+
for (size_t i = 0; i < list->nr; i++) {
69+
off_t size = 0;
70+
struct object_info info = OBJECT_INFO_INIT;
71+
info.disk_sizep = &size;
72+
if (oid_object_info_extended(ctx->repo,
73+
&list->oid[i],
74+
&info,
75+
OBJECT_INFO_FOR_PREFETCH) ||
76+
!size)
77+
oid_array_append(&ctx->current_batch, &list->oid[i]);
78+
}
79+
80+
if (ctx->current_batch.nr >= ctx->batch_size)
81+
download_batch(ctx);
82+
83+
return 0;
84+
}
85+
86+
static int do_backfill(struct backfill_context *ctx)
87+
{
88+
struct rev_info revs;
89+
struct path_walk_info info = PATH_WALK_INFO_INIT;
90+
int ret;
91+
92+
if (ctx->sparse) {
93+
CALLOC_ARRAY(info.pl, 1);
94+
if (get_sparse_checkout_patterns(info.pl)) {
95+
clear_pattern_list(info.pl);
96+
free(info.pl);
97+
return error(_("problem loading sparse-checkout"));
98+
}
99+
}
100+
101+
repo_init_revisions(ctx->repo, &revs, "");
102+
handle_revision_arg("HEAD", &revs, 0, 0);
103+
104+
info.blobs = 1;
105+
info.tags = info.commits = info.trees = 0;
106+
107+
info.revs = &revs;
108+
info.path_fn = fill_missing_blobs;
109+
info.path_fn_data = ctx;
110+
111+
ret = walk_objects_by_path(&info);
112+
113+
/* Download the objects that did not fill a batch. */
114+
if (!ret)
115+
download_batch(ctx);
116+
117+
clear_backfill_context(ctx);
118+
release_revisions(&revs);
119+
if (info.pl) {
120+
clear_pattern_list(info.pl);
121+
free(info.pl);
122+
}
123+
return ret;
124+
}
125+
126+
int cmd_backfill(int argc, const char **argv, const char *prefix, struct repository *repo)
127+
{
128+
struct backfill_context ctx = {
129+
.repo = repo,
130+
.current_batch = OID_ARRAY_INIT,
131+
.batch_size = 50000,
132+
.sparse = 0,
133+
};
134+
struct option options[] = {
135+
OPT_INTEGER(0, "batch-size", &ctx.batch_size,
136+
N_("Minimun number of objects to request at a time")),
137+
OPT_BOOL(0, "sparse", &ctx.sparse,
138+
N_("Restrict the missing objects to the current sparse-checkout")),
139+
OPT_END(),
140+
};
141+
142+
if (argc == 2 && !strcmp(argv[1], "-h"))
143+
usage_with_options(builtin_backfill_usage, options);
144+
145+
argc = parse_options(argc, argv, prefix, options, builtin_backfill_usage,
146+
0);
147+
148+
repo_config(repo, git_default_config, NULL);
149+
150+
if (ctx.sparse < 0)
151+
ctx.sparse = core_apply_sparse_checkout;
152+
153+
return do_backfill(&ctx);
154+
}

command-list.txt

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ git-annotate ancillaryinterrogators
6060
git-apply plumbingmanipulators complete
6161
git-archimport foreignscminterface
6262
git-archive mainporcelain
63+
git-backfill mainporcelain history
6364
git-bisect mainporcelain info
6465
git-blame ancillaryinterrogators complete
6566
git-branch mainporcelain history

dir.c

+3-7
Original file line numberDiff line numberDiff line change
@@ -1093,10 +1093,6 @@ static void invalidate_directory(struct untracked_cache *uc,
10931093
dir->dirs[i]->recurse = 0;
10941094
}
10951095

1096-
static int add_patterns_from_buffer(char *buf, size_t size,
1097-
const char *base, int baselen,
1098-
struct pattern_list *pl);
1099-
11001096
/* Flags for add_patterns() */
11011097
#define PATTERN_NOFOLLOW (1<<0)
11021098

@@ -1186,9 +1182,9 @@ static int add_patterns(const char *fname, const char *base, int baselen,
11861182
return 0;
11871183
}
11881184

1189-
static int add_patterns_from_buffer(char *buf, size_t size,
1190-
const char *base, int baselen,
1191-
struct pattern_list *pl)
1185+
int add_patterns_from_buffer(char *buf, size_t size,
1186+
const char *base, int baselen,
1187+
struct pattern_list *pl)
11921188
{
11931189
char *orig = buf;
11941190
int i, lineno = 1;

dir.h

+3
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,9 @@ void add_patterns_from_file(struct dir_struct *, const char *fname);
467467
int add_patterns_from_blob_to_list(struct object_id *oid,
468468
const char *base, int baselen,
469469
struct pattern_list *pl);
470+
int add_patterns_from_buffer(char *buf, size_t size,
471+
const char *base, int baselen,
472+
struct pattern_list *pl);
470473
void parse_path_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
471474
void add_pattern(const char *string, const char *base,
472475
int baselen, struct pattern_list *pl, int srcpos);

git.c

+1
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ static struct cmd_struct commands[] = {
506506
{ "annotate", cmd_annotate, RUN_SETUP },
507507
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
508508
{ "archive", cmd_archive, RUN_SETUP_GENTLY },
509+
{ "backfill", cmd_backfill, RUN_SETUP },
509510
{ "bisect", cmd_bisect, RUN_SETUP },
510511
{ "blame", cmd_blame, RUN_SETUP },
511512
{ "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ builtin_sources = [
487487
'builtin/annotate.c',
488488
'builtin/apply.c',
489489
'builtin/archive.c',
490+
'builtin/backfill.c',
490491
'builtin/bisect.c',
491492
'builtin/blame.c',
492493
'builtin/branch.c',

path-walk.c

+18
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "hex.h"
1111
#include "object.h"
1212
#include "oid-array.h"
13+
#include "repository.h"
1314
#include "revision.h"
1415
#include "string-list.h"
1516
#include "strmap.h"
@@ -119,6 +120,23 @@ static int add_children(struct path_walk_context *ctx,
119120
if (type == OBJ_TREE)
120121
strbuf_addch(&path, '/');
121122

123+
if (ctx->info->pl) {
124+
int dtype;
125+
enum pattern_match_result match;
126+
match = path_matches_pattern_list(path.buf, path.len,
127+
path.buf + base_len, &dtype,
128+
ctx->info->pl,
129+
ctx->repo->index);
130+
131+
if (ctx->info->pl->use_cone_patterns &&
132+
match == NOT_MATCHED)
133+
continue;
134+
else if (!ctx->info->pl->use_cone_patterns &&
135+
type == OBJ_BLOB &&
136+
match != MATCHED)
137+
continue;
138+
}
139+
122140
if (!(list = strmap_get(&ctx->paths_to_lists, path.buf))) {
123141
CALLOC_ARRAY(list, 1);
124142
list->type = type;

path-walk.h

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
struct rev_info;
88
struct oid_array;
9+
struct pattern_list;
910

1011
/**
1112
* The type of a function pointer for the method that is called on a list of
@@ -46,6 +47,16 @@ struct path_walk_info {
4647
* walk the children of such trees.
4748
*/
4849
int prune_all_uninteresting;
50+
51+
/**
52+
* Specify a sparse-checkout definition to match our paths to. Do not
53+
* walk outside of this sparse definition. If the patterns are in
54+
* cone mode, then the search may prune directories that are outside
55+
* of the cone. If not in cone mode, then all tree paths will be
56+
* explored but the path_fn will only be called when the path matches
57+
* the sparse-checkout patterns.
58+
*/
59+
struct pattern_list *pl;
4960
};
5061

5162
#define PATH_WALK_INFO_INIT { \

0 commit comments

Comments
 (0)