Merge pull request #7102 from pks-gitlab/pks-refformat-extension

Introduction of the "refFormat" extension
This commit is contained in:
Edward Thomson
2025-12-06 16:58:16 +00:00
committed by GitHub
13 changed files with 398 additions and 92 deletions

View File

@@ -21,6 +21,11 @@
*/
GIT_BEGIN_DECL
/** The type of the refdb as determined by "extensions.refStorage". */
typedef enum {
GIT_REFDB_FILES = 1 /**< Files backend using loose and packed refs. */
} git_refdb_t;
/**
* Create a new reference database with no backends.
*

View File

@@ -11,6 +11,7 @@
#include "types.h"
#include "oid.h"
#include "odb.h"
#include "refdb.h"
#include "buffer.h"
#include "commit.h"
@@ -377,6 +378,12 @@ typedef struct {
*/
git_oid_t oid_type;
#endif
/**
* Type of the reference database to use for this repository, or 0 for
* default (currently "files").
*/
git_refdb_t refdb_type;
} git_repository_init_options;
/** Current version for the `git_repository_init_options` structure */

View File

@@ -56,10 +56,48 @@ struct git_reference_iterator {
git_reference_iterator *iter);
};
typedef enum {
/**
* The refdb that is to be initialized is for a worktree.
*/
GIT_REFDB_BACKEND_INIT_IS_WORKTREE = (1u << 0),
/*
* Force-overwrite HEAD in case the refdb is already (partially)
* initialized.
*/
GIT_REFDB_BACKEND_INIT_FORCE_HEAD = (1u << 1)
} git_refdb_backend_init_flag_t;
/** An instance for a custom backend */
struct git_refdb_backend {
unsigned int version; /**< The backend API version */
/**
* Create a new refdb and initialize its structures.
*
* A refdb implementation may provide this function; if it is not
* provided, no data structures will be initialized for the refdb when
* a new repository is created.
*
* @param path The path of the Git directory that shall be initialized.
* @param initial_head The target that HEAD should point to. This value
* should only be applied when there isn't yet a HEAD
* reference, or when `GIT_REFDB_BACKEND_INIT_FORCE_HEAD`
* is passed. Optional, if unset the backend should not set
* up HEAD, either.
* @param mode The mode that shall be used to create files and
* directories. May be one of `git_repository_init_mode_t`
* or normal Unix permission bits.
* @param flags A combination of `git_refdb_backend_init_flag_t` flags.
* @return `0` on success, a negative error value code.
*/
int GIT_CALLBACK(init)(
git_refdb_backend *backend,
const char *head_target,
mode_t mode,
uint32_t flags);
/**
* Queries the refdb backend for the existence of a reference.
*

View File

@@ -39,28 +39,36 @@ int git_refdb_new(git_refdb **out, git_repository *repo)
int git_refdb_open(git_refdb **out, git_repository *repo)
{
git_refdb_backend *backend;
git_refdb *db;
git_refdb_backend *dir;
int error;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(repo);
*out = NULL;
if (git_refdb_new(&db, repo) < 0)
return -1;
if ((error = git_refdb_new(&db, repo)) < 0)
goto out;
/* Add the default (filesystem) backend */
if (git_refdb_backend_fs(&dir, repo) < 0) {
git_refdb_free(db);
return -1;
switch (repo->refdb_type) {
case GIT_REFDB_FILES:
if ((error = git_refdb_backend_fs(&backend, repo)) < 0)
goto out;
break;
default:
git_error_set(GIT_ERROR_REFERENCE, "unknown reference storage format");
error = GIT_EINVALID;
goto out;
}
db->repo = repo;
db->backend = dir;
db->backend = backend;
*out = db;
return 0;
out:
if (error)
git_refdb_free(db);
return error;
}
static void refdb_free_backend(git_refdb *db)
@@ -114,6 +122,16 @@ void git_refdb_free(git_refdb *db)
GIT_REFCOUNT_DEC(db, git_refdb__free);
}
int git_refdb_init(git_refdb *refdb, const char *head_target, mode_t mode, uint32_t flags)
{
GIT_ASSERT_ARG(refdb);
GIT_ASSERT_ARG(refdb->backend);
if (!refdb->backend->init)
return 0;
return refdb->backend->init(refdb->backend, head_target, mode, flags);
}
int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
{
GIT_ASSERT_ARG(exists);

View File

@@ -12,6 +12,8 @@
#include "git2/refdb.h"
#include "repository.h"
#define GIT_INVALID_HEAD "refs/heads/.invalid"
struct git_refdb {
git_refcount rc;
git_repository *repo;
@@ -20,6 +22,11 @@ struct git_refdb {
void git_refdb__free(git_refdb *db);
int git_refdb_init(git_refdb *refdb,
const char *head_target,
mode_t mode,
uint32_t flags);
int git_refdb_exists(
int *exists,
git_refdb *refdb,
@@ -125,4 +132,21 @@ int git_refdb_ensure_log(git_refdb *refdb, const char *refname);
int git_refdb_lock(void **payload, git_refdb *db, const char *refname);
int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message);
GIT_INLINE(const char *) git_refdb_type_name(git_refdb_t type)
{
switch (type) {
case GIT_REFDB_FILES:
return "files";
default:
return "unknown";
}
}
GIT_INLINE(git_refdb_t) git_refdb_type_fromstr(const char *name)
{
if (strcmp(name, "files") == 0)
return GIT_REFDB_FILES;
return 0;
}
#endif

View File

@@ -344,6 +344,60 @@ static int packed_loadloose(refdb_fs_backend *backend)
return error;
}
static int refdb_fs_backend__init(struct git_refdb_backend *_backend,
const char *head_target,
mode_t mode,
uint32_t flags)
{
refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
git_str path = GIT_STR_INIT, content = GIT_STR_INIT;
int error;
/*
* As most references are per repository and not per worktree we don't
* want to set up the typical "refs/" hierarchy in worktrees.
*/
if ((flags & GIT_REFDB_BACKEND_INIT_IS_WORKTREE) == 0) {
mode_t dmode;
if (mode == GIT_REPOSITORY_INIT_SHARED_UMASK)
dmode = 0777;
else if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP)
dmode = (0775 | S_ISGID);
else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL)
dmode = (0777 | S_ISGID);
else
dmode = mode;
if ((error = git_str_joinpath(&path, backend->gitpath, GIT_REFS_HEADS_DIR)) < 0 ||
(error = git_futils_mkdir(path.ptr, dmode, 0) < 0) ||
(error = git_str_joinpath(&path, backend->gitpath, GIT_REFS_TAGS_DIR)) < 0 ||
(error = git_futils_mkdir(path.ptr, dmode, 0) < 0))
goto out;
}
if (head_target) {
if ((error = git_str_joinpath(&path, backend->gitpath, GIT_HEAD_FILE)) < 0)
goto out;
if ((flags & GIT_REFDB_BACKEND_INIT_FORCE_HEAD) == 0 &&
git_fs_path_exists(path.ptr))
goto out;
if ((error = git_str_printf(&content, GIT_SYMREF "%s\n", head_target)) < 0 ||
(error = git_futils_writebuffer(&content, path.ptr, 0, mode)) < 0)
goto out;
}
error = 0;
out:
git_str_dispose(&content);
git_str_dispose(&path);
return error;
}
static int refdb_fs_backend__exists(
int *exists,
git_refdb_backend *_backend,
@@ -2545,6 +2599,7 @@ int git_refdb_backend_fs(
backend->fsync = 1;
backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS;
backend->parent.init = &refdb_fs_backend__init;
backend->parent.exists = &refdb_fs_backend__exists;
backend->parent.lookup = &refdb_fs_backend__lookup;
backend->parent.iterator = &refdb_fs_backend__iterator;

View File

@@ -45,8 +45,6 @@ typedef struct {
static repo_template_item repo_template[] = {
{ GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */
{ GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */
{ GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */
{ GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */
{ GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */
{ GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */
{ GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },

View File

@@ -10,6 +10,7 @@
#include <ctype.h>
#include "git2/object.h"
#include "git2/sys/refdb_backend.h"
#include "git2/sys/repository.h"
#include "buf.h"
@@ -69,6 +70,7 @@ static int check_repositoryformatversion(int *version, git_config *config);
static int check_extensions(git_config *config, int version);
static int load_global_config(git_config **config, bool use_env);
static int load_objectformat(git_repository *repo, git_config *config);
static int load_refstorage_format(git_repository *repo, git_config *config);
#define GIT_COMMONDIR_FILE "commondir"
#define GIT_GITDIR_FILE "gitdir"
@@ -350,6 +352,17 @@ int git_repository_new_ext(
repo->oid_type = opts && opts->oid_type ? opts->oid_type :
GIT_OID_DEFAULT;
/*
* This is a bit dirty, as this repository doesn't really have a refdb
* in the first place. But we do expect that we can create an "empty"
* ref iterator from such a repository, and things keep on working like
* this.
*
* It might make sense to eventually create an "in-memory" refdb type
* to serve this purpose.
*/
repo->refdb_type = GIT_REFDB_FILES;
return 0;
}
@@ -983,20 +996,28 @@ done:
return error;
}
static int obtain_config_and_set_oid_type(
git_config **config_ptr,
git_repository *repo)
static int read_repository_format(git_repository *repo)
{
int error;
git_str config_path = GIT_STR_INIT;
git_config *config = NULL;
int version = 0;
if ((error = git_repository__item_path(&config_path, repo,
GIT_REPOSITORY_ITEM_CONFIG)) < 0)
goto out;
/*
* We'd like to have the config, but git doesn't particularly
* care if it's not there, so we need to deal with that.
*
* Further note that we only read the repository's own configuration.
* No other configuration should play a role here. Most importantly, we
* ignore conditional includes as those may cause a cyclic dependency
* between reading the format and evaluating the conditionals. This is
* for example the case with "onbranch" conditions.
*/
error = git_repository_config_snapshot(&config, repo);
error = git_config_open_ondisk(&config, config_path.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto out;
@@ -1008,14 +1029,17 @@ static int obtain_config_and_set_oid_type(
goto out;
if (version > 0) {
if ((error = load_objectformat(repo, config)) < 0)
if ((error = load_objectformat(repo, config)) < 0 ||
(error = load_refstorage_format(repo, config)) < 0)
goto out;
} else {
repo->oid_type = GIT_OID_DEFAULT;
repo->refdb_type = GIT_REFDB_FILES;
}
out:
*config_ptr = config;
git_str_dispose(&config_path);
git_config_free(config);
return error;
}
@@ -1028,7 +1052,6 @@ int git_repository_open_bare(
git_repository *repo = NULL;
bool is_valid;
int error;
git_config *config;
if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 ||
(error = is_valid_repository_path(&is_valid, &path, &common_path, 0)) < 0)
@@ -1054,14 +1077,12 @@ int git_repository_open_bare(
repo->is_worktree = 0;
repo->workdir = NULL;
if ((error = obtain_config_and_set_oid_type(&config, repo)) < 0)
if ((error = read_repository_format(repo)) < 0)
goto cleanup;
*repo_ptr = repo;
cleanup:
git_config_free(config);
return error;
}
@@ -1148,7 +1169,7 @@ int git_repository_open_ext(
repo->is_worktree = is_worktree;
error = obtain_config_and_set_oid_type(&config, repo);
error = read_repository_format(repo);
if (error < 0)
goto cleanup;
@@ -1158,6 +1179,10 @@ int git_repository_open_ext(
if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) {
repo->is_bare = 1;
} else {
error = git_repository_config_snapshot(&config, repo);
if (error < 0 && error != GIT_ENOTFOUND)
goto cleanup;
if (config &&
((error = load_config_data(repo, config)) < 0 ||
(error = load_workdir(repo, config, &paths.workdir)) < 0))
@@ -1235,6 +1260,17 @@ int git_repository_wrap_odb(git_repository **out, git_odb *odb)
GIT_ASSERT(git_oid_type_is_valid(odb->options.oid_type));
repo->oid_type = odb->options.oid_type;
/*
* This is a bit dirty, as this repository doesn't really have a refdb
* in the first place. But we do expect that we can create an "empty"
* ref iterator from such a repository, and things keep on working like
* this.
*
* It might make sense to eventually create an "in-memory" refdb type
* to serve this purpose.
*/
repo->refdb_type = GIT_REFDB_FILES;
git_repository_set_odb(repo, odb);
*out = repo;
@@ -1869,7 +1905,8 @@ static const char *builtin_extensions[] = {
"noop",
"objectformat",
"worktreeconfig",
"preciousobjects"
"preciousobjects",
"refstorage",
};
static git_vector user_extensions = { 0, git__strcmp_cb };
@@ -2006,6 +2043,32 @@ int git_repository__set_objectformat(
return 0;
}
static int load_refstorage_format(git_repository *repo, git_config *config)
{
git_config_entry *entry = NULL;
int error;
if ((error = git_config_get_entry(&entry, config, "extensions.refstorage")) < 0) {
if (error == GIT_ENOTFOUND) {
repo->refdb_type = GIT_REFDB_FILES;
git_error_clear();
error = 0;
}
goto done;
}
if ((repo->refdb_type = git_refdb_type_fromstr(entry->value)) == 0) {
git_error_set(GIT_ERROR_REPOSITORY,
"unknown refstorage format '%s'", entry->value);
error = GIT_EINVALID;
}
done:
git_config_entry_free(entry);
return error;
}
int git_repository__extensions(char ***out, size_t *out_len)
{
git_vector extensions;
@@ -2098,32 +2161,6 @@ void git_repository__free_extensions(void)
git_vector_dispose_deep(&user_extensions);
}
int git_repository_create_head(const char *git_dir, const char *ref_name)
{
git_str ref_path = GIT_STR_INIT;
git_filebuf ref = GIT_FILEBUF_INIT;
const char *fmt;
int error;
if ((error = git_str_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) < 0 ||
(error = git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE)) < 0)
goto out;
if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0)
fmt = "ref: %s\n";
else
fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n";
if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 ||
(error = git_filebuf_commit(&ref)) < 0)
goto out;
out:
git_str_dispose(&ref_path);
git_filebuf_cleanup(&ref);
return error;
}
static bool is_chmod_supported(const char *file_path)
{
struct stat st1, st2;
@@ -2317,7 +2354,8 @@ static int repo_init_config(
const char *work_dir,
uint32_t flags,
uint32_t mode,
git_oid_t oid_type)
git_oid_t oid_type,
git_refdb_t refdb_type)
{
int error = 0;
git_str cfg_path = GIT_STR_INIT, worktree_path = GIT_STR_INIT;
@@ -2381,6 +2419,11 @@ static int repo_init_config(
SET_REPO_CONFIG(string, "extensions.objectformat", git_oid_type_name(oid_type));
}
if (refdb_type != GIT_REFDB_FILES) {
SET_REPO_CONFIG(int32, "core.repositoryformatversion", 1);
SET_REPO_CONFIG(string, "extensions.refstorage", git_refdb_type_name(refdb_type));
}
cleanup:
git_str_dispose(&cfg_path);
git_str_dispose(&worktree_path);
@@ -2792,27 +2835,19 @@ static int repo_init_directories(
return error;
}
static int repo_init_head(const char *repo_dir, const char *given)
static int get_initial_head(char **out, uint32_t *flags,
git_repository_init_options *opts)
{
git_config *cfg = NULL;
git_str head_path = GIT_STR_INIT, cfg_branch = GIT_STR_INIT;
git_str cfg_branch = GIT_STR_INIT, prefixed = GIT_STR_INIT;
const char *initial_head = NULL;
git_config *cfg = NULL;
int error;
if ((error = git_str_joinpath(&head_path, repo_dir, GIT_HEAD_FILE)) < 0)
goto out;
/*
* A template may have set a HEAD; use that unless it's been
* overridden by the caller's given initial head setting.
*/
if (git_fs_path_exists(head_path.ptr) && !given)
goto out;
if (given) {
initial_head = given;
} else if ((error = git_config_open_default(&cfg)) >= 0 &&
(error = git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch")) >= 0 &&
if (opts->initial_head) {
initial_head = opts->initial_head;
*flags |= GIT_REFDB_BACKEND_INIT_FORCE_HEAD;
} else if (git_config_open_default(&cfg) >= 0 &&
git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch") >= 0 &&
*cfg_branch.ptr) {
initial_head = cfg_branch.ptr;
}
@@ -2820,12 +2855,22 @@ static int repo_init_head(const char *repo_dir, const char *given)
if (!initial_head)
initial_head = GIT_BRANCH_DEFAULT;
error = git_repository_create_head(repo_dir, initial_head);
if (git__prefixcmp(initial_head, GIT_REFS_DIR) != 0) {
git_str_printf(&prefixed, GIT_REFS_HEADS_DIR "%s", initial_head);
initial_head = prefixed.ptr;
}
if ((*out = git__strdup(initial_head)) == NULL) {
error = -1;
goto out;
}
error = 0;
out:
git_config_free(cfg);
git_str_dispose(&head_path);
git_str_dispose(&cfg_branch);
git_str_dispose(&prefixed);
git_config_free(cfg);
return error;
}
@@ -2860,10 +2905,14 @@ int git_repository_init_ext(
git_repository_init_options *opts)
{
git_str repo_path = GIT_STR_INIT, wd_path = GIT_STR_INIT,
common_path = GIT_STR_INIT;
common_path = GIT_STR_INIT, path = GIT_STR_INIT;
const char *wd;
git_refdb *refdb;
bool is_valid;
git_oid_t oid_type = GIT_OID_DEFAULT;
git_refdb_t refdb_type = GIT_REFDB_FILES;
char *initial_head = NULL;
uint32_t refdb_init_flags = 0;
int error;
GIT_ASSERT_ARG(out);
@@ -2876,6 +2925,8 @@ int git_repository_init_ext(
if (opts->oid_type)
oid_type = opts->oid_type;
#endif
if (opts->refdb_type)
refdb_type = opts->refdb_type;
if ((error = repo_init_directories(&repo_path, &wd_path, given_repo, opts)) < 0)
goto out;
@@ -2895,20 +2946,79 @@ int git_repository_init_ext(
opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;
if ((error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode, oid_type)) < 0)
if ((error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode,
oid_type, refdb_type)) < 0)
goto out;
/*
* If we are re-initializing an existing repository we only
* want to reset HEAD in case the user explicitly provided a
* new target.
*/
if (opts->initial_head &&
(error = get_initial_head(&initial_head, &refdb_init_flags, opts)) < 0)
goto out;
/* TODO: reinitialize the templates */
} else {
git_str content = GIT_STR_INIT_CONST("ref: " GIT_INVALID_HEAD,
strlen("ref: " GIT_INVALID_HEAD));
mode_t dmode = pick_dir_mode(opts);
if ((error = repo_init_structure(repo_path.ptr, wd, opts)) < 0 ||
(error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode, oid_type)) < 0 ||
(error = repo_init_head(repo_path.ptr, opts->initial_head)) < 0)
(error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode,
oid_type, refdb_type)) < 0)
goto out;
/*
* Both HEAD and "refs/" must exist in a repository regardless
* of its ref format, otherwise it won't be recognized as a Git
* repository. As such, we create these now so that we can open
* the repository and properly initialize its refdb.
*/
if ((error = git_str_joinpath(&path, repo_path.ptr, GIT_HEAD_FILE)) < 0)
goto out;
/* Only overwrite HEAD if it wasn't created by the template. */
if (!git_fs_path_exists(path.ptr)) {
if ((error = git_futils_writebuffer(&content, path.ptr, 0,
GIT_REFS_FILE_MODE)) < 0)
goto out;
/*
* Ask the backend to force-overwrite the invalid HEAD
* we have just written.
*/
refdb_init_flags |= GIT_REFDB_BACKEND_INIT_FORCE_HEAD;
}
if ((error = git_str_joinpath(&path, repo_path.ptr, GIT_REFS_DIR)) < 0 ||
(error = git_futils_mkdir(path.ptr, dmode, 0)) < 0)
goto out;
/*
* Note that we also compute the initial HEAD in case the file
* already exists. This is done because the file HEAD may not
* actually be relevant to the backend. E.g. reftables do not
* care for this file at all, so we would end up with no HEAD
* at all if we didn't ask the backend to write one in that
* case.
*
* That being said, we don't force-overwrite HEAD so that the
* backends can first check whether the templates have already
* created a HEAD.
*/
if ((error = get_initial_head(&initial_head, &refdb_init_flags, opts)) < 0)
goto out;
}
if ((error = git_repository_open(out, repo_path.ptr)) < 0)
goto out;
if ((error = git_repository_refdb__weakptr(&refdb, *out)) < 0 ||
(error = git_refdb_init(refdb, initial_head, opts->mode, refdb_init_flags)) < 0)
goto out;
if (opts->origin_url &&
(error = repo_init_create_origin(*out, opts->origin_url)) < 0)
goto out;
@@ -2917,6 +3027,8 @@ out:
git_str_dispose(&common_path);
git_str_dispose(&repo_path);
git_str_dispose(&wd_path);
git_str_dispose(&path);
git__free(initial_head);
return error;
}

View File

@@ -159,6 +159,7 @@ struct git_repository {
is_bare:1,
is_worktree:1;
git_oid_t oid_type;
git_refdb_t refdb_type;
unsigned int lru_counter;
@@ -178,7 +179,6 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
int git_repository_head_commit(git_commit **commit, git_repository *repo);
int git_repository_head_tree(git_tree **tree, git_repository *repo);
int git_repository_create_head(const char *git_dir, const char *ref_name);
typedef int (*git_repository_foreach_worktree_cb)(git_repository *, void *);

View File

@@ -8,12 +8,14 @@
#include "worktree.h"
#include "buf.h"
#include "refdb.h"
#include "repository.h"
#include "path.h"
#include "git2/branch.h"
#include "git2/commit.h"
#include "git2/worktree.h"
#include "git2/sys/refdb_backend.h"
static bool is_worktree_dir(const char *dir)
{
@@ -305,12 +307,15 @@ int git_worktree_add(git_worktree **out, git_repository *repo,
const char *name, const char *worktree,
const git_worktree_add_options *opts)
{
git_str invalid_head_content = GIT_STR_INIT_CONST("ref: " GIT_INVALID_HEAD,
strlen("ref: " GIT_INVALID_HEAD));
git_str gitdir = GIT_STR_INIT, wddir = GIT_STR_INIT, buf = GIT_STR_INIT;
git_reference *ref = NULL, *head = NULL;
git_reference *ref = NULL, *head = NULL, *wt_head = NULL;
git_commit *commit = NULL;
git_repository *wt = NULL;
git_checkout_options coopts;
git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT;
git_refdb *refdb;
int err;
GIT_ERROR_CHECK_VERSION(
@@ -402,12 +407,26 @@ int git_worktree_add(git_worktree **out, git_repository *repo,
|| (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0)
goto out;
/* Set worktree's HEAD */
if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0)
/*
* Create the ref skeleton that is required to make a directory look
* like a Git repository. These are required regardless of the ref
* format used.
*/
if ((err = git_str_joinpath(&buf, gitdir.ptr, GIT_HEAD_FILE)) < 0 ||
(err = git_futils_writebuffer(&invalid_head_content, buf.ptr, 0, GIT_REFS_FILE_MODE)) < 0 ||
(err = git_str_joinpath(&buf, gitdir.ptr, GIT_REFS_DIR)) < 0 ||
(err = git_futils_mkdir(buf.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
goto out;
if ((err = git_repository_open(&wt, wddir.ptr)) < 0)
goto out;
/* Initialize the worktree refdb and set up HEAD. */
if ((err = git_repository_refdb__weakptr(&refdb, wt)) < 0 ||
(err = git_refdb_init(refdb, git_reference_name(ref), 0777,
GIT_REFDB_BACKEND_INIT_IS_WORKTREE | GIT_REFDB_BACKEND_INIT_FORCE_HEAD)) < 0)
goto out;
/* Checkout worktree's HEAD */
if ((err = git_checkout_head(wt, &coopts)) < 0)
goto out;
@@ -422,6 +441,7 @@ out:
git_str_dispose(&buf);
git_reference_free(ref);
git_reference_free(head);
git_reference_free(wt_head);
git_commit_free(commit);
git_repository_free(wt);

View File

@@ -112,7 +112,9 @@ void test_config_conditionals__invalid_conditional_fails(void)
static void set_head(git_repository *repo, const char *name)
{
cl_git_pass(git_repository_create_head(git_repository_path(repo), name));
struct git_reference *ref;
cl_git_pass(git_reference_symbolic_create(&ref, repo, GIT_HEAD_FILE, name, 1, NULL));
git_reference_free(ref);
}
void test_config_conditionals__onbranch(void)
@@ -123,12 +125,12 @@ void test_config_conditionals__onbranch(void)
assert_condition_includes("onbranch", "master/", false);
assert_condition_includes("onbranch", "foo", false);
set_head(_repo, "foo");
set_head(_repo, "refs/heads/foo");
assert_condition_includes("onbranch", "master", false);
assert_condition_includes("onbranch", "foo", true);
assert_condition_includes("onbranch", "f*o", true);
set_head(_repo, "dir/ref");
set_head(_repo, "refs/heads/dir/ref");
assert_condition_includes("onbranch", "dir/ref", true);
assert_condition_includes("onbranch", "dir/", true);
assert_condition_includes("onbranch", "dir/*", true);
@@ -137,7 +139,7 @@ void test_config_conditionals__onbranch(void)
assert_condition_includes("onbranch", "dir", false);
assert_condition_includes("onbranch", "dir*", false);
set_head(_repo, "dir/subdir/ref");
set_head(_repo, "refs/heads/dir/subdir/ref");
assert_condition_includes("onbranch", "dir/subdir/", true);
assert_condition_includes("onbranch", "dir/subdir/*", true);
assert_condition_includes("onbranch", "dir/subdir/ref", true);

View File

@@ -34,11 +34,12 @@ void test_core_opts__extensions_query(void)
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
cl_assert_equal_sz(out.count, 4);
cl_assert_equal_sz(out.count, 5);
cl_assert_equal_s("noop", out.strings[0]);
cl_assert_equal_s("objectformat", out.strings[1]);
cl_assert_equal_s("preciousobjects", out.strings[2]);
cl_assert_equal_s("worktreeconfig", out.strings[3]);
cl_assert_equal_s("refstorage", out.strings[3]);
cl_assert_equal_s("worktreeconfig", out.strings[4]);
git_strarray_dispose(&out);
}
@@ -51,12 +52,13 @@ void test_core_opts__extensions_add(void)
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
cl_assert_equal_sz(out.count, 5);
cl_assert_equal_sz(out.count, 6);
cl_assert_equal_s("foo", out.strings[0]);
cl_assert_equal_s("noop", out.strings[1]);
cl_assert_equal_s("objectformat", out.strings[2]);
cl_assert_equal_s("preciousobjects", out.strings[3]);
cl_assert_equal_s("worktreeconfig", out.strings[4]);
cl_assert_equal_s("refstorage", out.strings[4]);
cl_assert_equal_s("worktreeconfig", out.strings[5]);
git_strarray_dispose(&out);
}
@@ -69,12 +71,13 @@ void test_core_opts__extensions_remove(void)
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
cl_assert_equal_sz(out.count, 5);
cl_assert_equal_sz(out.count, 6);
cl_assert_equal_s("bar", out.strings[0]);
cl_assert_equal_s("baz", out.strings[1]);
cl_assert_equal_s("objectformat", out.strings[2]);
cl_assert_equal_s("preciousobjects", out.strings[3]);
cl_assert_equal_s("worktreeconfig", out.strings[4]);
cl_assert_equal_s("refstorage", out.strings[4]);
cl_assert_equal_s("worktreeconfig", out.strings[5]);
git_strarray_dispose(&out);
}
@@ -87,13 +90,14 @@ void test_core_opts__extensions_uniq(void)
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in)));
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out));
cl_assert_equal_sz(out.count, 6);
cl_assert_equal_sz(out.count, 7);
cl_assert_equal_s("bar", out.strings[0]);
cl_assert_equal_s("foo", out.strings[1]);
cl_assert_equal_s("noop", out.strings[2]);
cl_assert_equal_s("objectformat", out.strings[3]);
cl_assert_equal_s("preciousobjects", out.strings[4]);
cl_assert_equal_s("worktreeconfig", out.strings[5]);
cl_assert_equal_s("refstorage", out.strings[5]);
cl_assert_equal_s("worktreeconfig", out.strings[6]);
git_strarray_dispose(&out);
}

View File

@@ -874,3 +874,26 @@ void test_repo_open__can_reset_safe_directory_list(void)
git_str_dispose(&config_filename);
git_str_dispose(&config_data);
}
void test_repo_open__refstorage_extension(void)
{
git_repository *repo, *tmp;
git_config *config;
repo = cl_git_sandbox_init("empty_bare.git");
cl_git_pass(git_repository_open(&repo, "empty_bare.git"));
cl_git_pass(git_repository_config(&config, repo));
cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1));
cl_git_pass(git_config_set_string(config, "extensions.refStorage", "files"));
cl_git_pass(git_repository_open(&tmp, "empty_bare.git"));
git_repository_free(tmp);
cl_git_pass(git_config_set_string(config, "extensions.refStorage", "garbage"));
cl_git_fail_with(GIT_EINVALID, git_repository_open(&tmp, "empty_bare.git"));
git_repository_free(tmp);
git_config_free(config);
git_repository_free(repo);
}