Commit ac250c56c7d7bb11691c9dfbcd0dbf580d85e177

Ben Straub 2012-04-25T16:24:22

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)

diff --git a/include/git2/revparse.h b/include/git2/revparse.h
new file mode 100644
index 0000000..3fd69d9
--- /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 0000000..d6a7ebd
--- /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 0000000..05c12b0
--- /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");
+}
+