From 039fc4067989a14a09784ed16ce7126ac75461cb Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 10 Jul 2012 15:10:14 -0700 Subject: [PATCH 1/6] Add a couple of useful git_buf utilities * `git_buf_rfind` (with tests and tests for `git_buf_rfind_next`) * `git_buf_puts_escaped` and `git_buf_puts_escaped_regex` (with tests) to copy strings into a buffer while injecting an escape sequence (e.g. '\') in front of particular characters. --- src/buffer.c | 34 +++++++++++++++++++++++++++++ src/buffer.h | 19 ++++++++++++++++ tests-clar/core/buffer.c | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/src/buffer.c b/src/buffer.c index 04aaec3df..d16b17160 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -141,6 +141,40 @@ int git_buf_puts(git_buf *buf, const char *string) return git_buf_put(buf, string, strlen(string)); } +int git_buf_puts_escaped( + git_buf *buf, const char *string, const char *esc_chars, const char *esc_with) +{ + const char *scan = string; + size_t total = 0, esc_with_len = strlen(esc_with); + + while (*scan) { + size_t count = strcspn(scan, esc_chars); + total += count + 1 + esc_with_len; + scan += count + 1; + } + + ENSURE_SIZE(buf, buf->size + total + 1); + + for (scan = string; *scan; ) { + size_t count = strcspn(scan, esc_chars); + + memmove(buf->ptr + buf->size, scan, count); + scan += count; + buf->size += count; + + if (*scan) { + memmove(buf->ptr + buf->size, esc_with, esc_with_len); + buf->size += esc_with_len; + + memmove(buf->ptr + buf->size, scan, 1); + scan += 1; + buf->size += 1; + } + } + + return 0; +} + int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) { int len; diff --git a/src/buffer.h b/src/buffer.h index 50c75f64e..75f3b0e4f 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -90,6 +90,18 @@ void git_buf_rtruncate_at_char(git_buf *path, char separator); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b); +/** + * Copy string into buf prefixing every character that is contained in the + * esc_chars string with the esc_with string. + */ +int git_buf_puts_escaped( + git_buf *buf, const char *string, const char *esc_chars, const char *esc_with); + +GIT_INLINE(int) git_buf_puts_escape_regex(git_buf *buf, const char *string) +{ + return git_buf_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\"); +} + /** * Join two strings as paths, inserting a slash between as needed. * @return 0 on success, -1 on failure @@ -121,6 +133,13 @@ GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch) return idx; } +GIT_INLINE(ssize_t) git_buf_rfind(git_buf *buf, char ch) +{ + ssize_t idx = (ssize_t)buf->size - 1; + while (idx >= 0 && buf->ptr[idx] != ch) idx--; + return idx; +} + /* Remove whitespace from the end of the buffer */ void git_buf_rtrim(git_buf *buf); diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c index 6a718f459..996cb7bcb 100644 --- a/tests-clar/core/buffer.c +++ b/tests-clar/core/buffer.c @@ -611,3 +611,50 @@ void test_core_buffer__11(void) git_buf_free(&a); } + +void test_core_buffer__rfind_variants(void) +{ + git_buf a = GIT_BUF_INIT; + ssize_t len; + + cl_git_pass(git_buf_sets(&a, "/this/is/it/")); + + len = (ssize_t)git_buf_len(&a); + + cl_assert(git_buf_rfind(&a, '/') == len - 1); + cl_assert(git_buf_rfind_next(&a, '/') == len - 4); + + cl_assert(git_buf_rfind(&a, 'i') == len - 3); + cl_assert(git_buf_rfind_next(&a, 'i') == len - 3); + + cl_assert(git_buf_rfind(&a, 'h') == 2); + cl_assert(git_buf_rfind_next(&a, 'h') == 2); + + cl_assert(git_buf_rfind(&a, 'q') == -1); + cl_assert(git_buf_rfind_next(&a, 'q') == -1); + + git_buf_clear(&a); +} + +void test_core_buffer__puts_escaped(void) +{ + git_buf a = GIT_BUF_INIT; + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "", "")); + cl_assert_equal_s("this is a test", a.ptr); + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "t", "\\")); + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "i ", "__")); + cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); + cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); + + git_buf_free(&a); +} From b0fe11292219f5d63f2159e0b0eb24ff21d66b10 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 10 Jul 2012 15:13:30 -0700 Subject: [PATCH 2/6] Add path utilities to resolve relative paths This makes it easy to take a buffer containing a path with relative references (i.e. .. or . path segments) and resolve all of those into a clean path. This can be applied to URLs as well as file paths which can be useful. As part of this, I made the drive-letter detection apply on all platforms, not just windows. If you give a path that looks like "c:/..." on any platform, it seems like we might as well detect that as a rooted path. I suppose if you create a directory named "x:" on another platform and want to use that as the beginning of a relative path under the root directory of your repo, this could cause a problem, but then it seems like you're asking for trouble. --- src/path.c | 69 ++++++++++++++++++++++++++++++++++++++++-- src/path.h | 23 ++++++++++++++ tests-clar/core/path.c | 51 +++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 3 deletions(-) diff --git a/src/path.c b/src/path.c index 9c88240e0..e9bc4871c 100644 --- a/src/path.c +++ b/src/path.c @@ -17,9 +17,7 @@ #include #include -#ifdef GIT_WIN32 #define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':') -#endif /* * Based on the Android implementation, BSD licensed. @@ -172,11 +170,11 @@ int git_path_root(const char *path) { int offset = 0; -#ifdef GIT_WIN32 /* Does the root of the path look like a windows drive ? */ if (LOOKS_LIKE_DRIVE_PREFIX(path)) offset += 2; +#ifdef GIT_WIN32 /* Are we dealing with a windows network path? */ else if ((path[0] == '/' && path[1] == '/') || (path[0] == '\\' && path[1] == '\\')) @@ -464,6 +462,71 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base) return error; } +int git_path_resolve_relative(git_buf *path, size_t ceiling) +{ + char *base, *to, *from, *next; + size_t len; + + if (!path || git_buf_oom(path)) + return -1; + + if (ceiling > path->size) + ceiling = path->size; + + /* recognize drive prefixes, etc. that should not be backed over */ + if (ceiling == 0) + ceiling = git_path_root(path->ptr) + 1; + + /* recognize URL prefixes that should not be backed over */ + if (ceiling == 0) { + for (next = path->ptr; *next && git__isalpha(*next); ++next); + if (next[0] == ':' && next[1] == '/' && next[2] == '/') + ceiling = (next + 3) - path->ptr; + } + + base = to = from = path->ptr + ceiling; + + while (*from) { + for (next = from; *next && *next != '/'; ++next); + + len = next - from; + + if (len == 1 && from[0] == '.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == '.' && from[1] == '.') { + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + + else { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + } + + from += len; + + while (*from == '/') from++; + } + + *to = '\0'; + + path->size = to - path->ptr; + + return 0; +} + +int git_path_apply_relative(git_buf *target, const char *relpath) +{ + git_buf_joinpath(target, git_buf_cstr(target), relpath); + return git_path_resolve_relative(target, 0); +} + int git_path_cmp( const char *name1, size_t len1, int isdir1, const char *name2, size_t len2, int isdir2) diff --git a/src/path.h b/src/path.h index fd76805e5..d68393b3d 100644 --- a/src/path.h +++ b/src/path.h @@ -185,6 +185,29 @@ extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char */ extern int git_path_find_dir(git_buf *dir, const char *path, const char *base); +/** + * Resolve relative references within a path. + * + * This eliminates "./" and "../" relative references inside a path, + * as well as condensing multiple slashes into single ones. It will + * not touch the path before the "ceiling" length. + * + * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL + * prefix and not touch that part of the path. + */ +extern int git_path_resolve_relative(git_buf *path, size_t ceiling); + +/** + * Apply a relative path to base path. + * + * Note that the base path could be a filename or a URL and this + * should still work. The relative path is walked segment by segment + * with three rules: series of slashes will be condensed to a single + * slash, "." will be eaten with no change, and ".." will remove a + * segment from the base path. + */ +extern int git_path_apply_relative(git_buf *target, const char *relpath); + /** * Walk each directory entry, except '.' and '..', calling fn(state). * diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c index d826612ac..864393b70 100644 --- a/tests-clar/core/path.c +++ b/tests-clar/core/path.c @@ -418,3 +418,54 @@ void test_core_path__13_cannot_prettify_a_non_existing_file(void) git_buf_free(&p); } + +void test_core_path__14_apply_relative(void) +{ + git_buf p = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&p, "/this/is/a/base")); + + cl_git_pass(git_path_apply_relative(&p, "../test")); + cl_assert_equal_s("/this/is/a/test", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../the/./end")); + cl_assert_equal_s("/this/is/the/end", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "./of/this/../the/string")); + cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../../..")); + cl_assert_equal_s("/this/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_assert_equal_s("/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../..")); + cl_assert_equal_s("/", p.ptr); + + + cl_git_pass(git_buf_sets(&p, "d:/another/test")); + + cl_git_pass(git_path_apply_relative(&p, "../../../../..")); + cl_assert_equal_s("d:/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/.")); + cl_assert_equal_s("d:/from/here/and/back/", p.ptr); + + + cl_git_pass(git_buf_sets(&p, "https://my.url.com/test.git")); + + cl_git_pass(git_path_apply_relative(&p, "../another.git")); + cl_assert_equal_s("https://my.url.com/another.git", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../full/path/url.patch")); + cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "..")); + cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_assert_equal_s("https://", p.ptr); + + git_buf_free(&p); +} From c3a875c975cde11ffd18947c419b1cd38205e6c3 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 10 Jul 2012 15:20:08 -0700 Subject: [PATCH 3/6] Adding unicode space to match crlf patterns Adding 0x85 to `git__isspace` since we also look for that in filter.c as a whitespace character. --- src/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.h b/src/util.h index adc665027..bc122332f 100644 --- a/src/util.h +++ b/src/util.h @@ -206,7 +206,7 @@ GIT_INLINE(bool) git__isalpha(int c) GIT_INLINE(bool) git__isspace(int c) { - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); } GIT_INLINE(bool) git__iswildcard(int c) From b3ff1dab317dc9194c4bc124afd95ae9be2ad57b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 10 Jul 2012 15:22:39 -0700 Subject: [PATCH 4/6] Adding git_config_foreach_match() iteration fn Adding a new config iteration function that let's you iterate over just the config entries that match a particular regular expression. The old foreach becomes a simple use of this with an empty pattern. This also fixes an apparent bug in the existing `git_config_foreach` where returning a non-zero value from the iteration callback was not correctly aborting the iteration and the returned value was not being propogated back to the caller of foreach. Added to tests to cover all these changes. --- include/git2/config.h | 20 ++++++++++- src/config.c | 16 +++++++-- src/config_file.c | 39 ++++++++++++++++----- src/config_file.h | 17 ++++++++- tests-clar/config/read.c | 75 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 13 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 36946c4a5..c46e7fc9d 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -33,7 +33,7 @@ struct git_config_file { int (*set)(struct git_config_file *, const char *key, const char *value); int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_file *, const char *key); - int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data); + int (*foreach)(struct git_config_file *, const char *, int (*fn)(const char *, const char *, void *), void *data); void (*free)(struct git_config_file *); }; @@ -314,6 +314,24 @@ GIT_EXTERN(int) git_config_foreach( int (*callback)(const char *var_name, const char *value, void *payload), void *payload); +/** + * Perform an operation on each config variable matching a regular expression. + * + * This behaviors like `git_config_foreach` with an additional filter of a + * regular expression that filters which config keys are passed to the + * callback. + * + * @param cfg where to get the variables from + * @param regexp regular expression to match against config names + * @param callback the function to call on each variable + * @param payload the data to pass to the callback + * @return 0 or the return value of the callback which didn't return 0 + */ +GIT_EXTERN(int) git_config_foreach_match( + git_config *cfg, + const char *regexp, + int (*callback)(const char *var_name, const char *value, void *payload), + void *payload); /** * Query the value of a config variable and return it mapped to diff --git a/src/config.c b/src/config.c index d18b85c30..98fb3b20d 100644 --- a/src/config.c +++ b/src/config.c @@ -136,17 +136,27 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority) * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +int git_config_foreach( + git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +{ + return git_config_foreach_match(cfg, NULL, fn, data); +} + +int git_config_foreach_match( + git_config *cfg, + const char *regexp, + int (*fn)(const char *, const char *, void *), + void *data) { int ret = 0; unsigned int i; file_internal *internal; git_config_file *file; - for(i = 0; i < cfg->files.length && ret == 0; ++i) { + for (i = 0; i < cfg->files.length && ret == 0; ++i) { internal = git_vector_get(&cfg->files, i); file = internal->file; - ret = file->foreach(file, fn, data); + ret = file->foreach(file, regexp, fn, data); } return ret; diff --git a/src/config_file.c b/src/config_file.c index fd1aa8d08..1f3ebfca9 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -188,25 +188,46 @@ static void backend_free(git_config_file *_backend) git__free(backend); } -static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data) +static int file_foreach( + git_config_file *backend, + const char *regexp, + int (*fn)(const char *, const char *, void *), + void *data) { diskfile_backend *b = (diskfile_backend *)backend; cvar_t *var; const char *key; + regex_t regex; + int result = 0; if (!b->values) return 0; - git_strmap_foreach(b->values, key, var, - do { - if (fn(key, var->value, data) < 0) - break; + if (regexp != NULL) { + if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(®ex, result); + regfree(®ex); + return -1; + } + } - var = CVAR_LIST_NEXT(var); - } while (var != NULL); + git_strmap_foreach(b->values, key, var, + for (; var != NULL; var = CVAR_LIST_NEXT(var)) { + /* skip non-matching keys if regexp was provided */ + if (regexp && regexec(®ex, key, 0, NULL, 0) != 0) + continue; + + /* abort iterator on non-zero return value */ + if ((result = fn(key, var->value, data)) != 0) + goto cleanup; + } ); - return 0; +cleanup: + if (regexp != NULL) + regfree(®ex); + + return result; } static int config_set(git_config_file *cfg, const char *name, const char *value) @@ -337,6 +358,7 @@ static int config_get_multivar( result = regcomp(®ex, regex_str, REG_EXTENDED); if (result < 0) { giterr_set_regex(®ex, result); + regfree(®ex); return -1; } @@ -396,6 +418,7 @@ static int config_set_multivar( if (result < 0) { git__free(key); giterr_set_regex(&preg, result); + regfree(&preg); return -1; } diff --git a/src/config_file.h b/src/config_file.h index 0080b5713..c31292881 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -19,12 +19,27 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg) cfg->free(cfg); } +GIT_INLINE(int) git_config_file_set_string( + git_config_file *cfg, const char *name, const char *value) +{ + return cfg->set(cfg, name, value); +} + GIT_INLINE(int) git_config_file_foreach( git_config_file *cfg, int (*fn)(const char *key, const char *value, void *data), void *data) { - return cfg->foreach(cfg, fn, data); + return cfg->foreach(cfg, NULL, fn, data); +} + +GIT_INLINE(int) git_config_file_foreach_match( + git_config_file *cfg, + const char *regexp, + int (*fn)(const char *key, const char *value, void *data), + void *data) +{ + return cfg->foreach(cfg, regexp, fn, data); } #endif diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index f33bdd89e..a8504da02 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -191,6 +191,81 @@ void test_config_read__escaping_quotes(void) git_config_free(cfg); } +static int count_cfg_entries( + const char *var_name, const char *value, void *payload) +{ + int *count = payload; + GIT_UNUSED(var_name); + GIT_UNUSED(value); + (*count)++; + return 0; +} + +static int cfg_callback_countdown( + const char *var_name, const char *value, void *payload) +{ + int *count = payload; + GIT_UNUSED(var_name); + GIT_UNUSED(value); + (*count)--; + if (*count == 0) + return -100; + return 0; +} + +void test_config_read__foreach(void) +{ + git_config *cfg; + int count, ret; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass(git_config_foreach(cfg, count_cfg_entries, &count)); + cl_assert_equal_i(5, count); + + count = 3; + cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); + cl_assert_equal_i(-100, ret); + + git_config_free(cfg); +} + +void test_config_read__foreach_match(void) +{ + git_config *cfg; + int count; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "core.*", count_cfg_entries, &count)); + cl_assert_equal_i(3, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "remote\\.ab.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*url$", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*dummy.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*nomatch.*", count_cfg_entries, &count)); + cl_assert_equal_i(0, count); + + git_config_free(cfg); +} + #if 0 BEGIN_TEST(config10, "a repo's config overrides the global config") From 991a56c704f931e7eb52a0583e5d8684803f3b04 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 10 Jul 2012 15:35:38 -0700 Subject: [PATCH 5/6] Add flag to write gitlink on setting repo workdir This added a flag to the `git_repository_set_workdir()` function that enables generation of a `.git` gitlink file that links the new workdir to the parent repository. Essentially, the flag tells the function to write out the changes to disk to permanently set the workdir of the repository to the new path. If you pass this flag as true, then setting the workdir to something other than the default workdir (i.e. the parent of the .git repo directory), will create a plain file named ".git" with the standard gitlink contents "gitdir: ", and also update the "core.worktree" and "core.bare" config values. Setting the workdir to the default repo workdir will clear the core.worktree flag (but still permanently set core.bare to false). BTW, the libgit2 API does not currently provide a function for clearing the workdir and converting a non-bare repo into a bare one. --- include/git2/repository.h | 5 +- src/repository.c | 92 ++++++++++++++++++++++++++++++++---- tests-clar/repo/setters.c | 31 ++++++++++-- tests-clar/status/worktree.c | 4 +- 4 files changed, 116 insertions(+), 16 deletions(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index 0b56a0870..ff81b75ec 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -194,9 +194,12 @@ GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); * * @param repo A repository object * @param workdir The path to a working directory + * @param update_gitlink Create/update gitlink in workdir and set config + * "core.worktree" (if workdir is not the parent of the .git directory) * @return 0, or an error code */ -GIT_EXTERN(int) git_repository_set_workdir(git_repository *repo, const char *workdir); +GIT_EXTERN(int) git_repository_set_workdir( + git_repository *repo, const char *workdir, int update_gitlink); /** * Check if a repository is bare diff --git a/src/repository.c b/src/repository.c index bee7b3ee6..a2931713e 100644 --- a/src/repository.c +++ b/src/repository.c @@ -741,15 +741,24 @@ static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) #define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n" static int repo_write_template( - const char *git_dir, const char *file, mode_t mode, const char *content) + const char *git_dir, + bool allow_overwrite, + const char *file, + mode_t mode, + const char *content) { git_buf path = GIT_BUF_INIT; - int fd, error = 0; + int fd, error = 0, flags; if (git_buf_joinpath(&path, git_dir, file) < 0) return -1; - fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode); + if (allow_overwrite) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else + flags = O_WRONLY | O_CREAT | O_EXCL; + + fd = p_open(git_buf_cstr(&path), flags, mode); if (fd >= 0) { error = p_write(fd, content, strlen(content)); @@ -811,7 +820,7 @@ static int repo_init_structure(const char *git_dir, int is_bare) /* Make template files as needed */ for (i = 0; tmpl[i].file != NULL; ++i) { if (repo_write_template( - git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) + git_dir, false, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) return -1; } @@ -943,8 +952,47 @@ const char *git_repository_workdir(git_repository *repo) return repo->workdir; } -int git_repository_set_workdir(git_repository *repo, const char *workdir) +static int write_gitlink( + const char *in_dir, const char *to_repo) { + int error; + git_buf buf = GIT_BUF_INIT; + struct stat st; + + if (git_path_dirname_r(&buf, to_repo) < 0 || + git_path_to_dir(&buf) < 0) + return -1; + + /* don't write gitlink to natural workdir */ + if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 && + strcmp(in_dir, buf.ptr) == 0) + return GIT_PASSTHROUGH; + + if (git_buf_joinpath(&buf, in_dir, DOT_GIT) < 0) + return -1; + + if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { + giterr_set(GITERR_REPOSITORY, + "Cannot overwrite gitlink file into path '%s'", in_dir); + return GIT_EEXISTS; + } + + git_buf_clear(&buf); + + if (git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo) < 0) + return -1; + + error = repo_write_template(in_dir, true, DOT_GIT, 0644, buf.ptr); + + git_buf_free(&buf); + + return error; +} + +int git_repository_set_workdir( + git_repository *repo, const char *workdir, int update_gitlink) +{ + int error = 0; git_buf path = GIT_BUF_INIT; assert(repo && workdir); @@ -952,11 +1000,37 @@ int git_repository_set_workdir(git_repository *repo, const char *workdir) if (git_path_prettify_dir(&path, workdir, NULL) < 0) return -1; - git__free(repo->workdir); + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + return 0; - repo->workdir = git_buf_detach(&path); - repo->is_bare = 0; - return 0; + if (update_gitlink) { + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + error = write_gitlink(path.ptr, git_repository_path(repo)); + + /* passthrough error means gitlink is unnecessary */ + if (error == GIT_PASSTHROUGH) + error = git_config_delete(config, "core.worktree"); + else if (!error) + error = git_config_set_string(config, "core.worktree", path.ptr); + + if (!error) + error = git_config_set_bool(config, "core.bare", false); + } + + if (!error) { + char *old_workdir = repo->workdir; + + repo->workdir = git_buf_detach(&path); + repo->is_bare = 0; + + git__free(old_workdir); + } + + return error; } int git_repository_is_bare(git_repository *repo) diff --git a/tests-clar/repo/setters.c b/tests-clar/repo/setters.c index 6242d8541..cd6e389ae 100644 --- a/tests-clar/repo/setters.c +++ b/tests-clar/repo/setters.c @@ -2,6 +2,8 @@ #include "buffer.h" #include "posix.h" #include "util.h" +#include "path.h" +#include "fileops.h" static git_repository *repo; @@ -16,7 +18,7 @@ void test_repo_setters__cleanup(void) { git_repository_free(repo); cl_fixture_cleanup("testrepo.git"); - cl_must_pass(p_rmdir("new_workdir")); + cl_fixture_cleanup("new_workdir"); } void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standard_one(void) @@ -24,7 +26,7 @@ void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standar cl_assert(git_repository_is_bare(repo) == 1); cl_assert(git_repository_workdir(repo) == NULL); - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir")); + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); cl_assert(git_repository_workdir(repo) != NULL); cl_assert(git_repository_is_bare(repo) == 0); @@ -32,9 +34,30 @@ void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standar void test_repo_setters__setting_a_workdir_prettifies_its_path(void) { - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir")); + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "new_workdir/") == 0); +} + +void test_repo_setters__setting_a_workdir_creates_a_gitlink(void) +{ + git_config *cfg; + const char *val; + git_buf content = GIT_BUF_INIT; + + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", true)); + + cl_assert(git_path_isfile("./new_workdir/.git")); + + cl_git_pass(git_futils_readbuffer(&content, "./new_workdir/.git")); + cl_assert(git__prefixcmp(git_buf_cstr(&content), "gitdir: ") == 0); + cl_assert(git__suffixcmp(git_buf_cstr(&content), "testrepo.git/") == 0); + git_buf_free(&content); + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_get_string(&val, cfg, "core.worktree")); + cl_assert(git__suffixcmp(val, "new_workdir/") == 0); + git_config_free(cfg); } void test_repo_setters__setting_a_new_index_on_a_repo_which_has_already_loaded_one_properly_honors_the_refcount(void) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 3670b72a8..fed81e570 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -459,7 +459,7 @@ void test_status_worktree__status_file_without_index_or_workdir(void) cl_git_pass(p_mkdir("wd", 0777)); cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd")); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); cl_git_pass(git_index_open(&index, "empty-index")); cl_assert_equal_i(0, git_index_entrycount(index)); @@ -500,7 +500,7 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) cl_git_pass(p_mkdir("wd", 0777)); cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd")); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); cl_git_pass(git_index_open(&index, "my-index")); fill_index_wth_head_entries(repo, index); From 54e29b9380f1cd0fa596d71c317de907da19b13d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 10 Jul 2012 23:51:32 -0700 Subject: [PATCH 6/6] Fix missing NUL termination of buffer --- src/buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/buffer.c b/src/buffer.c index d16b17160..5d54ee1a5 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -172,6 +172,8 @@ int git_buf_puts_escaped( } } + buf->ptr[buf->size] = '\0'; + return 0; }