Commit 4196dd8e by Edward Thomson Committed by Edward Thomson

repository: Introduce "reserved names"

A repository can have multiple "reserved names" now, not just
a single "short name" for the repository folder itself.  Refactor
to include a git_repository__reserved_names that returns all the
reserved names for a repository.
parent c92987d1
......@@ -1351,30 +1351,31 @@ static bool verify_dotgit_hfs(const char *path, size_t len)
GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len)
{
const char *shortname = NULL;
size_t i, start, shortname_len = 0;
/* See if the repo has a custom shortname (not "GIT~1") */
if (repo &&
(shortname = git_repository__8dot3_name(repo)) &&
shortname != git_repository__8dot3_default)
shortname_len = strlen(shortname);
if (len >= 4 && strncasecmp(path, ".git", 4) == 0)
start = 4;
else if (len >= git_repository__8dot3_default_len &&
strncasecmp(path, git_repository__8dot3_default, git_repository__8dot3_default_len) == 0)
start = git_repository__8dot3_default_len;
else if (shortname_len && len >= shortname_len &&
strncasecmp(path, shortname, shortname_len) == 0)
start = shortname_len;
else
git_buf *reserved = git_repository__reserved_names_win32;
size_t reserved_len = git_repository__reserved_names_win32_len;
size_t start = 0, i;
if (repo)
git_repository__reserved_names(&reserved, &reserved_len, repo, true);
for (i = 0; i < reserved_len; i++) {
git_buf *r = &reserved[i];
if (len >= r->size &&
strncasecmp(path, r->ptr, r->size) == 0) {
start = r->size;
break;
}
}
if (!start)
return true;
/* Reject paths beginning with ".git\" */
/* Reject paths like ".git\" */
if (path[start] == '\\')
return false;
/* Reject paths like '.git ' or '.git.' */
for (i = start; i < len; i++) {
if (path[i] != ' ' && path[i] != '.')
return true;
......
......@@ -37,8 +37,16 @@
#define GIT_REPO_VERSION 0
const char *git_repository__8dot3_default = "GIT~1";
size_t git_repository__8dot3_default_len = 5;
git_buf git_repository__reserved_names_win32[] = {
{ DOT_GIT, 0, CONST_STRLEN(DOT_GIT) },
{ GIT_DIR_SHORTNAME, 0, CONST_STRLEN(GIT_DIR_SHORTNAME) }
};
size_t git_repository__reserved_names_win32_len = 2;
git_buf git_repository__reserved_names_posix[] = {
{ DOT_GIT, 0, CONST_STRLEN(DOT_GIT) },
};
size_t git_repository__reserved_names_posix_len = 1;
static void set_odb(git_repository *repo, git_odb *odb)
{
......@@ -110,6 +118,8 @@ void git_repository__cleanup(git_repository *repo)
void git_repository_free(git_repository *repo)
{
size_t i;
if (repo == NULL)
return;
......@@ -120,10 +130,12 @@ void git_repository_free(git_repository *repo)
git_diff_driver_registry_free(repo->diff_drivers);
repo->diff_drivers = NULL;
for (i = 0; i < repo->reserved_names.size; i++)
git_buf_free(git_array_get(repo->reserved_names, i));
git__free(repo->path_repository);
git__free(repo->workdir);
git__free(repo->namespace);
git__free(repo->name_8dot3);
git__memzero(repo, sizeof(*repo));
git__free(repo);
......@@ -153,18 +165,26 @@ static bool valid_repository_path(git_buf *repository_path)
static git_repository *repository_alloc(void)
{
git_repository *repo = git__calloc(1, sizeof(git_repository));
if (!repo)
return NULL;
if (git_cache_init(&repo->objects) < 0) {
git__free(repo);
return NULL;
}
if (repo == NULL ||
git_cache_init(&repo->objects) < 0)
goto on_error;
git_array_init_to_size(repo->reserved_names, 4);
if (!repo->reserved_names.ptr)
goto on_error;
/* set all the entries in the cvar cache to `unset` */
git_repository__cvar_cache_clear(repo);
return repo;
on_error:
if (repo)
git_cache_free(&repo->objects);
git__free(repo);
return NULL;
}
int git_repository_new(git_repository **out)
......@@ -314,6 +334,7 @@ static int read_gitfile(git_buf *path_out, const char *file_path)
static int find_repo(
git_buf *repo_path,
git_buf *parent_path,
git_buf *link_path,
const char *start_path,
uint32_t flags,
const char *ceiling_dirs)
......@@ -356,9 +377,14 @@ static int find_repo(
git_buf repo_link = GIT_BUF_INIT;
if (!(error = read_gitfile(&repo_link, path.ptr))) {
if (valid_repository_path(&repo_link))
if (valid_repository_path(&repo_link)) {
git_buf_swap(repo_path, &repo_link);
if (link_path)
error = git_buf_put(link_path,
path.ptr, path.size);
}
git_buf_free(&repo_link);
break;
}
......@@ -445,13 +471,16 @@ int git_repository_open_ext(
const char *ceiling_dirs)
{
int error;
git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT;
git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT,
link_path = GIT_BUF_INIT;
git_repository *repo;
if (repo_ptr)
*repo_ptr = NULL;
error = find_repo(&path, &parent, start_path, flags, ceiling_dirs);
error = find_repo(
&path, &parent, &link_path, start_path, flags, ceiling_dirs);
if (error < 0 || !repo_ptr)
return error;
......@@ -461,6 +490,11 @@ int git_repository_open_ext(
repo->path_repository = git_buf_detach(&path);
GITERR_CHECK_ALLOC(repo->path_repository);
if (link_path.size) {
repo->path_gitlink = git_buf_detach(&link_path);
GITERR_CHECK_ALLOC(repo->path_gitlink);
}
if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
repo->is_bare = 1;
else {
......@@ -512,7 +546,7 @@ int git_repository_discover(
git_buf_sanitize(out);
return find_repo(out, NULL, start_path, flags, ceiling_dirs);
return find_repo(out, NULL, NULL, start_path, flags, ceiling_dirs);
}
static int load_config(
......@@ -797,27 +831,88 @@ const char *git_repository_get_namespace(git_repository *repo)
return repo->namespace;
}
const char *git_repository__8dot3_name(git_repository *repo)
#ifdef GIT_WIN32
static int reserved_names_add8dot3(git_repository *repo, const char *path)
{
if (!repo->has_8dot3) {
repo->has_8dot3 = 1;
char *name = git_win32_path_8dot3_name(path);
const char *def = GIT_DIR_SHORTNAME;
size_t name_len, def_len = CONST_STRLEN(GIT_DIR_SHORTNAME);
git_buf *buf;
#ifdef GIT_WIN32
if (!name)
return 0;
name_len = strlen(name);
if (name_len == def_len && memcmp(name, def, def_len) == 0) {
git__free(name);
return 0;
}
if ((buf = git_array_alloc(repo->reserved_names)) == NULL)
return -1;
git_buf_attach(buf, name, name_len);
return true;
}
bool git_repository__reserved_names(
git_buf **out, size_t *outlen, git_repository *repo, bool include_ntfs)
{
GIT_UNUSED(include_ntfs);
if (repo->reserved_names.size == 0) {
git_buf *buf;
size_t i;
/* Add the static defaults */
for (i = 0; i < git_repository__reserved_names_win32_len; i++) {
if ((buf = git_array_alloc(repo->reserved_names)) == NULL)
goto on_error;
buf->ptr = git_repository__reserved_names_win32[i].ptr;
buf->size = git_repository__reserved_names_win32[i].size;
}
/* Try to add any repo-specific reserved names */
if (!repo->is_bare) {
repo->name_8dot3 = git_win32_path_8dot3_name(repo->path_repository);
const char *reserved_path = repo->path_gitlink ?
repo->path_gitlink : repo->path_repository;
/* We anticipate the 8.3 name is "GIT~1", so use a static for
* easy testing in the common case */
if (repo->name_8dot3 &&
strcasecmp(repo->name_8dot3, git_repository__8dot3_default) == 0)
repo->has_8dot3_default = 1;
if (reserved_names_add8dot3(repo, reserved_path) < 0)
goto on_error;
}
#endif
}
return repo->has_8dot3_default ?
git_repository__8dot3_default : repo->name_8dot3;
*out = repo->reserved_names.ptr;
*outlen = repo->reserved_names.size;
return true;
/* Always give good defaults, even on OOM */
on_error:
*out = git_repository__reserved_names_win32;
*outlen = git_repository__reserved_names_win32_len;
return false;
}
#else
bool git_repository__reserved_names(
git_buf **out, size_t *outlen, git_repository *repo, bool include_ntfs)
{
GIT_UNUSED(repo);
if (include_ntfs) {
*out = git_repository__reserved_names_win32;
*outlen = git_repository__reserved_names_win32_len;
} else {
*out = git_repository__reserved_names_posix;
*outlen = git_repository__reserved_names_posix_len;
}
return true;
}
#endif
static int check_repositoryformatversion(git_config *config)
{
......
......@@ -14,6 +14,7 @@
#include "git2/object.h"
#include "git2/config.h"
#include "array.h"
#include "cache.h"
#include "refs.h"
#include "buffer.h"
......@@ -27,6 +28,9 @@
#define GIT_DIR_MODE 0755
#define GIT_BARE_DIR_MODE 0777
/* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */
#define GIT_DIR_SHORTNAME "GIT~1"
/** Cvar cache identifiers */
typedef enum {
GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */
......@@ -124,13 +128,13 @@ struct git_repository {
git_diff_driver_registry *diff_drivers;
char *path_repository;
char *path_gitlink;
char *workdir;
char *namespace;
char *name_8dot3;
unsigned is_bare:1,
has_8dot3:1,
has_8dot3_default:1;
git_array_t(git_buf) reserved_names;
unsigned is_bare:1;
unsigned int lru_counter;
git_atomic attr_session_key;
......@@ -185,19 +189,24 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head
int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len);
/*
* Gets the DOS-compatible 8.3 "short name". This will return only the
* short name for the repository directory (ie, "git~1" for ".git"). This
* will always return a pointer to `git_repository__8dot3_default` when
* "GIT~1" is the short name. This will return NULL for bare repositories,
* and systems that do not have a short name.
*/
const char *git_repository__8dot3_name(git_repository *repo);
/* The default "reserved names" for a repository */
extern git_buf git_repository__reserved_names_win32[];
extern size_t git_repository__reserved_names_win32_len;
extern git_buf git_repository__reserved_names_posix[];
extern size_t git_repository__reserved_names_posix_len;
/* The default DOS-compatible 8.3 "short name" for a git repository,
* "GIT~1".
/*
* Gets any "reserved names" in the repository. This will return paths
* that should not be allowed in the repository (like ".git") to avoid
* conflicting with the repository path, or with alternate mechanisms to
* the repository path (eg, "GIT~1"). Every attempt will be made to look
* up all possible reserved names - if there was a conflict for the shortname
* GIT~1, for example, this function will try to look up the alternate
* shortname. If that fails, this function returns false, but out and outlen
* will still be populated with good defaults.
*/
extern const char *git_repository__8dot3_default;
extern size_t git_repository__8dot3_default_len;
bool git_repository__reserved_names(
git_buf **out, size_t *outlen, git_repository *repo, bool include_ntfs);
#endif
#include "clar_libgit2.h"
#include "../submodule/submodule_helpers.h"
#include "repository.h"
void test_repo_reservedname__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_repo_reservedname__includes_shortname_on_win32(void)
{
git_repository *repo;
git_buf *reserved;
size_t reserved_len;
repo = cl_git_sandbox_init("nasty");
cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, false));
#ifdef GIT_WIN32
cl_assert_equal_i(2, reserved_len);
cl_assert_equal_s(".git", reserved[0].ptr);
cl_assert_equal_s("GIT~1", reserved[1].ptr);
#else
cl_assert_equal_i(1, reserved_len);
cl_assert_equal_s(".git", reserved[0].ptr);
#endif
}
void test_repo_reservedname__includes_shortname_when_requested(void)
{
git_repository *repo;
git_buf *reserved;
size_t reserved_len;
repo = cl_git_sandbox_init("nasty");
cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, true));
cl_assert_equal_i(2, reserved_len);
cl_assert_equal_s(".git", reserved[0].ptr);
cl_assert_equal_s("GIT~1", reserved[1].ptr);
}
/* Ensures that custom shortnames are included: creates a GIT~1 so that the
* .git folder itself will have to be named GIT~2
*/
void test_repo_reservedname__custom_shortname_recognized(void)
{
#ifdef GIT_WIN32
git_repository *repo;
git_buf *reserved;
size_t reserved_len;
if (!cl_sandbox_supports_8dot3())
clar__skip();
repo = cl_git_sandbox_init("nasty");
cl_must_pass(p_rename("nasty/.git", "nasty/_temp"));
cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666);
cl_must_pass(p_rename("nasty/_temp", "nasty/.git"));
cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, true));
cl_assert_equal_i(3, reserved_len);
cl_assert_equal_s(".git", reserved[0].ptr);
cl_assert_equal_s("GIT~1", reserved[1].ptr);
cl_assert_equal_s("GIT~2", reserved[2].ptr);
#endif
}
/* When looking at the short name for a submodule, we need to prevent
* people from overwriting the `.git` file in the submodule working
* directory itself. We don't want to look at the actual repository
* path, since it will be in the super's repository above us, and
* typically named with the name of our subrepository. Consequently,
* preventing access to the short name of the actual repository path
* would prevent us from creating files with the same name as the
* subrepo. (Eg, a submodule named "libgit2" could not contain a file
* named "libgit2", which would be unfortunate.)
*/
void test_repo_reservedname__submodule_pointer(void)
{
#ifdef GIT_WIN32
git_repository *super_repo, *sub_repo;
git_submodule *sub;
git_buf *sub_reserved;
size_t sub_reserved_len;
if (!cl_sandbox_supports_8dot3())
clar__skip();
super_repo = setup_fixture_submod2();
assert_submodule_exists(super_repo, "sm_unchanged");
cl_git_pass(git_submodule_lookup(&sub, super_repo, "sm_unchanged"));
cl_git_pass(git_submodule_open(&sub_repo, sub));
cl_assert(git_repository__reserved_names(&sub_reserved, &sub_reserved_len, sub_repo, true));
cl_assert_equal_i(2, sub_reserved_len);
cl_assert_equal_s(".git", sub_reserved[0].ptr);
cl_assert_equal_s("GIT~1", sub_reserved[1].ptr);
git_submodule_free(sub);
git_repository_free(sub_repo);
#endif
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment