Skip to content

Commit

Permalink
Merge branch 'jk/repository-extension'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Oct 26, 2015
2 parents 9d4a069 + 067fbd4 commit fa46579
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 12 deletions.
88 changes: 88 additions & 0 deletions Documentation/technical/repository-version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Git Repository Format Versions
==============================

Every git repository is marked with a numeric version in the
`core.repositoryformatversion` key of its `config` file. This version
specifies the rules for operating on the on-disk repository data. An
implementation of git which does not understand a particular version
advertised by an on-disk repository MUST NOT operate on that repository;
doing so risks not only producing wrong results, but actually losing
data.

Because of this rule, version bumps should be kept to an absolute
minimum. Instead, we generally prefer these strategies:

- bumping format version numbers of individual data files (e.g.,
index, packfiles, etc). This restricts the incompatibilities only to
those files.

- introducing new data that gracefully degrades when used by older
clients (e.g., pack bitmap files are ignored by older clients, which
simply do not take advantage of the optimization they provide).

A whole-repository format version bump should only be part of a change
that cannot be independently versioned. For instance, if one were to
change the reachability rules for objects, or the rules for locking
refs, that would require a bump of the repository format version.

Note that this applies only to accessing the repository's disk contents
directly. An older client which understands only format `0` may still
connect via `git://` to a repository using format `1`, as long as the
server process understands format `1`.

The preferred strategy for rolling out a version bump (whether whole
repository or for a single file) is to teach git to read the new format,
and allow writing the new format with a config switch or command line
option (for experimentation or for those who do not care about backwards
compatibility with older gits). Then after a long period to allow the
reading capability to become common, we may switch to writing the new
format by default.

The currently defined format versions are:

Version `0`
-----------

This is the format defined by the initial version of git, including but
not limited to the format of the repository directory, the repository
configuration file, and the object and ref storage. Specifying the
complete behavior of git is beyond the scope of this document.

Version `1`
-----------

This format is identical to version `0`, with the following exceptions:

1. When reading the `core.repositoryformatversion` variable, a git
implementation which supports version 1 MUST also read any
configuration keys found in the `extensions` section of the
configuration file.

2. If a version-1 repository specifies any `extensions.*` keys that
the running git has not implemented, the operation MUST NOT
proceed. Similarly, if the value of any known key is not understood
by the implementation, the operation MUST NOT proceed.

Note that if no extensions are specified in the config file, then
`core.repositoryformatversion` SHOULD be set to `0` (setting it to `1`
provides no benefit, and makes the repository incompatible with older
implementations of git).

This document will serve as the master list for extensions. Any
implementation wishing to define a new extension should make a note of
it here, in order to claim the name.

The defined extensions are:

`noop`
~~~~~~

This extension does not change git's behavior at all. It is useful only
for testing format-1 compatibility.

`preciousObjects`
~~~~~~~~~~~~~~~~~

When the config key `extensions.preciousObjects` is set to `true`,
objects in the repository MUST NOT be deleted (e.g., by `git-prune` or
`git repack -d`).
20 changes: 11 additions & 9 deletions builtin/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,15 +394,17 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (gc_before_repack())
return -1;

if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
return error(FAILED_RUN, repack.argv[0]);

