Commit 734efe4b8e8f6cfc820c7f88b3fb4e4221cec0ab

Ben Straub 2012-06-01T14:18:52

Rev-parse: implement ":/foo" syntax.

diff --git a/src/revparse.c b/src/revparse.c
index 60e819c..6d88a1d 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -432,9 +432,6 @@ static int handle_caret_syntax(git_object **out, git_repository *repo, git_objec
             } else {
                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)) {
@@ -580,6 +577,49 @@ static int handle_colon_syntax(git_object **out,
    return git_tree_entry_2object(out, repo, entry);
 }
 
+static int git__revparse_global_grep(git_object **out, git_repository *repo, const char *pattern)
+{
+   git_revwalk *walk;
+   int retcode = GIT_ERROR;
+
+   if (!git_revwalk_new(&walk, repo)) {
+      regex_t preg;
+      int reg_error;
+      git_oid oid;
+
+      git_revwalk_sorting(walk, GIT_SORT_TIME);
+      git_revwalk_push_glob(walk, "refs/heads/*");
+
+      reg_error = regcomp(&preg, pattern, REG_EXTENDED);
+      if (reg_error != 0) {
+         giterr_set_regex(&preg, reg_error);
+      } else {
+         git_object *walkobj = NULL, *resultobj = NULL;
+         while(!git_revwalk_next(&oid, walk)) {
+            /* Fetch the commit object, and check for matches in the message */
+            if (walkobj != resultobj) git_object_free(walkobj);
+            if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) {
+               if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) {
+                  /* Match! */
+                  resultobj = walkobj;
+                  retcode = 0;
+                  break;
+               }
+            }
+         }
+         if (!resultobj) {
+            giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", pattern);
+         } else {
+            *out = resultobj;
+         }
+         regfree(&preg);
+         git_revwalk_free(walk);
+      }
+   }
+
+   return retcode;
+}
+
 int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
 {
    revparse_state current_state = REVPARSE_STATE_INIT,  next_state = REVPARSE_STATE_INIT;
@@ -591,8 +631,10 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec
    assert(out && repo && spec);
 
    if (spec[0] == ':') {
-      /* Either a global grep (":/foo") or a merge-stage path lookup (":2:Makefile").
-         Neither of these are handled just yet. */
+      if (spec[1] == '/') {
+         return git__revparse_global_grep(out, repo, spec+2);
+      }
+      /* TODO: support merge-stage path lookup (":2:Makefile"). */
       giterr_set(GITERR_INVALID, "Unimplemented");
       return GIT_ERROR;
    }
diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c
index 9ecdb82..0bd6ad7 100644
--- a/tests-clar/refs/revparse.c
+++ b/tests-clar/refs/revparse.c
@@ -156,11 +156,15 @@ void test_refs_revparse__date(void)
 
 void test_refs_revparse__colon(void)
 {
-   cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/foo"));
+   cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/"));
+   cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/not found in any commit"));
    cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README"));
 
    test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f");
    test_object("subtrees:ab/de/fgh/1.txt", "1f67fc4386b2d171e0d21be1c447e12660561f9b");
    test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6");
    test_object("master:new.txt", "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd");
+   test_object(":/Merge", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+   test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+   test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9");
 }