Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8fcaec5

Browse files
committed
refdb: initialize on-disk data structures via the backend
The initialization of the on-disk state of refdbs is currently not handled by the actual refdb backend, but it's implemented ad-hoc where needed. This is problematic once we have multiple different refdbs as the filesystem structure is of course not the same. Introduce a new callback function `git_refdb_backend::init()`. If set, this callback can be invoked via `git_refdb_init()` to initialize the on-disk state of a refdb. Like this, each backend can decide for itself how exactly to do this. Note that the initialization of the refdb is a bit intricate. A repository is only recognized as such when it has a "HEAD" file as well as a "refs/" directory. Consequently, regardless of which refdb format we use, those files must always be present. This also proves to be problematic for us, as we cannot access the repository and thus don't have access to the refdb if those files didn't exist. To work around the issue we thus handle the creation of those files outside of the refdb-specific logic. We actually use the same strategy as Git does, and write the invalid reference "ref: refs/heads/.invalid" into "HEAD". This looks almost like a ref, but the name of that ref is not valid and should thus trip up Git clients that try to read that ref in a repository that really uses a different format. So while that invalid "HEAD" reference will of course get rewritten by the "files" backend, other backends should just retain it as-is.
1 parent fc68ee3 commit 8fcaec5

File tree

8 files changed

+221
-55
lines changed

8 files changed

+221
-55
lines changed

include/git2/sys/refdb_backend.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,48 @@ struct git_reference_iterator {
5656
git_reference_iterator *iter);
5757
};
5858

