summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/revisions.txt6
-rw-r--r--sha1_name.c83
-rwxr-xr-xt/t1511-rev-parse-caret.sh73
3 files changed, 134 insertions, 28 deletions
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 8b519d7448..9e92734bc1 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -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
diff --git a/sha1_name.c b/sha1_name.c
index 8f49279642..ceb9cdd860 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -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
index 0000000000..e043cb7c64
--- /dev/null
+++ b/t/t1511-rev-parse-caret.sh
@@ -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