Merge branch 'nd/oneline-sha1-name-from-specific-ref'
authorJunio C Hamano <gitster@pobox.com>
Tue, 21 Dec 2010 22:30:19 +0000 (14:30 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 21 Dec 2010 22:30:19 +0000 (14:30 -0800)
* nd/oneline-sha1-name-from-specific-ref:
get_sha1: handle special case $commit^{/}
get_sha1: support $commit^{/regex} syntax
get_sha1_oneline: make callers prepare the commit list to traverse
get_sha1_oneline: fix lifespan rule of temp_commit_buffer variable

Documentation/revisions.txt
sha1_name.c
t/t1511-rev-parse-caret.sh [new file with mode: 0755]
index 8b519d74487ce1089462df8556e76d1829fb82d4..9e92734bc1b308c47e5f5726c704a3ddffa5beb9 100644 (file)
@@ -106,6 +106,12 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
   and dereference the tag recursively until a non-tag object is
   found.
 
+* A suffix '{caret}' to a revision parameter followed by a brace
+  pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`):
+  this is the same as `:/fix nasty bug` syntax below except that
+  it returns the youngest matching commit which is reachable from
+  the ref before '{caret}'.
+
 * A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
   a commit whose commit message matches the specified regular expression.
   This name returns the youngest matching commit which is
index 8f49279642bf1aa8ce74fee351772e3fb28a73c4..ceb9cdd860da78bf3fe0a1819846ded0e87fda8c 100644 (file)
@@ -7,6 +7,8 @@
 #include "refs.h"
 #include "remote.h"
 
+static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
+
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
        struct alternate_object_database *alt;
@@ -562,6 +564,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
                expected_type = OBJ_BLOB;
        else if (sp[0] == '}')
                expected_type = OBJ_NONE;
+       else if (sp[0] == '/')
+               expected_type = OBJ_COMMIT;
        else
                return -1;
 
@@ -576,19 +580,37 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
                if (!o || (!o->parsed && !parse_object(o->sha1)))
                        return -1;
                hashcpy(sha1, o->sha1);
+               return 0;
        }
-       else {
+
+       /*
+        * At this point, the syntax look correct, so
+        * if we do not get the needed object, we should
+        * barf.
+        */
+       o = peel_to_type(name, len, o, expected_type);
+       if (!o)
+               return -1;
+
+       hashcpy(sha1, o->sha1);
+       if (sp[0] == '/') {
+               /* "$commit^{/foo}" */
+               char *prefix;
+               int ret;
+               struct commit_list *list = NULL;
+
                /*
-                * At this point, the syntax look correct, so
-                * if we do not get the needed object, we should
-                * barf.
+                * $commit^{/}. Some regex implementation may reject.
+                * We don't need regex anyway. '' pattern always matches.
                 */
-               o = peel_to_type(name, len, o, expected_type);
-               if (o) {
-                       hashcpy(sha1, o->sha1);
+               if (sp[1] == '}')
                        return 0;
-               }
-               return -1;
+
+               prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
+               commit_list_insert((struct commit *)o, &list);
+               ret = get_sha1_oneline(prefix, sha1, list);
+               free(prefix);
+               return ret;
        }
        return 0;
 }
@@ -686,15 +708,14 @@ static int handle_one_ref(const char *path,
        if (object->type != OBJ_COMMIT)
                return 0;
        insert_by_date((struct commit *)object, list);
-       object->flags |= ONELINE_SEEN;
        return 0;
 }
 
-static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
+static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
+                           struct commit_list *list)
 {
-       struct commit_list *list = NULL, *backup = NULL, *l;
-       int retval = -1;
-       char *temp_commit_buffer = NULL;
+       struct commit_list *backup = NULL, *l;
+       int found = 0;
        regex_t regex;
 
        if (prefix[0] == '!') {
@@ -706,41 +727,45 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
        if (regcomp(&regex, prefix, REG_EXTENDED))
                die("Invalid search pattern: %s", prefix);
 
-       for_each_ref(handle_one_ref, &list);
-       for (l = list; l; l = l->next)
+       for (l = list; l; l = l->next) {
+               l->item->object.flags |= ONELINE_SEEN;
                commit_list_insert(l->item, &backup);
+       }
        while (list) {
-               char *p;
+               char *p, *to_free = NULL;
                struct commit *commit;
                enum object_type type;
                unsigned long size;
+               int matches;
 
                commit = pop_most_recent_commit(&list, ONELINE_SEEN);
                if (!parse_object(commit->object.sha1))
                        continue;
-               free(temp_commit_buffer);
                if (commit->buffer)
                        p = commit->buffer;
                else {
                        p = read_sha1_file(commit->object.sha1, &type, &size);
                        if (!p)
                                continue;
-                       temp_commit_buffer = p;
+                       to_free = p;
                }
-               if (!(p = strstr(p, "\n\n")))
-                       continue;
-               if (!regexec(&regex, p + 2, 0, NULL, 0)) {
+
+               p = strstr(p, "\n\n");
+               matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
+               free(to_free);
+
+               if (matches) {
                        hashcpy(sha1, commit->object.sha1);
-                       retval = 0;
+                       found = 1;
                        break;
                }
        }
        regfree(&regex);
-       free(temp_commit_buffer);
        free_commit_list(list);
        for (l = backup; l; l = l->next)
                clear_commit_marks(l->item, ONELINE_SEEN);
-       return retval;
+       free_commit_list(backup);
+       return found ? 0 : -1;
 }
 
 struct grab_nth_branch_switch_cbdata {
@@ -1107,9 +1132,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                struct cache_entry *ce;
                char *new_path = NULL;
                int pos;
-               if (namelen > 2 && name[1] == '/')
-                       /* don't need mode for commit */
-                       return get_sha1_oneline(name + 2, sha1);
+               if (namelen > 2 && name[1] == '/') {
+                       struct commit_list *list = NULL;
+                       for_each_ref(handle_one_ref, &list);
+                       return get_sha1_oneline(name + 2, sha1, list);
+               }
                if (namelen < 3 ||
                    name[2] != ':' ||
                    name[1] < '0' || '3' < name[1])
diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh
new file mode 100755 (executable)
index 0000000..e043cb7
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='tests for ref^{stuff}'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo blob >a-blob &&
+       git tag -a -m blob blob-tag `git hash-object -w a-blob`
+       mkdir a-tree &&
+       echo moreblobs >a-tree/another-blob &&
+       git add . &&
+       TREE_SHA1=`git write-tree` &&
+       git tag -a -m tree tree-tag "$TREE_SHA1" &&
+       git commit -m Initial &&
+       git tag -a -m commit commit-tag &&
+       git branch ref &&
+       git checkout master &&
+       echo modified >>a-blob &&
+       git add -u &&
+       git commit -m Modified
+'
+
+test_expect_success 'ref^{non-existent}' '
+       test_must_fail git rev-parse ref^{non-existent}
+'
+
+test_expect_success 'ref^{}' '
+       git rev-parse ref >expected &&
+       git rev-parse ref^{} >actual &&
+       test_cmp expected actual &&
+       git rev-parse commit-tag^{} >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'ref^{commit}' '
+       git rev-parse ref >expected &&
+       git rev-parse ref^{commit} >actual &&
+       test_cmp expected actual &&
+       git rev-parse commit-tag^{commit} >actual &&
+       test_cmp expected actual &&
+       test_must_fail git rev-parse tree-tag^{commit} &&
+       test_must_fail git rev-parse blob-tag^{commit}
+'
+
+test_expect_success 'ref^{tree}' '
+       echo $TREE_SHA1 >expected &&
+       git rev-parse ref^{tree} >actual &&
+       test_cmp expected actual &&
+       git rev-parse commit-tag^{tree} >actual &&
+       test_cmp expected actual &&
+       git rev-parse tree-tag^{tree} >actual &&
+       test_cmp expected actual &&
+       test_must_fail git rev-parse blob-tag^{tree}
+'
+
+test_expect_success 'ref^{/.}' '
+       git rev-parse master >expected &&
+       git rev-parse master^{/.} >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'ref^{/non-existent}' '
+       test_must_fail git rev-parse master^{/non-existent}
+'
+
+test_expect_success 'ref^{/Initial}' '
+       git rev-parse ref >expected &&
+       git rev-parse master^{/Initial} >actual &&
+       test_cmp expected actual
+'
+
+test_done