Commit 886f183ac3dc43c774700825ba32b7b6ffbfc3c3

Ben Straub 2012-05-07T14:26:40

Rev-parse: "ref^{/regex}" syntax.

diff --git a/src/revparse.c b/src/revparse.c
index e03e333..5474264 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -20,6 +20,7 @@
 #include "git2/refs.h"
 #include "git2/repository.h"
 #include "git2/config.h"
+#include "git2/revwalk.h"
 
 GIT_BEGIN_DECL
 
@@ -294,7 +295,7 @@ static git_otype parse_obj_type(const char *str)
    return GIT_OBJ_BAD;
 }
 
-static int handle_caret_syntax(git_object **out, git_object *obj, const char *movement)
+static int handle_caret_syntax(git_object **out, git_repository *repo, git_object *obj, const char *movement)
 {
    git_commit *commit;
    size_t movementlen = strlen(movement);
@@ -325,8 +326,48 @@ static int handle_caret_syntax(git_object **out, git_object *obj, const char *mo
       
       /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */
       if (movement[1] == '/') {
-         /* TODO */
-         return GIT_ERROR;
+         int retcode = GIT_ERROR;
+         git_revwalk *walk;
+         if (!git_revwalk_new(&walk, repo)) {
+            git_oid oid;
+            regex_t preg;
+            git_buf buf = GIT_BUF_INIT;
+
+            git_revwalk_sorting(walk, GIT_SORT_TIME);
+            git_revwalk_push(walk, git_object_id(obj));
+
+            /* Extract the regex from the movement string */
+            git_buf_put(&buf, movement+2, strlen(movement)-3);
+
+            if (!regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED)) {
+               while(!git_revwalk_next(&oid, walk)) {
+                  git_object *walkobj;
+                  char str[41];
+                  git_oid_fmt(str, &oid);
+                  str[40] = 0;
+
+                  /* Fetch the commit object, and check for matches in the message */
+                  if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) {
+                     if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) {
+                        /* Found it! */
+                        retcode = 0;
+                        *out = walkobj;
+                        if (obj == walkobj) {
+                           /* Avoid leaking an object */
+                           git_object_free(walkobj);
+                        }
+                        break;
+                     }
+                     git_object_free(walkobj);
+                  }
+               }
+               regfree(&preg);
+            }
+
+            git_buf_free(&buf);
+            git_revwalk_free(walk);
+         }
+         return retcode;
       }
 
       /* {...} -> Dereference until we reach an object of a certain type. */
@@ -444,14 +485,14 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
       case REVPARSE_STATE_CARET:
          /* Gather characters until NULL, '~', or '^' */
          if (!*spec_cur) {
-            retcode = handle_caret_syntax(out, cur_obj, git_buf_cstr(&stepbuffer));
+            retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer));
             next_state = REVPARSE_STATE_DONE;
          } else if (*spec_cur == '~') {
-            retcode = handle_caret_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer));
+            retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer));
             git_buf_clear(&stepbuffer);
             next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE;
          } else if (*spec_cur == '^') {
-            retcode = handle_caret_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer));
+            retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer));
             git_buf_clear(&stepbuffer);
             if (retcode < 0) {
                next_state = REVPARSE_STATE_DONE;
diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c
index 944a18b..e967f7a 100644
--- a/tests-clar/refs/revparse.c
+++ b/tests-clar/refs/revparse.c
@@ -122,6 +122,21 @@ void test_refs_revparse__reflog(void)
    test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
    test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
    test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__revwalk(void)
+{
+   cl_git_fail(git_revparse_single(&g_obj, g_repo, "master^{/not found in any commit}"));
+   cl_git_fail(git_revparse_single(&g_obj, g_repo, "master^{/merge}"));
+
+   test_object("master^{/anoth}", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+   test_object("master^{/Merge}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+   test_object("br2^{/Merge}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+   test_object("master^{/fo.rth}", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+}
+
+void test_refs_revparse__date(void)
+{
    /* Not ready yet
    test_object("HEAD@{100 years ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
    test_object("master@{2012-4-30 10:23:20}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");