Skip to content

Commit f89baca

Browse files
committed
Merge branch 'jk/repository-extension' into maint
Prepare for Git on-disk repository representation to undergo backward incompatible changes by introducing a new repository format version "1", with an extension mechanism. * jk/repository-extension: introduce "preciousObjects" repository extension introduce "extensions" form of core.repositoryformatversion
2 parents b05c2f9 + 067fbd4 commit f89baca

File tree

8 files changed

+209
-12
lines changed

8 files changed

+209
-12
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
Git Repository Format Versions
2+
==============================
3+
4+
Every git repository is marked with a numeric version in the
5+
`core.repositoryformatversion` key of its `config` file. This version
6+
specifies the rules for operating on the on-disk repository data. An
7+
implementation of git which does not understand a particular version
8+
advertised by an on-disk repository MUST NOT operate on that repository;
9+
doing so risks not only producing wrong results, but actually losing
10+
data.
11+
12+
Because of this rule, version bumps should be kept to an absolute
13+
minimum. Instead, we generally prefer these strategies:
14+
15+
- bumping format version numbers of individual data files (e.g.,
16+
index, packfiles, etc). This restricts the incompatibilities only to
17+
those files.
18+
19+
- introducing new data that gracefully degrades when used by older
20+
clients (e.g., pack bitmap files are ignored by older clients, which
21+
simply do not take advantage of the optimization they provide).
22+
23+
A whole-repository format version bump should only be part of a change
24+
that cannot be independently versioned. For instance, if one were to
25+
change the reachability rules for objects, or the rules for locking
26+
refs, that would require a bump of the repository format version.
27+
28+
Note that this applies only to accessing the repository's disk contents
29+
directly. An older client which understands only format `0` may still
30+
connect via `git://` to a repository using format `1`, as long as the
31+
server process understands format `1`.
32+
33+
The preferred strategy for rolling out a version bump (whether whole
34+
repository or for a single file) is to teach git to read the new format,
35+
and allow writing the new format with a config switch or command line
36+
option (for experimentation or for those who do not care about backwards
37+
compatibility with older gits). Then after a long period to allow the
38+
reading capability to become common, we may switch to writing the new
39+
format by default.
40+
41+
The currently defined format versions are:
42+
43+
Version `0`
44+
-----------
45+
46+
This is the format defined by the initial version of git, including but
47+
not limited to the format of the repository directory, the repository
48+
configuration file, and the object and ref storage. Specifying the
49+
complete behavior of git is beyond the scope of this document.
50+
51+
Version `1`
52+
-----------
53+
54+
This format is identical to version `0`, with the following exceptions:
55+
56+
1. When reading the `core.repositoryformatversion` variable, a git
57+
implementation which supports version 1 MUST also read any
58+
configuration keys found in the `extensions` section of the
59+
configuration file.
60+
61+
2. If a version-1 repository specifies any `extensions.*` keys that
62+
the running git has not implemented, the operation MUST NOT
63+
proceed. Similarly, if the value of any known key is not understood
64+
by the implementation, the operation MUST NOT proceed.
65+
66+
Note that if no extensions are specified in the config file, then
67+
`core.repositoryformatversion` SHOULD be set to `0` (setting it to `1`
68+
provides no benefit, and makes the repository incompatible with older
69+
implementations of git).
70+
71+
This document will serve as the master list for extensions. Any
72+
implementation wishing to define a new extension should make a note of
73+
it here, in order to claim the name.
74+
75+
The defined extensions are:
76+
77+
`noop`
78+
~~~~~~
79+
80+
This extension does not change git's behavior at all. It is useful only
81+
for testing format-1 compatibility.
82+
83+
`preciousObjects`
84+
~~~~~~~~~~~~~~~~~
85+
86+
When the config key `extensions.preciousObjects` is set to `true`,
87+
objects in the repository MUST NOT be deleted (e.g., by `git-prune` or
88+
`git repack -d`).

builtin/gc.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -340,15 +340,17 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
340340
if (gc_before_repack())
341341
return -1;
342342

