Merge branch 'ps/hash-and-ref-format-from-config'

The default object hash and ref backend format used to be settable
only with explicit command line option to "git init" and
environment variables, but now they can be configured in the user's
global and system wide configuration.

* ps/hash-and-ref-format-from-config:
  setup: make ref storage format configurable via config
  setup: make object format configurable via config
  setup: merge configuration of repository formats
  t0001: delete repositories when object format tests finish
  t0001: exercise initialization with ref formats more thoroughly
maint
Junio C Hamano 2024-08-23 09:02:35 -07:00
commit 2cf9c2206c
3 changed files with 216 additions and 40 deletions

View File

@ -8,3 +8,13 @@ endif::[]
`init.defaultBranch`::
Allows overriding the default branch name e.g. when initializing
a new repository.
`init.defaultObjectFormat`::
Allows overriding the default object format for new repositories. See
`--object-format=` in linkgit:git-init[1]. Both the command line option
and the `GIT_DEFAULT_HASH` environment variable take precedence over
this config.
`init.defaultRefFormat`::
Allows overriding the default ref storage format for new repositories.
See `--ref-format=` in linkgit:git-init[1]. Both the command line
option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
precedence over this config.

101
setup.c
View File

@ -2320,14 +2320,67 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
write_file(git_link, "gitdir: %s", git_dir);
}

static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash)
struct default_format_config {
int hash;
enum ref_storage_format ref_format;
};

static int read_default_format_config(const char *key, const char *value,
const struct config_context *ctx UNUSED,
void *payload)
{
const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT);
struct default_format_config *cfg = payload;
char *str = NULL;
int ret;

if (!strcmp(key, "init.defaultobjectformat")) {
ret = git_config_string(&str, key, value);
if (ret)
goto out;
cfg->hash = hash_algo_by_name(str);
if (cfg->hash == GIT_HASH_UNKNOWN)
warning(_("unknown hash algorithm '%s'"), str);
goto out;
}

if (!strcmp(key, "init.defaultrefformat")) {
ret = git_config_string(&str, key, value);
if (ret)
goto out;
cfg->ref_format = ref_storage_format_by_name(str);
if (cfg->ref_format == REF_STORAGE_FORMAT_UNKNOWN)
warning(_("unknown ref storage format '%s'"), str);
goto out;
}

ret = 0;
out:
free(str);
return ret;
}

static void repository_format_configure(struct repository_format *repo_fmt,
int hash, enum ref_storage_format ref_format)
{
struct default_format_config cfg = {
.hash = GIT_HASH_UNKNOWN,
.ref_format = REF_STORAGE_FORMAT_UNKNOWN,
};
struct config_options opts = {
.respect_includes = 1,
.ignore_repo = 1,
.ignore_worktree = 1,
};
const char *env;

config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);

/*
* If we already have an initialized repo, don't allow the user to
* specify a different algorithm, as that could cause corruption.
* Otherwise, if the user has specified one on the command line, use it.
*/
env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT);
if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo)
die(_("attempt to reinitialize repository with different hash"));
else if (hash != GIT_HASH_UNKNOWN)
@ -2337,26 +2390,27 @@ static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash
if (env_algo == GIT_HASH_UNKNOWN)
die(_("unknown hash algorithm '%s'"), env);
repo_fmt->hash_algo = env_algo;
} else if (cfg.hash != GIT_HASH_UNKNOWN) {
repo_fmt->hash_algo = cfg.hash;
}
}