if (prune_expire) {
argv_array_push(&prune, prune_expire);
if (quiet)
argv_array_push(&prune, "--no-progress");
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
return error(FAILED_RUN, prune.argv[0]);
if (!repository_format_precious_objects) {
if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
return error(FAILED_RUN, repack.argv[0]);

if (prune_expire) {
argv_array_push(&prune, prune_expire);
if (quiet)
argv_array_push(&prune, "--no-progress");
if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
return error(FAILED_RUN, prune.argv[0]);
}
}

if (prune_worktrees_expire) {
Expand Down
3 changes: 3 additions & 0 deletions builtin/prune.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix)

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

if (repository_format_precious_objects)
die(_("cannot prune in a precious-objects repo"));

while (argc--) {
unsigned char sha1[20];
const char *name = *argv++;
Expand Down
3 changes: 3 additions & 0 deletions builtin/repack.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, builtin_repack_options,
git_repack_usage, 0);

if (delete_redundant && repository_format_precious_objects)
die(_("cannot delete packs in a precious-objects repo"));

if (pack_kept_objects < 0)
pack_kept_objects = write_bitmaps;

Expand Down
7 changes: 7 additions & 0 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,15 @@ extern char *notes_ref_name;

extern int grafts_replace_parents;

/*
* GIT_REPO_VERSION is the version we write by default. The
* _READ variant is the highest number we know how to
* handle.
*/
#define GIT_REPO_VERSION 0
#define GIT_REPO_VERSION_READ 1
extern int repository_format_version;
extern int repository_format_precious_objects;
extern int check_repository_format(void);

#define MTIME_CHANGED 0x0001
Expand Down
1 change: 1 addition & 0 deletions environment.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ int warn_ambiguous_refs = 1;
int warn_on_object_refname_ambiguity = 1;
int ref_paranoia = -1;
int repository_format_version;
int repository_format_precious_objects;
const char *git_commit_encoding;
const char *git_log_output_encoding;
int shared_repository = PERM_UMASK;
Expand Down
39 changes: 36 additions & 3 deletions setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
static int inside_git_dir = -1;
static int inside_work_tree = -1;
static int work_tree_config_is_bogus;
static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;

/*
* The input parameter must contain an absolute path, and it must already be
Expand Down Expand Up @@ -356,10 +357,25 @@ void setup_work_tree(void)

static int check_repo_format(const char *var, const char *value, void *cb)
{
const char *ext;

if (strcmp(var, "core.repositoryformatversion") == 0)
repository_format_version = git_config_int(var, value);
else if (strcmp(var, "core.sharedrepository") == 0)
shared_repository = git_config_perm(var, value);
else if (skip_prefix(var, "extensions.", &ext)) {
/*
* record any known extensions here; otherwise,
* we fall through to recording it as unknown, and
* check_repository_format will complain
*/
if (!strcmp(ext, "noop"))
;
else if (!strcmp(ext, "preciousobjects"))
repository_format_precious_objects = git_config_bool(var, value);
else
string_list_append(&unknown_extensions, ext);
}
return 0;
}

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

string_list_clear(&unknown_extensions, 0);

if (get_common_dir(&sb, gitdir))
fn = check_repo_format;
else
Expand All @@ -387,16 +405,31 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
* is a good one.
*/
git_config_early(fn, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (GIT_REPO_VERSION_READ < repository_format_version) {
if (!nongit_ok)
die ("Expected git repo version <= %d, found %d",
GIT_REPO_VERSION, repository_format_version);
GIT_REPO_VERSION_READ, repository_format_version);
warning("Expected git repo version <= %d, found %d",
GIT_REPO_VERSION, repository_format_version);
GIT_REPO_VERSION_READ, repository_format_version);
warning("Please upgrade Git");
*nongit_ok = -1;
ret = -1;
}

if (repository_format_version >= 1 && unknown_extensions.nr) {
int i;

if (!nongit_ok)
die("unknown repository extension: %s",
unknown_extensions.items[0].string);

for (i = 0; i < unknown_extensions.nr; i++)
warning("unknown repository extension: %s",
unknown_extensions.items[i].string);
*nongit_ok = -1;
ret = -1;
}

strbuf_release(&sb);
return ret;
}
Expand Down
60 changes: 60 additions & 0 deletions t/t1302-repo-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,64 @@ test_expect_success 'gitdir required mode' '
)
'

check_allow () {
git rev-parse --git-dir >actual &&
echo .git >expect &&
test_cmp expect actual
}

check_abort () {
test_must_fail git rev-parse --git-dir
}

# avoid git-config, since it cannot be trusted to run
# in a repository with a broken version
mkconfig () {
echo '[core]' &&
echo "repositoryformatversion = $1" &&
shift &&

if test $# -gt 0; then
echo '[extensions]' &&
for i in "$@"; do
echo "$i"
done
fi
}

while read outcome version extensions; do
test_expect_success "$outcome version=$version $extensions" "
mkconfig $version $extensions >.git/config &&
check_${outcome}
"
done <<\EOF
allow 0
allow 1
allow 1 noop
abort 1 no-such-extension
allow 0 no-such-extension
EOF

test_expect_success 'precious-objects allowed' '
mkconfig 1 preciousObjects >.git/config &&
check_allow
'

test_expect_success 'precious-objects blocks destructive repack' '
test_must_fail git repack -ad
'

test_expect_success 'other repacks are OK' '
test_commit foo &&
git repack
'

test_expect_success 'precious-objects blocks prune' '
test_must_fail git prune
'

test_expect_success 'gc runs without complaint' '
git gc
'

test_done

0 comments on commit fa46579

Please sign in to comment.