343-
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
344-
return error(FAILED_RUN, repack.argv[0]);
345-
346-
if (prune_expire) {
347-
argv_array_push(&prune, prune_expire);
348-
if (quiet)
349-
argv_array_push(&prune, "--no-progress");
350-
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
351-
return error(FAILED_RUN, prune.argv[0]);
343+
if (!repository_format_precious_objects) {
344+
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
345+
return error(FAILED_RUN, repack.argv[0]);
346+
347+
if (prune_expire) {
348+
argv_array_push(&prune, prune_expire);
349+
if (quiet)
350+
argv_array_push(&prune, "--no-progress");
351+
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
352+
return error(FAILED_RUN, prune.argv[0]);
353+
}
352354
}
353355

354356
if (prune_worktrees_expire) {

builtin/prune.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
119119

120120
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
121121

122+
if (repository_format_precious_objects)
123+
die(_("cannot prune in a precious-objects repo"));
124+
122125
while (argc--) {
123126
unsigned char sha1[20];
124127
const char *name = *argv++;

builtin/repack.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
193193
argc = parse_options(argc, argv, prefix, builtin_repack_options,
194194
git_repack_usage, 0);
195195

196+
if (delete_redundant && repository_format_precious_objects)
197+
die(_("cannot delete packs in a precious-objects repo"));
198+
196199
if (pack_kept_objects < 0)
197200
pack_kept_objects = write_bitmaps;
198201

cache.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,15 @@ extern char *notes_ref_name;
696696

697697
extern int grafts_replace_parents;
698698

699+
/*
700+
* GIT_REPO_VERSION is the version we write by default. The
701+
* _READ variant is the highest number we know how to
702+
* handle.
703+
*/
699704
#define GIT_REPO_VERSION 0
705+
#define GIT_REPO_VERSION_READ 1
700706
extern int repository_format_version;
707+
extern int repository_format_precious_objects;
701708
extern int check_repository_format(void);
702709

703710
#define MTIME_CHANGED 0x0001

environment.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ int warn_ambiguous_refs = 1;
2626
int warn_on_object_refname_ambiguity = 1;
2727
int ref_paranoia = -1;
2828
int repository_format_version;
29+
int repository_format_precious_objects;
2930
const char *git_commit_encoding;
3031
const char *git_log_output_encoding;
3132
int shared_repository = PERM_UMASK;

setup.c

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
static int inside_git_dir = -1;
66
static int inside_work_tree = -1;
77
static int work_tree_config_is_bogus;
8+
static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;
89

910
/*
1011
* The input parameter must contain an absolute path, and it must already be
@@ -352,10 +353,25 @@ void setup_work_tree(void)
352353

353354
static int check_repo_format(const char *var, const char *value, void *cb)
354355
{
356+
const char *ext;
357+
355358
if (strcmp(var, "core.repositoryformatversion") == 0)
356359
repository_format_version = git_config_int(var, value);
357360
else if (strcmp(var, "core.sharedrepository") == 0)
358361
shared_repository = git_config_perm(var, value);
362+
else if (skip_prefix(var, "extensions.", &ext)) {
363+
/*
364+
* record any known extensions here; otherwise,
365+
* we fall through to recording it as unknown, and
366+
* check_repository_format will complain
367+
*/
368+
if (!strcmp(ext, "noop"))
369+
;
370+
else if (!strcmp(ext, "preciousobjects"))
371+
repository_format_precious_objects = git_config_bool(var, value);
372+
else
373+
string_list_append(&unknown_extensions, ext);
374+
}
359375
return 0;
360376
}
361377

@@ -366,6 +382,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
366382
config_fn_t fn;
367383
int ret = 0;
368384

385+
string_list_clear(&unknown_extensions, 0);
386+
369387
if (get_common_dir(&sb, gitdir))
370388
fn = check_repo_format;
371389
else
@@ -383,16 +401,31 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
383401
* is a good one.
384402
*/
385403
git_config_early(fn, NULL, repo_config);
386-
if (GIT_REPO_VERSION < repository_format_version) {
404+
if (GIT_REPO_VERSION_READ < repository_format_version) {
387405
if (!nongit_ok)
388406
die ("Expected git repo version <= %d, found %d",
389-
GIT_REPO_VERSION, repository_format_version);
407+
GIT_REPO_VERSION_READ, repository_format_version);
390408
warning("Expected git repo version <= %d, found %d",
391-
GIT_REPO_VERSION, repository_format_version);
409+
GIT_REPO_VERSION_READ, repository_format_version);
392410
warning("Please upgrade Git");
393411
*nongit_ok = -1;
394412
ret = -1;
395413
}
414+
415+
if (repository_format_version >= 1 && unknown_extensions.nr) {
416+
int i;
417+
418+
if (!nongit_ok)
419+
die("unknown repository extension: %s",
420+
unknown_extensions.items[0].string);
421+
422+
for (i = 0; i < unknown_extensions.nr; i++)
423+
warning("unknown repository extension: %s",
424+
unknown_extensions.items[i].string);
425+
*nongit_ok = -1;
426+
ret = -1;
427+
}
428+
396429
strbuf_release(&sb);
397430
return ret;
398431
}

t/t1302-repo-version.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,64 @@ test_expect_success 'gitdir required mode' '
6767
)
6868
'
6969

70+
check_allow () {
71+
git rev-parse --git-dir >actual &&
72+
echo .git >expect &&
73+
test_cmp expect actual
74+
}
75+
76+
check_abort () {
77+
test_must_fail git rev-parse --git-dir
78+
}
79+
80+
# avoid git-config, since it cannot be trusted to run
81+
# in a repository with a broken version
82+
mkconfig () {
83+
echo '[core]' &&
84+
echo "repositoryformatversion = $1" &&
85+
shift &&
86+
87+
if test $# -gt 0; then
88+
echo '[extensions]' &&
89+
for i in "$@"; do
90+
echo "$i"
91+
done
92+
fi
93+
}
94+
95+
while read outcome version extensions; do
96+
test_expect_success "$outcome version=$version $extensions" "
97+
mkconfig $version $extensions >.git/config &&
98+
check_${outcome}
99+
"
100+
done <<\EOF
101+
allow 0
102+
allow 1
103+
allow 1 noop
104+
abort 1 no-such-extension
105+
allow 0 no-such-extension
106+
EOF
107+
108+
test_expect_success 'precious-objects allowed' '
109+
mkconfig 1 preciousObjects >.git/config &&
110+
check_allow
111+
'
112+
113+
test_expect_success 'precious-objects blocks destructive repack' '
114+
test_must_fail git repack -ad
115+
'
116+
117+
test_expect_success 'other repacks are OK' '
118+
test_commit foo &&
119+
git repack
120+
'
121+
122+
test_expect_success 'precious-objects blocks prune' '
123+
test_must_fail git prune
124+
'
125+
126+
test_expect_success 'gc runs without complaint' '
127+
git gc
128+
'
129+
70130
test_done

0 commit comments

Comments
 (0)