static void validate_ref_storage_format(struct repository_format *repo_fmt,
enum ref_storage_format format)
{
const char *name = getenv("GIT_DEFAULT_REF_FORMAT");
repo_set_hash_algo(the_repository, repo_fmt->hash_algo);

env = getenv("GIT_DEFAULT_REF_FORMAT");
if (repo_fmt->version >= 0 &&
format != REF_STORAGE_FORMAT_UNKNOWN &&
format != repo_fmt->ref_storage_format) {
ref_format != REF_STORAGE_FORMAT_UNKNOWN &&
ref_format != repo_fmt->ref_storage_format) {
die(_("attempt to reinitialize repository with different reference storage format"));
} else if (format != REF_STORAGE_FORMAT_UNKNOWN) {
repo_fmt->ref_storage_format = format;
} else if (name) {
format = ref_storage_format_by_name(name);
if (format == REF_STORAGE_FORMAT_UNKNOWN)
die(_("unknown ref storage format '%s'"), name);
repo_fmt->ref_storage_format = format;
} else if (ref_format != REF_STORAGE_FORMAT_UNKNOWN) {
repo_fmt->ref_storage_format = ref_format;
} else if (env) {
ref_format = ref_storage_format_by_name(env);
if (ref_format == REF_STORAGE_FORMAT_UNKNOWN)
die(_("unknown ref storage format '%s'"), env);
repo_fmt->ref_storage_format = ref_format;
} else if (cfg.ref_format != REF_STORAGE_FORMAT_UNKNOWN) {
repo_fmt->ref_storage_format = cfg.ref_format;
}
repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format);
}

