Commit a51bdbcfa17c46578700006eea2ef1f2dea03cf5

Ben Straub 2012-04-30T20:21:45

Implementing rev-parse's ref@{n} and @{-n} syntaxes. Added some reflags to the test repo to support unit tests.

diff --git a/src/revparse.c b/src/revparse.c
index 7a07e0c..61a9abc 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -16,6 +16,8 @@
 #include "git2/refs.h"
 #include "git2/tag.h"
 #include "git2/commit.h"
+#include "git2/reflog.h"
+#include "git2/refs.h"
 
 GIT_BEGIN_DECL
 
@@ -111,20 +113,97 @@ static int revparse_lookup_object(git_object **out, git_repository *repo, const 
 }
 
 
-static int walk_ref_history(git_object **out, const char *refspec, const char *reflogspec)
+static int walk_ref_history(git_object **out, git_repository *repo, const char *refspec, const char *reflogspec)
 {
-   /* TODO */
+   git_reference *ref;
+   git_reflog *reflog = NULL;
+   int n, retcode = GIT_ERROR;
+   size_t i, refloglen;
+   const git_reflog_entry *entry;
+   git_buf buf = GIT_BUF_INIT;
+
+   if (git__prefixcmp(reflogspec, "@{") != 0 ||
+       git__suffixcmp(reflogspec, "}") != 0) {
+      giterr_set(GITERR_INVALID, "Bad reflogspec '%s'", reflogspec);
+      return GIT_ERROR;
+   }
 
-   /* Empty refspec means current branch */
+   /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */
+   if (strlen(refspec) == 0 && !git__prefixcmp(reflogspec, "@{-")) {
+      if (git__strtol32(&n, reflogspec+3, NULL, 0) < 0 ||
+          n < 1) {
+         giterr_set(GITERR_INVALID, "Invalid reflogspec %s", reflogspec);
+         return GIT_ERROR;
+      }
+      git_reference_lookup(&ref, repo, "HEAD");
+      git_reflog_read(&reflog, ref);
+      git_reference_free(ref);
 
-   /* Possible syntaxes for reflogspec:
-    * "8" -> 8th prior value for the ref
-    * "-2" -> nth branch checked out before the current one (refspec must be empty)
-    * "yesterday", "1 month 2 weeks 3 days 4 hours 5 seconds ago", "1979-02-26 18:30:00"
-    *   -> value of ref at given point in time
-    * "upstream" or "u" -> branch the ref is set to build on top of
-    * */
-   return 0;
+      refloglen = git_reflog_entrycount(reflog);
+      for (i=0; i < refloglen; i++) {
+         const char *msg;
+         entry = git_reflog_entry_byindex(reflog, i);
+
+         msg = git_reflog_entry_msg(entry);
+         if (!git__prefixcmp(msg, "checkout: moving")) {
+            n--;
+            if (!n) {
+               char *branchname = strrchr(msg, ' ') + 1;
+               git_buf_printf(&buf, "refs/heads/%s", branchname);
+               retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf));
+               break;
+            }
+         }
+      }
+   } else {
+      if (!strlen(refspec)) {
+         /* Empty refspec means current branch */
+         /* Get the target of HEAD */
+         git_reference_lookup(&ref, repo, "HEAD");
+         git_buf_puts(&buf, git_reference_target(ref));
+         git_reference_free(ref);
+
+         /* Get the reflog for that ref */
+         git_reference_lookup(&ref, repo, git_buf_cstr(&buf));
+         git_reflog_read(&reflog, ref);
+         git_reference_free(ref);
+      } else {
+         if (git__prefixcmp(refspec, "refs/heads/") != 0) {
+            git_buf_printf(&buf, "refs/heads/%s", refspec);
+         } else {
+            git_buf_puts(&buf, refspec);
+         }
+      }
+
+      /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */
+      if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) {
+         /* TODO */
+      }
+
+      /* @{N} -> Nth prior value for the ref (from reflog) */
+      else if (!git__strtol32(&n, reflogspec+2, NULL, 0)) {
+         if (n == 0) {
+            retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf));
+         } else if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) {
+            if (!git_reflog_read(&reflog, ref)) {
+               const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, n);
+               const git_oid *oid = git_reflog_entry_oidold(entry);
+               retcode = git_object_lookup(out, repo, oid, GIT_OBJ_ANY);
+            }
+            git_reference_free(ref);
+         }
+      }
+
+      /* @{Anything else} -> try to parse the expression into a date, and get the value of the ref as it
+         was then. */
+      else {
+         /* TODO */
+      }
+   }
+
+   if (reflog) git_reflog_free(reflog);
+   git_buf_free(&buf);
+   return retcode;
 }
 
 static git_object* dereference_object(git_object *obj)
