summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2015-03-18 04:59:16 +0100
committerCarlos Martín Nieto <cmn@dwim.me>2015-03-18 04:59:16 +0100
commit83ad46f7269aa9768c768a366f5cc3912f84a421 (patch)
treec88e81545510f33d013e6fc316464a7bacaf8a9f
parent4c2e6b1e871aaee3de35862da5dd0135fbcd5ec7 (diff)
parent4196dd8e8fceaaa42ad5854e1ce362c14b8c0bf6 (diff)
downloadlibgit2-83ad46f7269aa9768c768a366f5cc3912f84a421.tar.gz
Merge remote-tracking branch 'ethomson/submodule_8dot3'
-rw-r--r--src/path.c39
-rw-r--r--src/repository.c147
-rw-r--r--src/repository.h42
-rw-r--r--tests/repo/reservedname.c108
4 files changed, 275 insertions, 61 deletions
diff --git a/src/path.c b/src/path.c
index 64c7b7e12..993fcf60f 100644
--- a/src/path.c
+++ b/src/path.c
@@ -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;
diff --git a/src/repository.c b/src/repository.c
index b1f94f0e2..6a80070d1 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -38,8 +38,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)
{
@@ -111,6 +119,8 @@ void git_repository__cleanup(git_repository *repo)
void git_repository_free(git_repository *repo)
{
+ size_t i;
+
if (repo == NULL)
return;
@@ -121,10 +131,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__free(repo->ident_name);
git__free(repo->ident_email);
@@ -156,18 +168,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)
@@ -327,6 +347,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)
@@ -369,9 +390,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;
}
@@ -458,13 +484,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;
@@ -474,6 +503,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 {
@@ -525,7 +559,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(
@@ -810,27 +844,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)
{
diff --git a/src/repository.h b/src/repository.h
index 56d443d3c..253287368 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -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,16 +128,17 @@ struct git_repository {
git_diff_driver_registry *diff_drivers;
char *path_repository;
+ char *path_gitlink;
char *workdir;
char *namespace;
- char *name_8dot3;
char *ident_name;
char *ident_email;
- 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;
@@ -188,19 +193,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
diff --git a/tests/repo/reservedname.c b/tests/repo/reservedname.c
new file mode 100644
index 000000000..faea0cc2b
--- /dev/null
+++ b/tests/repo/reservedname.c
@@ -0,0 +1,108 @@
+#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
+}