Skip to content

Commit fa46579

Browse files
committed
Merge branch 'jk/repository-extension'
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 9d4a069 + 067fbd4 commit fa46579

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
@@ -394,15 +394,17 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
394394
if (gc_before_repack())
395395
return -1;
396396

397-
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
398-
return error(FAILED_RUN, repack.argv[0]);
399-
400-
if (prune_expire) {
401-
argv_array_push(&prune, prune_expire);
402-
if (quiet)
403-
argv_array_push(&prune, "--no-progress");
404-
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
405-
return error(FAILED_RUN, prune.argv[0]);
397+
if (!repository_format_precious_objects) {
398+
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
399+
return error(FAILED_RUN, repack.argv[0]);
400+
401+
if (prune_expire) {
402+
argv_array_push(&prune, prune_expire);
403+
if (quiet)
404+
argv_array_push(&prune, "--no-progress");
405+
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
406+
return error(FAILED_RUN, prune.argv[0]);
407+
}
406408
}
407409

408410
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
@@ -697,8 +697,15 @@ extern char *notes_ref_name;
697697

698698
extern int grafts_replace_parents;
699699

700+
/*
701+
* GIT_REPO_VERSION is the version we write by default. The
702+
* _READ variant is the highest number we know how to
703+
* handle.
704+
*/
700705
#define GIT_REPO_VERSION 0
706+
#define GIT_REPO_VERSION_READ 1
701707
extern int repository_format_version;
708+
extern int repository_format_precious_objects;
702709
extern int check_repository_format(void);
703710

704711
#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
@@ -356,10 +357,25 @@ void setup_work_tree(void)
356357

357358
static int check_repo_format(const char *var, const char *value, void *cb)
358359
{
360+
const char *ext;
361+
359362
if (strcmp(var, "core.repositoryformatversion") == 0)
360363
repository_format_version = git_config_int(var, value);
361364
else if (strcmp(var, "core.sharedrepository") == 0)
362365
shared_repository = git_config_perm(var, value);
366+
else if (skip_prefix(var, "extensions.", &ext)) {
367+
/*
368+
* record any known extensions here; otherwise,
369+
* we fall through to recording it as unknown, and
370+
* check_repository_format will complain
371+
*/
372+
if (!strcmp(ext, "noop"))
373+
;
374+
else if (!strcmp(ext, "preciousobjects"))
375+
repository_format_precious_objects = git_config_bool(var, value);
376+
else
377+
string_list_append(&unknown_extensions, ext);
378+
}
363379
return 0;
364380
}
365381

@@ -370,6 +386,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
370386
config_fn_t fn;
371387
int ret = 0;
372388

389+
string_list_clear(&unknown_extensions, 0);
390+
373391
if (get_common_dir(&sb, gitdir))
374392
fn = check_repo_format;
375393
else
@@ -387,16 +405,31 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
387405
* is a good one.
388406
*/
389407
git_config_early(fn, NULL, repo_config);
390-
if (GIT_REPO_VERSION < repository_format_version) {
408+
if (GIT_REPO_VERSION_READ < repository_format_version) {
391409
if (!nongit_ok)
392410
die ("Expected git repo version <= %d, found %d",
393-
GIT_REPO_VERSION, repository_format_version);
411+
GIT_REPO_VERSION_READ, repository_format_version);
394412
warning("Expected git repo version <= %d, found %d",
395-
GIT_REPO_VERSION, repository_format_version);
413+
GIT_REPO_VERSION_READ, repository_format_version);
396414
warning("Please upgrade Git");
397415
*nongit_ok = -1;
398416
ret = -1;
399417
}
418+
419+
if (repository_format_version >= 1 && unknown_extensions.nr) {
420+
int i;
421+
422+
if (!nongit_ok)
423+
die("unknown repository extension: %s",
424+
unknown_extensions.items[0].string);
425+
426+
for (i = 0; i < unknown_extensions.nr; i++)
427+
warning("unknown repository extension: %s",
428+
unknown_extensions.items[i].string);
429+
*nongit_ok = -1;
430+
ret = -1;
431+
}
432+
400433
strbuf_release(&sb);
401434
return ret;
402435
}

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)