59+
typedef enum {
60+
/**
61+
* The refdb that is to be initialized is for a worktree.
62+
*/
63+
GIT_REFDB_BACKEND_INIT_IS_WORKTREE = (1u << 0),
64+
65+
/*
66+
* Force-overwrite HEAD in case the refdb is already (partially)
67+
* initialized.
68+
*/
69+
GIT_REFDB_BACKEND_INIT_FORCE_HEAD = (1u << 1)
70+
} git_refdb_backend_init_flag_t;
71+
5972
/** An instance for a custom backend */
6073
struct git_refdb_backend {
6174
unsigned int version; /**< The backend API version */
6275

76+
/**
77+
* Create a new refdb and initialize its structures.
78+
*
79+
* A refdb implementation may provide this function; if it is not
80+
* provided, no data structures will be initialized for the refdb when
81+
* a new repository is created.
82+
*
83+
* @param path The path of the Git directory that shall be initialized.
84+
* @param initial_head The target that HEAD should point to. This value
85+
* should only be applied when there isn't yet a HEAD
86+
* reference, or when `GIT_REFDB_BACKEND_INIT_FORCE_HEAD`
87+
* is passed. Optional, if unset the backend should not set
88+
* up HEAD, either.
89+
* @param mode The mode that shall be used to create files and
90+
* directories. May be one of `git_repository_init_mode_t`
91+
* or normal Unix permission bits.
92+
* @param flags A combination of `git_refdb_backend_init_flag_t` flags.
93+
* @return `0` on success, a negative error value code.
94+
*/
95+
int GIT_CALLBACK(init)(
96+
git_refdb_backend *backend,
97+
const char *head_target,
98+
mode_t mode,
99+
uint32_t flags);
100+
63101
/**
64102
* Queries the refdb backend for the existence of a reference.
65103
*

src/libgit2/refdb.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ void git_refdb_free(git_refdb *db)
122122
GIT_REFCOUNT_DEC(db, git_refdb__free);
123123
}
124124

125+
int git_refdb_init(git_refdb *refdb, const char *head_target, mode_t mode, uint32_t flags)
126+
{
127+
GIT_ASSERT_ARG(refdb);
128+
GIT_ASSERT_ARG(refdb->backend);
129+
130+
if (!refdb->backend->init)
131+
return 0;
132+
return refdb->backend->init(refdb->backend, head_target, mode, flags);
133+
}
134+
125135
int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
126136
{
127137
GIT_ASSERT_ARG(exists);

src/libgit2/refdb.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "git2/refdb.h"
1313
#include "repository.h"
1414

15+
#define GIT_INVALID_HEAD "refs/heads/.invalid"
16+
1517
struct git_refdb {
1618
git_refcount rc;
1719
git_repository *repo;
@@ -20,6 +22,11 @@ struct git_refdb {
2022

2123
void git_refdb__free(git_refdb *db);
2224

25+
int git_refdb_init(git_refdb *refdb,
26+
const char *head_target,
27+
mode_t mode,
28+
uint32_t flags);
29+
2330
int git_refdb_exists(
2431
int *exists,
2532
git_refdb *refdb,

src/libgit2/refdb_fs.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,60 @@ static int packed_loadloose(refdb_fs_backend *backend)
344344
return error;
345345
}
346346

347+
static int refdb_fs_backend__init(struct git_refdb_backend *_backend,
348+
const char *head_target,
349+
mode_t mode,
350+
uint32_t flags)
351+
{
352+
refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
353+
git_str path = GIT_STR_INIT, content = GIT_STR_INIT;
354+
int error;
355+
356+
/*
357+
* As most references are per repository and not per worktree we don't
358+
* want to set up the typical "refs/" hierarchy in worktrees.
359+
*/
360+
if ((flags & GIT_REFDB_BACKEND_INIT_IS_WORKTREE) == 0) {
361+
mode_t dmode;
362+
363+
if (mode == GIT_REPOSITORY_INIT_SHARED_UMASK)
364+
dmode = 0777;
365+
else if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP)
366+
dmode = (0775 | S_ISGID);
367+
else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL)
368+
dmode = (0777 | S_ISGID);
369+
else
370+
dmode = mode;
371+
372+
if ((error = git_str_joinpath(&path, backend->gitpath, GIT_REFS_HEADS_DIR)) < 0 ||
373+
(error = git_futils_mkdir(path.ptr, dmode, 0) < 0) ||
374+
(error = git_str_joinpath(&path, backend->gitpath, GIT_REFS_TAGS_DIR)) < 0 ||
375+
(error = git_futils_mkdir(path.ptr, dmode, 0) < 0))
376+
goto out;
377+
}
378+
379+
if (head_target) {
380+
if ((error = git_str_joinpath(&path, backend->gitpath, GIT_HEAD_FILE)) < 0)
381+
goto out;
382+
383+
if ((flags & GIT_REFDB_BACKEND_INIT_FORCE_HEAD) == 0 &&
384+
git_fs_path_exists(path.ptr))
385+
goto out;
386+
387+
if ((error = git_str_printf(&content, GIT_SYMREF "%s\n", head_target)) < 0 ||
388+
(error = git_futils_writebuffer(&content, path.ptr, 0, mode)) < 0)
389+
goto out;
390+
}
391+
392+
error = 0;
393+
394+
out:
395+
git_str_dispose(&content);
396+
git_str_dispose(&path);
397+
398+
return error;
399+
}
400+
347401
static int refdb_fs_backend__exists(
348402
int *exists,
349403
git_refdb_backend *_backend,
@@ -2545,6 +2599,7 @@ int git_refdb_backend_fs(
25452599
backend->fsync = 1;
25462600
backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS;
25472601

2602+
backend->parent.init = &refdb_fs_backend__init;
25482603
backend->parent.exists = &refdb_fs_backend__exists;
25492604
backend->parent.lookup = &refdb_fs_backend__lookup;
25502605
backend->parent.iterator = &refdb_fs_backend__iterator;

src/libgit2/repo_template.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ typedef struct {
4545
static repo_template_item repo_template[] = {
4646
{ GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */
4747
{ GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */
48-
{ GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */
49-
{ GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */
5048
{ GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */
5149
{ GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */
5250
{ GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },

src/libgit2/repository.c

Lines changed: 88 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <ctype.h>
1111

1212
#include "git2/object.h"
13+
#include "git2/sys/refdb_backend.h"
1314
#include "git2/sys/repository.h"
1415

1516
#include "buf.h"
@@ -2160,32 +2161,6 @@ void git_repository__free_extensions(void)
21602161
git_vector_dispose_deep(&user_extensions);
21612162
}
21622163

2163-
int git_repository_create_head(const char *git_dir, const char *ref_name)
2164-
{
2165-
git_str ref_path = GIT_STR_INIT;
2166-
git_filebuf ref = GIT_FILEBUF_INIT;
2167-
const char *fmt;
2168-
int error;
2169-
2170-
if ((error = git_str_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) < 0 ||
2171-
(error = git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE)) < 0)
2172-
goto out;
2173-
2174-
if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0)
2175-
fmt = "ref: %s\n";
2176-
else
2177-
fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n";
2178-
2179-
if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 ||
2180-
(error = git_filebuf_commit(&ref)) < 0)
2181-
goto out;
2182-
2183-
out:
2184-
git_str_dispose(&ref_path);
2185-
git_filebuf_cleanup(&ref);
2186-
return error;
2187-
}
2188-
21892164
static bool is_chmod_supported(const char *file_path)
21902165
{
21912166
struct stat st1, st2;
@@ -2860,40 +2835,42 @@ static int repo_init_directories(
28602835
return error;
28612836
}
28622837

2863-
static int repo_init_head(const char *repo_dir, const char *given)
2838+
static int get_initial_head(char **out, uint32_t *flags,
2839+
git_repository_init_options *opts)
28642840
{
2865-
git_config *cfg = NULL;
2866-
git_str head_path = GIT_STR_INIT, cfg_branch = GIT_STR_INIT;
2841+
git_str cfg_branch = GIT_STR_INIT, prefixed = GIT_STR_INIT;
28672842
const char *initial_head = NULL;
2843+
git_config *cfg = NULL;
28682844
int error;
28692845

2870-
if ((error = git_str_joinpath(&head_path, repo_dir, GIT_HEAD_FILE)) < 0)
2871-
goto out;
2872-
2873-
/*
2874-
* A template may have set a HEAD; use that unless it's been
2875-
* overridden by the caller's given initial head setting.
2876-
*/
2877-
if (git_fs_path_exists(head_path.ptr) && !given)
2878-
goto out;
2879-
2880-
if (given) {
2881-
initial_head = given;
2882-
} else if ((error = git_config_open_default(&cfg)) >= 0 &&
2883-
(error = git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch")) >= 0 &&
2846+
if (opts->initial_head) {
2847+
initial_head = opts->initial_head;
2848+
*flags |= GIT_REFDB_BACKEND_INIT_FORCE_HEAD;
2849+
} else if (git_config_open_default(&cfg) >= 0 &&
2850+
git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch") >= 0 &&
28842851
*cfg_branch.ptr) {
28852852
initial_head = cfg_branch.ptr;
28862853
}
28872854

28882855
if (!initial_head)
28892856
initial_head = GIT_BRANCH_DEFAULT;
28902857

2891-
error = git_repository_create_head(repo_dir, initial_head);
2858+
if (git__prefixcmp(initial_head, GIT_REFS_DIR) != 0) {
2859+
git_str_printf(&prefixed, GIT_REFS_HEADS_DIR "%s", initial_head);
2860+
initial_head = prefixed.ptr;
2861+
}
2862+
2863+
if ((*out = git__strdup(initial_head)) == NULL) {
2864+
error = -1;
2865+
goto out;
2866+
}
2867+
2868+
error = 0;
28922869

28932870
out:
2894-
git_config_free(cfg);
2895-
git_str_dispose(&head_path);
28962871
git_str_dispose(&cfg_branch);
2872+
git_str_dispose(&prefixed);
2873+
git_config_free(cfg);
28972874

28982875
return error;
28992876
}
@@ -2928,11 +2905,14 @@ int git_repository_init_ext(
29282905
git_repository_init_options *opts)
29292906
{
29302907
git_str repo_path = GIT_STR_INIT, wd_path = GIT_STR_INIT,
2931-
common_path = GIT_STR_INIT;
2908+
common_path = GIT_STR_INIT, path = GIT_STR_INIT;
29322909
const char *wd;
2910+
git_refdb *refdb;
29332911
bool is_valid;
29342912
git_oid_t oid_type = GIT_OID_DEFAULT;
29352913
git_refdb_t refdb_type = GIT_REFDB_FILES;
2914+
char *initial_head = NULL;
2915+
uint32_t refdb_init_flags = 0;
29362916
int error;
29372917

29382918
GIT_ASSERT_ARG(out);
@@ -2970,18 +2950,75 @@ int git_repository_init_ext(
29702950
oid_type, refdb_type)) < 0)
29712951
goto out;
29722952

2953+
/*
2954+
* If we are re-initializing an existing repository we only
2955+
* want to reset HEAD in case the user explicitly provided a
2956+
* new target.
2957+
*/
2958+
if (opts->initial_head &&
2959+
(error = get_initial_head(&initial_head, &refdb_init_flags, opts)) < 0)
2960+
goto out;
2961+
29732962
/* TODO: reinitialize the templates */
29742963
} else {
2964+
git_str content = GIT_STR_INIT_CONST("ref: " GIT_INVALID_HEAD,
2965+
strlen("ref: " GIT_INVALID_HEAD));
2966+
mode_t dmode = pick_dir_mode(opts);
2967+
29752968
if ((error = repo_init_structure(repo_path.ptr, wd, opts)) < 0 ||
29762969
(error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode,
2977-
oid_type, refdb_type)) < 0 ||
2978-
(error = repo_init_head(repo_path.ptr, opts->initial_head)) < 0)
2970+
oid_type, refdb_type)) < 0)
2971+
goto out;
2972+
2973+
/*
2974+
* Both HEAD and "refs/" must exist in a repository regardless
2975+
* of its ref format, otherwise it won't be recognized as a Git
2976+
* repository. As such, we create these now so that we can open
2977+
* the repository and properly initialize its refdb.
2978+
*/
2979+
if ((error = git_str_joinpath(&path, repo_path.ptr, GIT_HEAD_FILE)) < 0)
2980+
goto out;
2981+
2982+
/* Only overwrite HEAD if it wasn't created by the template. */
2983+
if (!git_fs_path_exists(path.ptr)) {
2984+
if ((error = git_futils_writebuffer(&content, path.ptr, 0,
2985+
GIT_REFS_FILE_MODE)) < 0)
2986+
goto out;
2987+
2988+
/*
2989+
* Ask the backend to force-overwrite the invalid HEAD
2990+
* we have just written.
2991+
*/
2992+
refdb_init_flags |= GIT_REFDB_BACKEND_INIT_FORCE_HEAD;
2993+
}
2994+
2995+
if ((error = git_str_joinpath(&path, repo_path.ptr, GIT_REFS_DIR)) < 0 ||
2996+
(error = git_futils_mkdir(path.ptr, dmode, 0)) < 0)
2997+
goto out;
2998+
2999+
/*
3000+
* Note that we also compute the initial HEAD in case the file
3001+
* already exists. This is done because the file HEAD may not
3002+
* actually be relevant to the backend. E.g. reftables do not
3003+
* care for this file at all, so we would end up with no HEAD
3004+
* at all if we didn't ask the backend to write one in that
3005+
* case.
3006+
*
3007+
* That being said, we don't force-overwrite HEAD so that the
3008+
* backends can first check whether the templates have already
3009+
* created a HEAD.
3010+
*/
3011+
if ((error = get_initial_head(&initial_head, &refdb_init_flags, opts)) < 0)
29793012
goto out;
29803013
}
29813014

29823015
if ((error = git_repository_open(out, repo_path.ptr)) < 0)
29833016
goto out;
29843017

3018+
if ((error = git_repository_refdb__weakptr(&refdb, *out)) < 0 ||
3019+
(error = git_refdb_init(refdb, initial_head, opts->mode, refdb_init_flags)) < 0)
3020+
goto out;
3021+
29853022
if (opts->origin_url &&
29863023
(error = repo_init_create_origin(*out, opts->origin_url)) < 0)
29873024
goto out;
@@ -2990,6 +3027,8 @@ int git_repository_init_ext(
29903027
git_str_dispose(&common_path);
29913028
git_str_dispose(&repo_path);
29923029
git_str_dispose(&wd_path);
3030+
git_str_dispose(&path);
3031+
git__free(initial_head);
29933032

29943033
return error;
29953034
}

src/libgit2/repository.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
179179

180180
int git_repository_head_commit(git_commit **commit, git_repository *repo);
181181
int git_repository_head_tree(git_tree **tree, git_repository *repo);
182-
int git_repository_create_head(const char *git_dir, const char *ref_name);
183182

184183
typedef int (*git_repository_foreach_worktree_cb)(git_repository *, void *);
185184

0 commit comments

Comments
 (0)