diff options
author | Edward Thomson <ethomson@microsoft.com> | 2012-12-20 03:12:49 +0400 |
---|---|---|
committer | Edward Thomson <ethomson@microsoft.com> | 2012-12-20 03:12:49 +0400 |
commit | 92c4d25820a55186f3834891673fb9d72715d978 (patch) | |
tree | 9b09ab2eb1d111d5662a1f0f0b7fa13df07587d0 | |
parent | 5c3c86b06e84ac70735b2629209de2dcc0e09034 (diff) | |
parent | 7fcec8342890031a0d22f0d013833badd81091e8 (diff) |
Merge pull request #1138 from ethomson/fetchhead
fetchhead reading/iterating
-rw-r--r-- | include/git2/errors.h | 1 | ||||
-rw-r--r-- | include/git2/repository.h | 18 | ||||
-rw-r--r-- | src/fetchhead.c | 186 | ||||
-rw-r--r-- | src/fetchhead.h | 15 | ||||
-rw-r--r-- | src/util.c | 18 | ||||
-rw-r--r-- | src/util.h | 1 | ||||
-rw-r--r-- | tests-clar/fetchhead/nonetwork.c | 280 |
7 files changed, 472 insertions, 47 deletions
diff --git a/include/git2/errors.h b/include/git2/errors.h index 63b6bc8ee..8ca900969 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -63,6 +63,7 @@ typedef enum { GITERR_THREAD, GITERR_STASH, GITERR_CHECKOUT, + GITERR_FETCHHEAD, } git_error_t; /** diff --git a/include/git2/repository.h b/include/git2/repository.h index 216f59b51..96b47f440 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -490,6 +490,24 @@ GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *re */ GIT_EXTERN(int) git_repository_message_remove(git_repository *repo); +typedef int (*git_repository_fetchhead_foreach_cb)(const char *ref_name, + const char *remote_url, + const git_oid *oid, + unsigned int is_merge, + void *payload); + +/** + * Call callback 'callback' for each entry in the given FETCH_HEAD file. + * + * @param repo A repository object + * @param callback Callback function + * @param payload Pointer to callback data (optional) + * @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error + */ +GIT_EXTERN(int) git_repository_fetchhead_foreach(git_repository *repo, + git_repository_fetchhead_foreach_cb callback, + void *payload); + /** * Calculate hash of file using repository filtering rules. * diff --git a/src/fetchhead.c b/src/fetchhead.c index ed47bab48..14878feb5 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -10,6 +10,7 @@ #include "fetchhead.h" #include "common.h" +#include "buffer.h" #include "fileops.h" #include "filebuf.h" #include "refs.h" @@ -26,19 +27,28 @@ int git_fetchhead_ref_cmp(const void *a, const void *b) if (two->is_merge && !one->is_merge) return 1; - return strcmp(one->ref_name, two->ref_name); + if (one->ref_name && two->ref_name) + return strcmp(one->ref_name, two->ref_name); + else if (one->ref_name) + return -1; + else if (two->ref_name) + return 1; + + return 0; } int git_fetchhead_ref_create( - git_fetchhead_ref **fetchhead_ref_out, + git_fetchhead_ref **out, git_oid *oid, - int is_merge, + unsigned int is_merge, const char *ref_name, const char *remote_url) { - git_fetchhead_ref *fetchhead_ref = NULL; + git_fetchhead_ref *fetchhead_ref; + + assert(out && oid); - assert(fetchhead_ref_out && oid && ref_name && remote_url); + *out = NULL; fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref)); GITERR_CHECK_ALLOC(fetchhead_ref); @@ -47,10 +57,14 @@ int git_fetchhead_ref_create( git_oid_cpy(&fetchhead_ref->oid, oid); fetchhead_ref->is_merge = is_merge; - fetchhead_ref->ref_name = git__strdup(ref_name); - fetchhead_ref->remote_url = git__strdup(remote_url); - *fetchhead_ref_out = fetchhead_ref; + if (ref_name) + fetchhead_ref->ref_name = git__strdup(ref_name); + + if (remote_url) + fetchhead_ref->remote_url = git__strdup(remote_url); + + *out = fetchhead_ref; return 0; } @@ -114,6 +128,162 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); } +static int fetchhead_ref_parse( + git_oid *oid, + unsigned int *is_merge, + git_buf *ref_name, + const char **remote_url, + char *line, + size_t line_num) +{ + char *oid_str, *is_merge_str, *desc, *name = NULL; + const char *type = NULL; + int error = 0; + + *remote_url = NULL; + + if (!*line) { + giterr_set(GITERR_FETCHHEAD, + "Empty line in FETCH_HEAD line %d", line_num); + return -1; + } + + /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */ + if ((oid_str = git__strsep(&line, "\t")) == NULL) { + oid_str = line; + line += strlen(line); + + *is_merge = 1; + } + + if (strlen(oid_str) != GIT_OID_HEXSZ) { + giterr_set(GITERR_FETCHHEAD, + "Invalid object ID in FETCH_HEAD line %d", line_num); + return -1; + } + + if (git_oid_fromstr(oid, oid_str) < 0) { + const git_error *oid_err = giterr_last(); + const char *err_msg = oid_err ? oid_err->message : "Invalid object ID"; + + giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %d", + err_msg, line_num); + return -1; + } + + /* Parse new data from newer git clients */ + if (*line) { + if ((is_merge_str = git__strsep(&line, "\t")) == NULL) { + giterr_set(GITERR_FETCHHEAD, + "Invalid description data in FETCH_HEAD line %d", line_num); + return -1; + } + + if (*is_merge_str == '\0') + *is_merge = 1; + else if (strcmp(is_merge_str, "not-for-merge") == 0) + *is_merge = 0; + else { + giterr_set(GITERR_FETCHHEAD, + "Invalid for-merge entry in FETCH_HEAD line %d", line_num); + return -1; + } + + if ((desc = line) == NULL) { + giterr_set(GITERR_FETCHHEAD, + "Invalid description in FETCH_HEAD line %d", line_num); + return -1; + } + + if (git__prefixcmp(desc, "branch '") == 0) { + type = GIT_REFS_HEADS_DIR; + name = desc + 8; + } else if (git__prefixcmp(desc, "tag '") == 0) { + type = GIT_REFS_TAGS_DIR; + name = desc + 5; + } else if (git__prefixcmp(desc, "'") == 0) + name = desc + 1; + + if (name) { + if ((desc = strchr(name, '\'')) == NULL || + git__prefixcmp(desc, "' of ") != 0) { + giterr_set(GITERR_FETCHHEAD, + "Invalid description in FETCH_HEAD line %d", line_num); + return -1; + } + + *desc = '\0'; + desc += 5; + } + + *remote_url = desc; + } + + git_buf_clear(ref_name); + + if (type) + git_buf_join(ref_name, '/', type, name); + else if(name) + git_buf_puts(ref_name, name); + + return error; +} + +int git_repository_fetchhead_foreach(git_repository *repo, + git_repository_fetchhead_foreach_cb cb, + void *payload) +{ + git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT; + const char *ref_name; + git_oid oid; + const char *remote_url; + unsigned int is_merge; + char *buffer, *line; + size_t line_num = 0; + int error = 0; + + assert(repo && cb); + + if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0) + return -1; + + if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0) + goto done; + + buffer = file.ptr; + + while ((line = git__strsep(&buffer, "\n")) != NULL) { + ++line_num; + + if ((error = fetchhead_ref_parse(&oid, &is_merge, &name, &remote_url, + line, line_num)) < 0) + goto done; + + if (git_buf_len(&name) > 0) + ref_name = git_buf_cstr(&name); + else + ref_name = NULL; + + if ((cb(ref_name, remote_url, &oid, is_merge, payload)) != 0) { + error = GIT_EUSER; + goto done; + } + } + + if (*buffer) { + giterr_set(GITERR_FETCHHEAD, "No EOL at line %d", line_num+1); + error = -1; + goto done; + } + +done: + git_buf_free(&file); + git_buf_free(&path); + git_buf_free(&name); + + return error; +} + void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref) { if (fetchhead_ref == NULL) diff --git a/src/fetchhead.h b/src/fetchhead.h index ec7c1985b..e694f8aa8 100644 --- a/src/fetchhead.h +++ b/src/fetchhead.h @@ -9,18 +9,25 @@ #include "vector.h" -typedef struct git_fetchhead_ref { +struct git_fetchhead_ref { git_oid oid; unsigned int is_merge; char *ref_name; char *remote_url; -} git_fetchhead_ref; +}; -int git_fetchhead_ref_create(git_fetchhead_ref **fetchhead_ref_out, git_oid *oid, int is_merge, const char *ref_name, const char *remote_url); +typedef struct git_fetchhead_ref git_fetchhead_ref; + +int git_fetchhead_ref_create( + git_fetchhead_ref **fetchhead_ref_out, + git_oid *oid, + unsigned int is_merge, + const char *ref_name, + const char *remote_url); int git_fetchhead_ref_cmp(const void *a, const void *b); -int git_fetchhead_write(git_repository *repository, git_vector *fetchhead_refs); +int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs); void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref); diff --git a/src/util.c b/src/util.c index 6df32b1c3..059e55dd7 100644 --- a/src/util.c +++ b/src/util.c @@ -276,6 +276,24 @@ char *git__strtok(char **end, const char *sep) return NULL; } +/* Similar to strtok, but does not collapse repeated tokens. */ +char *git__strsep(char **end, const char *sep) +{ + char *start = *end, *ptr = *end; + + while (*ptr && !strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + *end = ptr + 1; + *ptr = '\0'; + + return start; + } + + return NULL; +} + void git__hexdump(const char *buffer, size_t len) { static const size_t LINE_WIDTH = 16; diff --git a/src/util.h b/src/util.h index cb1c4fdc2..aa193580b 100644 --- a/src/util.h +++ b/src/util.h @@ -107,6 +107,7 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) #endif extern char *git__strtok(char **end, const char *sep); +extern char *git__strsep(char **end, const char *sep); extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str); diff --git a/tests-clar/fetchhead/nonetwork.c b/tests-clar/fetchhead/nonetwork.c index c86ae7402..6ffc6ea2f 100644 --- a/tests-clar/fetchhead/nonetwork.c +++ b/tests-clar/fetchhead/nonetwork.c @@ -2,6 +2,7 @@ #include "repository.h" #include "fetchhead.h" + #include "fetchhead_data.h" #define DO_LOCAL_TEST 0 @@ -23,77 +24,286 @@ static void cleanup_repository(void *path) cl_fixture_cleanup((const char *)path); } -void test_fetchhead_nonetwork__write(void) +static void populate_fetchhead(git_vector *out, git_repository *repo) { - git_vector fetchhead_vector; - git_fetchhead_ref *fetchhead[6]; - git_oid oid[6]; - git_buf fetchhead_buf = GIT_BUF_INIT; - size_t i; - int equals = 0; - - git_vector_init(&fetchhead_vector, 6, NULL); - - cl_set_cleanup(&cleanup_repository, "./test1"); + git_fetchhead_ref *fetchhead_ref; + git_oid oid; - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_pass(git_oid_fromstr(&oid[0], + cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead[0], &oid[0], 1, + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 1, "refs/heads/master", "git://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[0])); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); - cl_git_pass(git_oid_fromstr(&oid[1], + cl_git_pass(git_oid_fromstr(&oid, "0966a434eb1a025db6b71485ab63a3bfbea520b6")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead[1], &oid[1], 0, + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, "refs/heads/first-merge", "git://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[1])); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); - cl_git_pass(git_oid_fromstr(&oid[2], + cl_git_pass(git_oid_fromstr(&oid, "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead[2], &oid[2], 0, + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, "refs/heads/no-parent", "git://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[2])); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); - cl_git_pass(git_oid_fromstr(&oid[3], + cl_git_pass(git_oid_fromstr(&oid, "d96c4e80345534eccee5ac7b07fc7603b56124cb")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead[3], &oid[3], 0, + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, "refs/tags/annotated_tag", "git://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[3])); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); - cl_git_pass(git_oid_fromstr(&oid[4], + cl_git_pass(git_oid_fromstr(&oid, "55a1a760df4b86a02094a904dfa511deb5655905")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead[4], &oid[4], 0, + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, "refs/tags/blob", "git://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[4])); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); - cl_git_pass(git_oid_fromstr(&oid[5], + cl_git_pass(git_oid_fromstr(&oid, "8f50ba15d49353813cc6e20298002c0d17b0a9ee")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead[5], &oid[5], 0, + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, "refs/tags/commit_tree", "git://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[5])); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); - git_fetchhead_write(g_repo, &fetchhead_vector); + cl_git_pass(git_fetchhead_write(repo, out)); +} + +void test_fetchhead_nonetwork__write(void) +{ + git_vector fetchhead_vector = GIT_VECTOR_INIT; + git_fetchhead_ref *fetchhead_ref; + git_buf fetchhead_buf = GIT_BUF_INIT; + int equals = 0; + size_t i; + + git_vector_init(&fetchhead_vector, 6, NULL); + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + populate_fetchhead(&fetchhead_vector, g_repo); cl_git_pass(git_futils_readbuffer(&fetchhead_buf, "./test1/.git/FETCH_HEAD")); equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA) == 0); - for (i=0; i < 6; i++) - git_fetchhead_ref_free(fetchhead[i]); - git_buf_free(&fetchhead_buf); + git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) { + git_fetchhead_ref_free(fetchhead_ref); + } + git_vector_free(&fetchhead_vector); cl_assert(equals); } +typedef struct { + git_vector *fetchhead_vector; + size_t idx; +} fetchhead_ref_cb_data; + +static int fetchhead_ref_cb(const char *name, const char *url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + fetchhead_ref_cb_data *cb_data = payload; + git_fetchhead_ref *expected; + + cl_assert(payload); + + expected = git_vector_get(cb_data->fetchhead_vector, cb_data->idx); + + cl_assert(git_oid_cmp(&expected->oid, oid) == 0); + cl_assert(expected->is_merge == is_merge); + + if (expected->ref_name) + cl_assert(strcmp(expected->ref_name, name) == 0); + else + cl_assert(name == NULL); + + if (expected->remote_url) + cl_assert(strcmp(expected->remote_url, url) == 0); + else + cl_assert(url == NULL); + + cb_data->idx++; + + return 0; +} + +void test_fetchhead_nonetwork__read(void) +{ + git_vector fetchhead_vector = GIT_VECTOR_INIT; + git_fetchhead_ref *fetchhead_ref; + fetchhead_ref_cb_data cb_data; + size_t i; + + memset(&cb_data, 0x0, sizeof(fetchhead_ref_cb_data)); + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + populate_fetchhead(&fetchhead_vector, g_repo); + + cb_data.fetchhead_vector = &fetchhead_vector; + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, fetchhead_ref_cb, &cb_data)); + + git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) { + git_fetchhead_ref_free(fetchhead_ref); + } + + git_vector_free(&fetchhead_vector); +} + +static int read_old_style_cb(const char *name, const char *url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + git_oid expected; + + GIT_UNUSED(payload); + + git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); + + cl_assert(name == NULL); + cl_assert(url == NULL); + cl_assert(git_oid_cmp(&expected, oid) == 0); + cl_assert(is_merge == 1); + + return 0; +} + +void test_fetchhead_nonetwork__read_old_style(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\n"); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_old_style_cb, NULL)); +} + +static int read_type_missing(const char *ref_name, const char *remote_url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + git_oid expected; + + GIT_UNUSED(payload); + + git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); + + cl_assert(strcmp(ref_name, "name") == 0); + cl_assert(strcmp(remote_url, "remote_url") == 0); + cl_assert(git_oid_cmp(&expected, oid) == 0); + cl_assert(is_merge == 0); + + return 0; +} + +void test_fetchhead_nonetwork__type_missing(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\t'name' of remote_url\n"); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_type_missing, NULL)); +} + +static int read_name_missing(const char *ref_name, const char *remote_url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + git_oid expected; + + GIT_UNUSED(payload); + + git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); + + cl_assert(ref_name == NULL); + cl_assert(strcmp(remote_url, "remote_url") == 0); + cl_assert(git_oid_cmp(&expected, oid) == 0); + cl_assert(is_merge == 0); + + return 0; +} + +void test_fetchhead_nonetwork__name_missing(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tremote_url\n"); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_name_missing, NULL)); +} + +static int read_noop(const char *ref_name, const char *remote_url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + GIT_UNUSED(ref_name); + GIT_UNUSED(remote_url); + GIT_UNUSED(oid); + GIT_UNUSED(is_merge); + GIT_UNUSED(payload); + + return 0; +} + +void test_fetchhead_nonetwork__nonexistent(void) +{ + int error; + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_fail((error = git_repository_fetchhead_foreach(g_repo, read_noop, NULL))); + cl_assert(error == GIT_ENOTFOUND); +} + +void test_fetchhead_nonetwork__invalid_unterminated_last_line(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "unterminated"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); +} + +void test_fetchhead_nonetwork__invalid_oid(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "shortoid\n"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); +} + +void test_fetchhead_nonetwork__invalid_for_merge(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tinvalid-merge\t\n"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); + + cl_assert(git__prefixcmp(giterr_last()->message, "Invalid for-merge") == 0); +} + +void test_fetchhead_nonetwork__invalid_description(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\n"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); + + cl_assert(git__prefixcmp(giterr_last()->message, "Invalid description") == 0); +} + |