@@ -322,7 +401,7 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
          } else if (*spec_cur == '@') {
             /* '@' syntax doesn't allow chaining */
             git_buf_puts(&stepbuffer, spec_cur);
-            retcode = walk_ref_history(out, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer));
+            retcode = walk_ref_history(out, repo, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer));
             next_state = REVPARSE_STATE_DONE;
          } else if (*spec_cur == '^') {
             next_state = REVPARSE_STATE_CARET;
@@ -335,10 +414,7 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
 
          if (current_state != next_state) {
             /* Leaving INIT state, find the object specified, in case that state needs it */
-            retcode = revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer));
-            if (retcode < 0) {
-               next_state = REVPARSE_STATE_DONE;
-            }
+            revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer));
          }
          break;
 
diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c
index 08cf406..d8013ab 100644
--- a/tests-clar/refs/revparse.c
+++ b/tests-clar/refs/revparse.c
@@ -133,5 +133,29 @@ void test_refs_revparse__chaining(void)
 
 void test_refs_revparse__reflog(void)
 {
-   /* TODO: how to create a fixture for this? git_reflog_write? */
+   cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-xyz}"));
+   cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-0}"));
+
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "@{-2}"));
+   oid_str_cmp(g_obj, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "@{-1}"));
+   oid_str_cmp(g_obj, "a4a7dce85cf63874e984719f4fdd239f5145052f");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "master@{0}"));
+   oid_str_cmp(g_obj, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "master@{1}"));
+   oid_str_cmp(g_obj, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "@{0}"));
+   oid_str_cmp(g_obj, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "@{1}"));
+   oid_str_cmp(g_obj, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+   /* Not ready yet
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "HEAD@{100 years ago}"));
+   oid_str_cmp(g_obj, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "master@{2012-4-30 10:23:20}"));
+   oid_str_cmp(g_obj, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "master@{upstream}"));
+   oid_str_cmp(g_obj, "???");
+   cl_git_pass(git_revparse_single(&g_obj, g_repo, "master@{u}"));
+   oid_str_cmp(g_obj, "???");
+   */
 }
diff --git a/tests/resources/testrepo.git/logs/HEAD b/tests/resources/testrepo.git/logs/HEAD
new file mode 100644
index 0000000..5bdcb7c
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/HEAD
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700	clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0700	commit: 
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0700	checkout: moving from master to br2
+c47800c7266a2be04c571c04d5a6614691ea99bd a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub <bstraub@github.com> 1335806617 -0700	commit: checking in
+a4a7dce85cf63874e984719f4fdd239f5145052f a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806621 -0700	checkout: moving from br2 to master
diff --git a/tests/resources/testrepo.git/logs/refs/heads/br2 b/tests/resources/testrepo.git/logs/refs/heads/br2
new file mode 100644
index 0000000..4e27f6b
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/heads/br2
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0700	branch: Created from refs/remotes/origin/br2
+a4a7dce85cf63874e984719f4fdd239f5145052f a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub <bstraub@github.com> 1335806617 -0700	commit: checking in
diff --git a/tests/resources/testrepo.git/logs/refs/heads/master b/tests/resources/testrepo.git/logs/refs/heads/master
new file mode 100644
index 0000000..c41e87a
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700	clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0700	commit: checking in
diff --git a/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD b/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD
new file mode 100644
index 0000000..f1aac6d
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700	clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git