diff options
author | Ben Straub <bstraub@github.com> | 2012-04-25 16:24:22 -0700 |
---|---|---|
committer | Ben Straub <bstraub@github.com> | 2012-05-11 11:30:45 -0700 |
commit | ac250c56c7d7bb11691c9dfbcd0dbf580d85e177 (patch) | |
tree | 09684c246208dcf49386561203b6bc2e255ba532 | |
parent | fb49bdf9c7837892154bf7efdb3db6c3ec63e396 (diff) | |
download | libgit2-ac250c56c7d7bb11691c9dfbcd0dbf580d85e177.tar.gz |
First stab at implementation of rev-parse.
This version supports refspecs of these kinds:
- Full & partial SHAs
- Output from "git describe"
- "/refs/heads/master" (full ref names)
- "master" (partial ref names)
- "FETCH_HEAD" (named heads)
-rw-r--r-- | include/git2/revparse.h | 22 | ||||
-rw-r--r-- | src/revparse.c | 175 | ||||
-rw-r--r-- | tests-clar/refs/revparse.c | 74 |
3 files changed, 271 insertions, 0 deletions
diff --git a/include/git2/revparse.h b/include/git2/revparse.h new file mode 100644 index 000000000..3fd69d91d --- /dev/null +++ b/include/git2/revparse.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_revparse_h__ +#define INCLUDE_git_revparse_h__ + +#include "common.h" +#include "types.h" + + +GIT_BEGIN_DECL + +GIT_EXTERN(int) git_revparse_single(git_object **out, git_repository *repo, const char *spec); + +//GIT_EXTERN(int) git_revparse_multi(TODO); + +GIT_END_DECL + +#endif diff --git a/src/revparse.c b/src/revparse.c new file mode 100644 index 000000000..d6a7ebd19 --- /dev/null +++ b/src/revparse.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <assert.h> + +#include "common.h" +#include "buffer.h" + +#include "git2/revparse.h" +#include "git2/object.h" +#include "git2/oid.h" +#include "git2/refs.h" + +GIT_BEGIN_DECL + +typedef enum { + REVPARSE_STATE_INIT, + + /* for parsing "@{...}" */ + REVPARSE_STATE_REF_A, + REVPARSE_STATE_REF_B, + + /* for "^{...}" and ^... */ + REVPARSE_STATE_PARENTS_A, + REVPARSE_STATE_PARENTS_B, + + /* For "~..." */ + REVPARSE_STATE_LIINEAR, + + /* For joining parents and linear, as in "master^2~3^2" */ + REVPARSE_STATE_JOIN, +} revparse_state; + +static int revparse_lookup_fully_qualifed_ref(git_object **out, git_repository *repo, const char*spec) +{ + git_reference *ref; + git_object *obj = NULL; + + if (!git_reference_lookup(&ref, repo, spec)) { + git_reference *resolved_ref; + if (!git_reference_resolve(&resolved_ref, ref)) { + if (!git_object_lookup(&obj, repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY)) { + *out = obj; + } + git_reference_free(resolved_ref); + } + git_reference_free(ref); + } + if (obj) { + return 0; + } + + return GIT_ERROR; +} + +static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec) +{ + size_t speclen = strlen(spec); + git_object *obj = NULL; + git_oid oid; + git_buf refnamebuf = GIT_BUF_INIT; + static const char* formatters[] = { + "refs/%s", + "refs/tags/%s", + "refs/heads/%s", + "refs/remotes/%s", + "refs/remotes/%s/HEAD", + NULL + }; + unsigned int i; + const char *substr; + + /* "git describe" output; snip everything before/including "-g" */ + substr = strstr(spec, "-g"); + if (substr) { + spec = substr + 2; + speclen = strlen(spec); + } + + /* SHA or prefix */ + if (!git_oid_fromstrn(&oid, spec, speclen)) { + if (!git_object_lookup_prefix(&obj, repo, &oid, speclen, GIT_OBJ_ANY)) { + *out = obj; + return 0; + } + } + + /* Fully-named ref */ + if (!revparse_lookup_fully_qualifed_ref(&obj, repo, spec)) { + *out = obj; + return 0; + } + + /* Partially-named ref; match in this order: */ + for (i=0; formatters[i]; i++) { + git_buf_clear(&refnamebuf); + if (git_buf_printf(&refnamebuf, formatters[i], spec) < 0) { + return GIT_ERROR; + } + + if (!revparse_lookup_fully_qualifed_ref(&obj, repo, git_buf_cstr(&refnamebuf))) { + git_buf_free(&refnamebuf); + *out = obj; + return 0; + } + } + git_buf_free(&refnamebuf); + + giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec); + return GIT_ERROR; +} + + +static void set_invalid_syntax_err(const char *spec) +{ + giterr_set(GITERR_INVALID, "Refspec '%s' is not valid.", spec); +} + + +int git_revparse_single(git_object **out, git_repository *repo, const char *spec) +{ + revparse_state current_state = REVPARSE_STATE_INIT; + revparse_state next_state = REVPARSE_STATE_INIT; + const char *spec_cur = spec; + git_object *obj = NULL; + git_buf specbuffer = GIT_BUF_INIT; + git_buf stepbuffer = GIT_BUF_INIT; + + assert(out && repo && spec); + + while (1) { + switch (current_state) { + case REVPARSE_STATE_INIT: + if (!*spec_cur) { + /* No operators, just a name. Find it and return. */ + return revparse_lookup_object(out, repo, spec); + } else if (*spec_cur == '@') { + next_state = REVPARSE_STATE_REF_A; + } + spec_cur++; + + if (current_state != next_state) { + /* Leaving INIT state, find the object specified and carry on */ + assert(!git_buf_set(&specbuffer, spec, spec_cur - spec)); + assert(!revparse_lookup_object(&obj, repo, git_buf_cstr(&specbuffer))); + } + break; + + case REVPARSE_STATE_REF_A: + /* Found '@', look for '{', fail otherwise */ + if (*spec_cur != '{') { + set_invalid_syntax_err(spec); + return GIT_ERROR; + } + spec_cur++; + next_state = REVPARSE_STATE_REF_B; + break; + + case REVPARSE_STATE_REF_B: + /* Found "@{", gather things until a '}' */ + break; + } + + current_state = next_state; + } + + return 0; +} + + +GIT_END_DECL diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c new file mode 100644 index 000000000..05c12b074 --- /dev/null +++ b/tests-clar/refs/revparse.c @@ -0,0 +1,74 @@ +#include "clar_libgit2.h" + +#include "git2/revparse.h" + +static git_repository *g_repo; +static git_object *g_obj; + + + +// Hepers +static void oid_str_cmp(const git_oid *oid, const char *str) +{ + git_oid oid2; + cl_git_pass(git_oid_fromstr(&oid2, str)); + cl_assert(0 == git_oid_cmp(oid, &oid2)); +} + + +void test_refs_revparse__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_revparse__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_obj = NULL; +} + + +void test_refs_revparse__shas(void) +{ + // Full SHA should return a valid object + cl_git_pass(git_revparse_single(&g_obj, g_repo, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + oid_str_cmp(git_object_id(g_obj), "c47800c7266a2be04c571c04d5a6614691ea99bd"); + cl_git_pass(git_revparse_single(&g_obj, g_repo, "c47800c")); + oid_str_cmp(git_object_id(g_obj), "c47800c7266a2be04c571c04d5a6614691ea99bd"); +} + +void test_refs_revparse__head(void) +{ + // Named head should return a valid object + cl_git_pass(git_revparse_single(&g_obj, g_repo, "HEAD")); + oid_str_cmp(git_object_id(g_obj), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); +} + +void test_refs_revparse__full_refs(void) +{ + // Fully-qualified refs should return valid objects + cl_git_pass(git_revparse_single(&g_obj, g_repo, "refs/heads/master")); + oid_str_cmp(git_object_id(g_obj), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_revparse_single(&g_obj, g_repo, "refs/heads/test")); + oid_str_cmp(git_object_id(g_obj), "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_revparse_single(&g_obj, g_repo, "refs/tags/test")); + oid_str_cmp(git_object_id(g_obj), "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); +} + +void test_refs_revparse__partial_refs(void) +{ + // Partially-qualified refs should return valid objects + cl_git_pass(git_revparse_single(&g_obj, g_repo, "point_to_blob")); + oid_str_cmp(git_object_id(g_obj), "1385f264afb75a56a5bec74243be9b367ba4ca08"); + cl_git_pass(git_revparse_single(&g_obj, g_repo, "packed-test")); + oid_str_cmp(git_object_id(g_obj), "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + cl_git_pass(git_revparse_single(&g_obj, g_repo, "br2")); + oid_str_cmp(git_object_id(g_obj), "a4a7dce85cf63874e984719f4fdd239f5145052f"); +} + +void test_refs_revparse__describe_output(void) +{ + cl_git_pass(git_revparse_single(&g_obj, g_repo, "blah-7-gc47800c")); + oid_str_cmp(git_object_id(g_obj), "c47800c7266a2be04c571c04d5a6614691ea99bd"); +} + |