int init_db(const char *git_dir, const char *real_git_dir,
@ -2389,22 +2443,15 @@ int init_db(const char *git_dir, const char *real_git_dir,
}
startup_info->have_repository = 1;

/* Check to see if the repository version is right.
/*
* Check to see if the repository version is right.
* Note that a newly created repository does not have
* config file, so this will not fail. What we are catching
* is an attempt to reinitialize new repository with an old tool.
*/
check_repository_format(&repo_fmt);

validate_hash_algorithm(&repo_fmt, hash);
validate_ref_storage_format(&repo_fmt, ref_storage_format);

/*
* Now that we have set up both the hash algorithm and the ref storage
* format we can update the repository's settings accordingly.
*/
repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
repo_set_ref_storage_format(the_repository, repo_fmt.ref_storage_format);
repository_format_configure(&repo_fmt, hash, ref_storage_format);

/*
* Ensure `core.hidedotfiles` is processed. This must happen after we

View File

@ -500,6 +500,7 @@ test_expect_success 're-init from a linked worktree' '
'

test_expect_success 'init honors GIT_DEFAULT_HASH' '
test_when_finished "rm -rf sha1 sha256" &&
GIT_DEFAULT_HASH=sha1 git init sha1 &&
git -C sha1 rev-parse --show-object-format >actual &&
echo sha1 >expected &&
@ -511,6 +512,7 @@ test_expect_success 'init honors GIT_DEFAULT_HASH' '
'

test_expect_success 'init honors --object-format' '
test_when_finished "rm -rf explicit-sha1 explicit-sha256" &&
git init --object-format=sha1 explicit-sha1 &&
git -C explicit-sha1 rev-parse --show-object-format >actual &&
echo sha1 >expected &&
@ -521,7 +523,58 @@ test_expect_success 'init honors --object-format' '
test_cmp expected actual
'

test_expect_success 'init honors init.defaultObjectFormat' '
test_when_finished "rm -rf sha1 sha256" &&

test_config_global init.defaultObjectFormat sha1 &&
(
sane_unset GIT_DEFAULT_HASH &&
git init sha1 &&
git -C sha1 rev-parse --show-object-format >actual &&
echo sha1 >expected &&
test_cmp expected actual
) &&

test_config_global init.defaultObjectFormat sha256 &&
(
sane_unset GIT_DEFAULT_HASH &&
git init sha256 &&
git -C sha256 rev-parse --show-object-format >actual &&
echo sha256 >expected &&
test_cmp expected actual
)
'

test_expect_success 'init warns about invalid init.defaultObjectFormat' '
test_when_finished "rm -rf repo" &&
test_config_global init.defaultObjectFormat garbage &&

echo "warning: unknown hash algorithm ${SQ}garbage${SQ}" >expect &&
git init repo 2>err &&
test_cmp expect err &&

git -C repo rev-parse --show-object-format >actual &&
echo $GIT_DEFAULT_HASH >expected &&
test_cmp expected actual
'

test_expect_success '--object-format overrides GIT_DEFAULT_HASH' '
test_when_finished "rm -rf repo" &&
GIT_DEFAULT_HASH=sha1 git init --object-format=sha256 repo &&
git -C repo rev-parse --show-object-format >actual &&
echo sha256 >expected
'

test_expect_success 'GIT_DEFAULT_HASH overrides init.defaultObjectFormat' '
test_when_finished "rm -rf repo" &&
test_config_global init.defaultObjectFormat sha1 &&
GIT_DEFAULT_HASH=sha256 git init repo &&
git -C repo rev-parse --show-object-format >actual &&
echo sha256 >expected
'

test_expect_success 'extensions.objectFormat is not allowed with repo version 0' '
test_when_finished "rm -rf explicit-v0" &&
git init --object-format=sha256 explicit-v0 &&
git -C explicit-v0 config core.repositoryformatversion 0 &&
test_must_fail git -C explicit-v0 rev-parse --show-object-format
@ -558,15 +611,6 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown back
grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
'

test_expect_success DEFAULT_REPO_FORMAT 'init with GIT_DEFAULT_REF_FORMAT=files' '
test_when_finished "rm -rf refformat" &&
GIT_DEFAULT_REF_FORMAT=files git init refformat &&
echo 0 >expect &&
git -C refformat config core.repositoryformatversion >actual &&
test_cmp expect actual &&
test_must_fail git -C refformat config extensions.refstorage
'

test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
test_when_finished "rm -rf refformat" &&
cat >expect <<-EOF &&
@ -576,15 +620,90 @@ test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
test_cmp expect err
'

test_expect_success 'init with --ref-format=files' '
test_expect_success 'init warns about invalid init.defaultRefFormat' '
test_when_finished "rm -rf repo" &&
test_config_global init.defaultRefFormat garbage &&

echo "warning: unknown ref storage format ${SQ}garbage${SQ}" >expect &&
git init repo 2>err &&
test_cmp expect err &&

git -C repo rev-parse --show-ref-format >actual &&
echo $GIT_DEFAULT_REF_FORMAT >expected &&
test_cmp expected actual
'

backends="files reftable"
for format in $backends
do
test_expect_success DEFAULT_REPO_FORMAT "init with GIT_DEFAULT_REF_FORMAT=$format" '
test_when_finished "rm -rf refformat" &&
git init --ref-format=files refformat &&
echo files >expect &&
GIT_DEFAULT_REF_FORMAT=$format git init refformat &&

if test $format = files
then
test_must_fail git -C refformat config extensions.refstorage &&
echo 0 >expect
else
git -C refformat config extensions.refstorage &&
echo 1 >expect
fi &&
git -C refformat config core.repositoryformatversion >actual &&
test_cmp expect actual &&

echo $format >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'

test_expect_success "init with --ref-format=$format" '
test_when_finished "rm -rf refformat" &&
git init --ref-format=$format refformat &&
echo $format >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'

test_expect_success "init with init.defaultRefFormat=$format" '
test_when_finished "rm -rf refformat" &&
test_config_global init.defaultRefFormat $format &&
(
sane_unset GIT_DEFAULT_REF_FORMAT &&
git init refformat
) &&

echo $format >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'

test_expect_success "--ref-format=$format overrides GIT_DEFAULT_REF_FORMAT" '
test_when_finished "rm -rf refformat" &&
GIT_DEFAULT_REF_FORMAT=garbage git init --ref-format=$format refformat &&
echo $format >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'
done

test_expect_success "--ref-format= overrides GIT_DEFAULT_REF_FORMAT" '
test_when_finished "rm -rf refformat" &&
GIT_DEFAULT_REF_FORMAT=files git init --ref-format=reftable refformat &&
echo reftable >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'

test_expect_success "GIT_DEFAULT_REF_FORMAT= overrides init.defaultRefFormat" '
test_when_finished "rm -rf refformat" &&
test_config_global init.defaultRefFormat files &&

GIT_DEFAULT_REF_FORMAT=reftable git init refformat &&
echo reftable >expect &&
git -C refformat rev-parse --show-ref-format >actual &&
test_cmp expect actual
'

backends="files reftable"
for from_format in $backends
do
test_expect_success "re-init with same format ($from_format)" '