summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVicent Marti <tanoku@gmail.com>2011-02-05 12:42:41 +0200
committerVicent Marti <tanoku@gmail.com>2011-02-05 12:42:41 +0200
commitf725931b4865317b58c1f1600724cb36e586c332 (patch)
tree571497dd0b831f683ffa6326014db2549e5a9a21
parentc836c332f17ff2da8bdf6d18fb3d59eac2586ca9 (diff)
downloadlibgit2-f725931b4865317b58c1f1600724cb36e586c332.tar.gz
Fix directory/path manipulation methods
The `dirname` and `dirbase` methods have been replaced with the Android implementation, which is actually compilant to some kind of standard. A new method `topdir` has been added, which returns the topmost directory in a path. These changes fix issue #49: `gitfo_prettify_dir_path` converts "./.git/" to ".git/", so the code at src/repository.c:190 goes out of bounds when trying to find the topmost directory. The new `git__topdir` method handles this gracefully, and the fixed `git__dirname` now returns the proper value for the repository's working dir. E.g. /repo/.git/ ==> working dir '/repo/' .git/ ==> working dir '.' Signed-off-by: Vicent Marti <tanoku@gmail.com>
-rw-r--r--src/odb_loose.c2
-rw-r--r--src/repository.c17
-rw-r--r--src/util.c204
-rw-r--r--src/util.h55
-rw-r--r--tests/t00-core.c98
5 files changed, 286 insertions, 90 deletions
diff --git a/src/odb_loose.c b/src/odb_loose.c
index b031bde3f..f89eb71ff 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -59,7 +59,7 @@ static int make_temp_file(git_file *fd, char *tmp, size_t n, char *file)
size_t tmplen = strlen(template);
int dirlen;
- if ((dirlen = git__dirname(tmp, n, file)) < 0)
+ if ((dirlen = git__dirname_r(tmp, n, file)) < 0)
return GIT_ERROR;
if ((dirlen + tmplen) >= n)
diff --git a/src/repository.c b/src/repository.c
index 37d5a49f0..3093cf5bd 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -157,7 +157,9 @@ static int assign_repository_DIRs(git_repository *repo,
static int guess_repository_DIRs(git_repository *repo, const char *repository_path)
{
- char path_aux[GIT_PATH_MAX], *last_DIR;
+ char path_aux[GIT_PATH_MAX];
+ const char *topdir;
+
int path_len;
int error = GIT_SUCCESS;
@@ -185,12 +187,10 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa
path_aux[path_len] = 0;
- last_DIR = (path_aux + path_len - 2);
+ if ((topdir = git__topdir(path_aux)) == NULL)
+ return GIT_EINVALIDPATH;
- while (*last_DIR != '/')
- last_DIR--;
-
- if (strcmp(last_DIR, GIT_DIR) == 0) {
+ if (strcmp(topdir, GIT_DIR) == 0) {
repo->is_bare = 0;
/* index file */
@@ -198,8 +198,9 @@ static int guess_repository_DIRs(git_repository *repo, const char *repository_pa
repo->path_index = git__strdup(path_aux);
/* working dir */
- *(last_DIR + 1) = 0;
- repo->path_workdir = git__strdup(path_aux);
+ repo->path_workdir = git__dirname(path_aux);
+ if (repo->path_workdir == NULL)
+ return GIT_EINVALIDPATH;
} else {
repo->is_bare = 1;
diff --git a/src/util.c b/src/util.c
index ef97f68a4..d9d77eccf 100644
--- a/src/util.c
+++ b/src/util.c
@@ -36,65 +36,193 @@ int git__suffixcmp(const char *str, const char *suffix)
return strcmp(str + (a - b), suffix);
}
-int git__dirname(char *dir, size_t n, char *path)
+/*
+ * Based on the Android implementation, BSD licensed.
+ * Check http://android.git.kernel.org/
+ */
+int git__basename_r(char *buffer, size_t bufflen, const char *path)
{
- char *s;
- size_t len;
+ const char *endp, *startp;
+ int len, result;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ startp = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ /* All slashes becomes "/" */
+ if (endp == path && *endp == '/') {
+ startp = "/";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Find the start of the base */
+ startp = endp;
+ while (startp > path && *(startp - 1) != '/')
+ startp--;
- assert(dir && n > 1);
+ len = endp - startp +1;
- if (!path || !*path || (s = strrchr(path, '/')) == NULL) {
- strcpy(dir, ".");
- return 1;
+Exit:
+ result = len;
+ if (buffer == NULL) {
+ return result;
+ }
+ if (len > (int)bufflen-1) {
+ len = (int)bufflen-1;
+ result = GIT_ENOMEM;
}
- if (s == path) { /* "/[aaa]" */
- strcpy(dir, "/");
- return 1;
+ if (len >= 0) {
+ memcpy(buffer, startp, len);
+ buffer[len] = 0;
}
+ return result;
+}
- if ((len = s - path) >= n)
- return GIT_ERROR;
+/*
+ * Based on the Android implementation, BSD licensed.
+ * Check http://android.git.kernel.org/
+ */
+int git__dirname_r(char *buffer, size_t bufflen, const char *path)
+{
+ const char *endp;
+ int result, len;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ path = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ /* Find the start of the dir */
+ while (endp > path && *endp != '/')
+ endp--;
+
+ /* Either the dir is "/" or there are no slashes */
+ if (endp == path) {
+ path = (*endp == '/') ? "/" : ".";
+ len = 1;
+ goto Exit;
+ }
+
+ do {
+ endp--;
+ } while (endp > path && *endp == '/');
+
+ len = endp - path +1;
+
+Exit:
+ result = len;
+ if (len+1 > GIT_PATH_MAX) {
+ return GIT_ENOMEM;
+ }
+ if (buffer == NULL)
+ return result;
+
+ if (len > (int)bufflen-1) {
+ len = (int)bufflen-1;
+ result = GIT_ENOMEM;
+ }
+
+ if (len >= 0) {
+ memcpy(buffer, path, len);
+ buffer[len] = 0;
+ }
+ return result;
+}
+
+
+char *git__dirname(const char *path)
+{
+ char *dname = NULL;
+ int len;
+
+ len = (path ? strlen(path) : 0) + 2;
+ dname = (char *)git__malloc(len);
+ if (dname == NULL)
+ return NULL;
- memcpy(dir, path, len);
- dir[len] = '\0';
+ if (git__dirname_r(dname, len, path) < GIT_SUCCESS) {
+ free(dname);
+ return NULL;
+ }
- return len;
+ return dname;
}
-int git__basename(char *base, size_t n, char *path)
+char *git__basename(const char *path)
{
- char *s;
- size_t len;
+ char *bname = NULL;
+ int len;
- assert(base && n > 1);
+ len = (path ? strlen(path) : 0) + 2;
+ bname = (char *)git__malloc(len);
+ if (bname == NULL)
+ return NULL;
- if (!path || !*path) {
- strcpy(base, ".");
- return 1;
+ if (git__basename_r(bname, len, path) < GIT_SUCCESS) {
+ free(bname);
+ return NULL;
}
+
+ return bname;
+}
+
+
+const char *git__topdir(const char *path)
+{
+ size_t len;
+ int i;
+
+ assert(path);
len = strlen(path);
- if ((s = strrchr(path, '/')) == NULL) {
- if (len >= n)
- return GIT_ERROR;
- strcpy(base, path);
- return len;
- }
+ if (!len || path[len - 1] != '/')
+ return NULL;
- if (s == path && len == 1) { /* "/" */
- strcpy(base, "/");
- return 1;
- }
+ for (i = len - 2; i >= 0; --i)
+ if (path[i] == '/')
+ break;
- len -= s - path;
- if (len >= n)
- return GIT_ERROR;
+ return &path[i + 1];
+}
+
+static char *strtok_raw(char *output, char *src, char *delimit, int keep)
+{
+ while (*src && strchr(delimit, *src) == NULL)
+ *output++ = *src++;
- memcpy(base, s+1, len);
- base[len] = '\0';
+ *output = 0;
+
+ if (keep)
+ return src;
+ else
+ return *src ? src+1 : src;
+}
- return len;
+char *git__strtok(char *output, char *src, char *delimit)
+{
+ return strtok_raw(output, src, delimit, 0);
+}
+
+char *git__strtok_keep(char *output, char *src, char *delimit)
+{
+ return strtok_raw(output, src, delimit, 1);
}
void git__hexdump(const char *buffer, size_t len)
diff --git a/src/util.h b/src/util.h
index 99c4f5a84..67ff4aec1 100644
--- a/src/util.h
+++ b/src/util.h
@@ -19,8 +19,44 @@ extern int git__fmt(char *, size_t, const char *, ...)
extern int git__prefixcmp(const char *str, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
-extern int git__dirname(char *dir, size_t n, char *path);
-extern int git__basename(char *base, size_t n, char *path);
+/*
+ * The dirname() function shall take a pointer to a character string
+ * that contains a pathname, and return a pointer to a string that is a
+ * pathname of the parent directory of that file. Trailing '/' characters
+ * in the path are not counted as part of the path.
+ *
+ * If path does not contain a '/', then dirname() shall return a pointer to
+ * the string ".". If path is a null pointer or points to an empty string,
+ * dirname() shall return a pointer to the string "." .
+ *
+ * The `git__dirname` implementation is thread safe. The returned
+ * string must be manually free'd.
+ *
+ * The `git__dirname_r` implementation expects a string allocated
+ * by the user with big enough size.
+ */
+extern char *git__dirname(const char *path);
+extern int git__dirname_r(char *buffer, size_t bufflen, const char *path);
+
+/*
+ * This function returns the basename of the file, which is the last
+ * part of its full name given by fname, with the drive letter and
+ * leading directories stripped off. For example, the basename of
+ * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo.
+ *
+ * Trailing slashes and backslashes are significant: the basename of
+ * c:/foo/bar/ is an empty string after the rightmost slash.
+ *
+ * The `git__basename` implementation is thread safe. The returned
+ * string must be manually free'd.
+ *
+ * The `git__basename_r` implementation expects a string allocated
+ * by the user with big enough size.
+ */
+extern char *git__basename(const char *path);
+extern int git__basename_r(char *buffer, size_t bufflen, const char *path);
+
+extern const char *git__topdir(const char *path);
extern void git__hexdump(const char *buffer, size_t n);
extern uint32_t git__hash(const void *key, int len, uint32_t seed);
@@ -40,6 +76,21 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s))))
#endif
+enum git_splitpath_flags
+{
+ GIT_SPL_PATH = 1,
+ GIT_SPL_FILE = 2,
+ GIT_SPL_EXT = 4,
+ GIT_SPL_PATH_FILE = GIT_SPL_PATH + GIT_SPL_FILE,
+ GIT_SPL_FILE_EXT = GIT_SPL_FILE + GIT_SPL_EXT,
+ GIT_SPL_EXT_NO_PERIOD = 8,
+};
+
+
+extern char *git__splitpath(char *path, int flag);
+extern char *git__strtok(char *output, char *src, char *delimit);
+extern char *git__strtok_keep(char *output, char *src, char *delimit);
+
/*
* Realloc the buffer pointed at by variable 'x' so that it can hold
* at least 'nr' entries; the number of entries currently allocated
diff --git a/tests/t00-core.c b/tests/t00-core.c
index 08bd5cb13..7dd09955f 100644
--- a/tests/t00-core.c
+++ b/tests/t00-core.c
@@ -61,61 +61,76 @@ BEGIN_TEST("strutil", suffix_comparison)
END_TEST
BEGIN_TEST("strutil", dirname)
- char dir[64];
-
- must_be_true(!(git__dirname(dir, sizeof(dir), NULL) < 0));
- must_be_true(!strcmp(dir, "."));
-
- must_be_true(!(git__dirname(dir, sizeof(dir), "") < 0));
- must_be_true(!strcmp(dir, "."));
-
- must_be_true(!(git__dirname(dir, sizeof(dir), "a") < 0));
- must_be_true(!strcmp(dir, "."));
-
- must_be_true(!(git__dirname(dir, sizeof(dir), "/") < 0));
- must_be_true(!strcmp(dir, "/"));
-
- must_be_true(!(git__dirname(dir, sizeof(dir), "/usr") < 0));
- must_be_true(!strcmp(dir, "/"));
+ char dir[64], *dir2;
+
+#define DIRNAME_TEST(A, B) { \
+ must_be_true(git__dirname_r(dir, sizeof(dir), A) >= 0); \
+ must_be_true(strcmp(dir, B) == 0); \
+ must_be_true((dir2 = git__dirname(A)) != NULL); \
+ must_be_true(strcmp(dir2, B) == 0); \
+ free(dir2); \
+}
- /* TODO: should this be "/" instead (ie strip trailing / first) */
- must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/") < 0));
- must_be_true(!strcmp(dir, "/usr"));
+ DIRNAME_TEST(NULL, ".");
+ DIRNAME_TEST("", ".");
+ DIRNAME_TEST("a", ".");
+ DIRNAME_TEST("/", "/");
+ DIRNAME_TEST("/usr", "/");
+ DIRNAME_TEST("/usr/", "/");
+ DIRNAME_TEST("/usr/lib", "/usr");
+ DIRNAME_TEST("usr/lib", "usr");
+ DIRNAME_TEST(".git/", ".");
- must_be_true(!(git__dirname(dir, sizeof(dir), "/usr/lib") < 0));
- must_be_true(!strcmp(dir, "/usr"));
+#undef DIRNAME_TEST
- must_be_true(!(git__dirname(dir, sizeof(dir), "usr/lib") < 0));
- must_be_true(!strcmp(dir, "usr"));
END_TEST
BEGIN_TEST("strutil", basename)
- char base[64];
+ char base[64], *base2;
+
+#define BASENAME_TEST(A, B) { \
+ must_be_true(git__basename_r(base, sizeof(base), A) >= 0); \
+ must_be_true(strcmp(base, B) == 0); \
+ must_be_true((base2 = git__basename(A)) != NULL); \
+ must_be_true(strcmp(base2, B) == 0); \
+ free(base2); \
+}
- must_be_true(!(git__basename(base, sizeof(base), NULL) < 0));
- must_be_true(!strcmp(base, "."));
+ BASENAME_TEST(NULL, ".");
+ BASENAME_TEST("", ".");
+ BASENAME_TEST("a", "a");
+ BASENAME_TEST("/", "/");
+ BASENAME_TEST("/usr", "usr");
+ BASENAME_TEST("/usr/", "usr");
+ BASENAME_TEST("/usr/lib", "lib");
+ BASENAME_TEST("usr/lib", "lib");
- must_be_true(!(git__basename(base, sizeof(base), "") < 0));
- must_be_true(!strcmp(base, "."));
+#undef BASENAME_TEST
- must_be_true(!(git__basename(base, sizeof(base), "a") < 0));
- must_be_true(!strcmp(base, "a"));
+END_TEST
- must_be_true(!(git__basename(base, sizeof(base), "/") < 0));
- must_be_true(!strcmp(base, "/"));
+BEGIN_TEST("strutil", topdir)
+ const char *dir;
- must_be_true(!(git__basename(base, sizeof(base), "/usr") < 0));
- must_be_true(!strcmp(base, "usr"));
+#define TOPDIR_TEST(A, B) { \
+ must_be_true((dir = git__topdir(A)) != NULL); \
+ must_be_true(strcmp(dir, B) == 0); \
+}
- /* TODO: should this be "usr" instead (ie strip trailing / first) */
- must_be_true(!(git__basename(base, sizeof(base), "/usr/") < 0));
- must_be_true(!strcmp(base, ""));
+ TOPDIR_TEST(".git/", ".git/");
+ TOPDIR_TEST("/.git/", ".git/");
+ TOPDIR_TEST("usr/local/.git/", ".git/");
+ TOPDIR_TEST("./.git/", ".git/");
+ TOPDIR_TEST("/usr/.git/", ".git/");
+ TOPDIR_TEST("/", "/");
+ TOPDIR_TEST("a/", "a/");
- must_be_true(!(git__basename(base, sizeof(base), "/usr/lib") < 0));
- must_be_true(!strcmp(base, "lib"));
+ must_be_true(git__topdir("/usr/.git") == NULL);
+ must_be_true(git__topdir(".") == NULL);
+ must_be_true(git__topdir("") == NULL);
+ must_be_true(git__topdir("a") == NULL);
- must_be_true(!(git__basename(base, sizeof(base), "usr/lib") < 0));
- must_be_true(!strcmp(base, "lib"));
+#undef TOPDIR_TEST
END_TEST
/* Initial size of 1 will cause writing past array bounds prior to fix */
@@ -579,6 +594,7 @@ git_testsuite *libgit2_suite_core(void)
ADD_TEST(suite, "strutil", suffix_comparison);
ADD_TEST(suite, "strutil", dirname);
ADD_TEST(suite, "strutil", basename);
+ ADD_TEST(suite, "strutil", topdir);
ADD_TEST(suite, "vector", initial_size_one);
ADD_TEST(suite, "vector", remove);