Skip to content

Commit 41cd797

Browse files
committed
Merge branch 'nd/oneline-sha1-name-from-specific-ref'
* 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
2 parents 643b6ef + 4322842 commit 41cd797

File tree

3 files changed

+134
-28
lines changed

3 files changed

+134
-28
lines changed

Documentation/revisions.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
106106
and dereference the tag recursively until a non-tag object is
107107
found.
108108

109+
* A suffix '{caret}' to a revision parameter followed by a brace
110+
pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`):
111+
this is the same as `:/fix nasty bug` syntax below except that
112+
it returns the youngest matching commit which is reachable from
113+
the ref before '{caret}'.
114+
109115
* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
110116
a commit whose commit message matches the specified regular expression.
111117
This name returns the youngest matching commit which is

sha1_name.c

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "refs.h"
88
#include "remote.h"
99

10+
static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
11+
1012
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
1113
{
1214
struct alternate_object_database *alt;
@@ -562,6 +564,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
562564
expected_type = OBJ_BLOB;
563565
else if (sp[0] == '}')
564566
expected_type = OBJ_NONE;
567+
else if (sp[0] == '/')
568+
expected_type = OBJ_COMMIT;
565569
else
566570
return -1;
567571

@@ -576,19 +580,37 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
576580
if (!o || (!o->parsed && !parse_object(o->sha1)))
577581
return -1;
578582
hashcpy(sha1, o->sha1);
583+
return 0;
579584
}
580-
else {
585+
586+
/*
587+
* At this point, the syntax look correct, so
588+
* if we do not get the needed object, we should
589+
* barf.
590+
*/
591+
o = peel_to_type(name, len, o, expected_type);
592+
if (!o)
593+
return -1;
594+
595+
hashcpy(sha1, o->sha1);
596+
if (sp[0] == '/') {
597+
/* "$commit^{/foo}" */
598+
char *prefix;
599+
int ret;
600+
struct commit_list *list = NULL;
601+
581602
/*
582-
* At this point, the syntax look correct, so
583-
* if we do not get the needed object, we should
584-
* barf.
603+
* $commit^{/}. Some regex implementation may reject.
604+
* We don't need regex anyway. '' pattern always matches.
585605
*/
586-
o = peel_to_type(name, len, o, expected_type);
587-
if (o) {
588-
hashcpy(sha1, o->sha1);
606+
if (sp[1] == '}')
589607
return 0;
590-
}
591-
return -1;
608+
609+
prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
610+
commit_list_insert((struct commit *)o, &list);
611+
ret = get_sha1_oneline(prefix, sha1, list);
612+
free(prefix);
613+
return ret;
592614
}
593615
return 0;
594616
}
@@ -686,15 +708,14 @@ static int handle_one_ref(const char *path,
686708
if (object->type != OBJ_COMMIT)
687709
return 0;
688710
insert_by_date((struct commit *)object, list);
689-
object->flags |= ONELINE_SEEN;
690711
return 0;
691712
}
692713

693-
static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
714+
static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
715+
struct commit_list *list)
694716
{
695-
struct commit_list *list = NULL, *backup = NULL, *l;
696-
int retval = -1;
697-
char *temp_commit_buffer = NULL;
717+
struct commit_list *backup = NULL, *l;
718+
int found = 0;
698719
regex_t regex;
699720

700721
if (prefix[0] == '!') {
@@ -706,41 +727,45 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
706727
if (regcomp(&regex, prefix, REG_EXTENDED))
707728
die("Invalid search pattern: %s", prefix);
708729

709-
for_each_ref(handle_one_ref, &list);
710-
for (l = list; l; l = l->next)
730+
for (l = list; l; l = l->next) {
731+
l->item->object.flags |= ONELINE_SEEN;
711732
commit_list_insert(l->item, &backup);
733+
}
712734
while (list) {
713-
char *p;
735+
char *p, *to_free = NULL;
714736
struct commit *commit;
715737
enum object_type type;
716738
unsigned long size;
739+
int matches;
717740

718741
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
719742
if (!parse_object(commit->object.sha1))
720743
continue;
721-
free(temp_commit_buffer);
722744
if (commit->buffer)
723745
p = commit->buffer;
724746
else {
725747
p = read_sha1_file(commit->object.sha1, &type, &size);
726748
if (!p)
727749
continue;
728-
temp_commit_buffer = p;
750+
to_free = p;
729751
}
730-
if (!(p = strstr(p, "\n\n")))
731-
continue;
732-
if (!regexec(&regex, p + 2, 0, NULL, 0)) {
752+
753+
p = strstr(p, "\n\n");
754+
matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
755+
free(to_free);
756+
757+
if (matches) {
733758
hashcpy(sha1, commit->object.sha1);
734-
retval = 0;
759+
found = 1;
735760
break;
736761
}
737762
}
738763
regfree(&regex);
739-
free(temp_commit_buffer);
740764
free_commit_list(list);
741765
for (l = backup; l; l = l->next)
742766
clear_commit_marks(l->item, ONELINE_SEEN);
743-
return retval;
767+
free_commit_list(backup);
768+
return found ? 0 : -1;
744769
}
745770

746771
struct grab_nth_branch_switch_cbdata {
@@ -1107,9 +1132,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
11071132
struct cache_entry *ce;
11081133
char *new_path = NULL;
11091134
int pos;
1110-
if (namelen > 2 && name[1] == '/')
1111-
/* don't need mode for commit */
1112-
return get_sha1_oneline(name + 2, sha1);
1135+
if (namelen > 2 && name[1] == '/') {
1136+
struct commit_list *list = NULL;
1137+
for_each_ref(handle_one_ref, &list);
1138+
return get_sha1_oneline(name + 2, sha1, list);
1139+
}
11131140
if (namelen < 3 ||
11141141
name[2] != ':' ||
11151142
name[1] < '0' || '3' < name[1])

t/t1511-rev-parse-caret.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/bin/sh
2+
3+
test_description='tests for ref^{stuff}'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'setup' '
8+
echo blob >a-blob &&
9+
git tag -a -m blob blob-tag `git hash-object -w a-blob`
10+
mkdir a-tree &&
11+
echo moreblobs >a-tree/another-blob &&
12+
git add . &&
13+
TREE_SHA1=`git write-tree` &&
14+
git tag -a -m tree tree-tag "$TREE_SHA1" &&
15+
git commit -m Initial &&
16+
git tag -a -m commit commit-tag &&
17+
git branch ref &&
18+
git checkout master &&
19+
echo modified >>a-blob &&
20+
git add -u &&
21+
git commit -m Modified
22+
'
23+
24+
test_expect_success 'ref^{non-existent}' '
25+
test_must_fail git rev-parse ref^{non-existent}
26+
'
27+
28+
test_expect_success 'ref^{}' '
29+
git rev-parse ref >expected &&
30+
git rev-parse ref^{} >actual &&
31+
test_cmp expected actual &&
32+
git rev-parse commit-tag^{} >actual &&
33+
test_cmp expected actual
34+
'
35+
36+
test_expect_success 'ref^{commit}' '
37+
git rev-parse ref >expected &&
38+
git rev-parse ref^{commit} >actual &&
39+
test_cmp expected actual &&
40+
git rev-parse commit-tag^{commit} >actual &&
41+
test_cmp expected actual &&
42+
test_must_fail git rev-parse tree-tag^{commit} &&
43+
test_must_fail git rev-parse blob-tag^{commit}
44+
'
45+
46+
test_expect_success 'ref^{tree}' '
47+
echo $TREE_SHA1 >expected &&
48+
git rev-parse ref^{tree} >actual &&
49+
test_cmp expected actual &&
50+
git rev-parse commit-tag^{tree} >actual &&
51+
test_cmp expected actual &&
52+
git rev-parse tree-tag^{tree} >actual &&
53+
test_cmp expected actual &&
54+
test_must_fail git rev-parse blob-tag^{tree}
55+
'
56+
57+
test_expect_success 'ref^{/.}' '
58+
git rev-parse master >expected &&
59+
git rev-parse master^{/.} >actual &&
60+
test_cmp expected actual
61+
'
62+
63+
test_expect_success 'ref^{/non-existent}' '
64+
test_must_fail git rev-parse master^{/non-existent}
65+
'
66+
67+
test_expect_success 'ref^{/Initial}' '
68+
git rev-parse ref >expected &&
69+
git rev-parse master^{/Initial} >actual &&
70+
test_cmp expected actual
71+
'
72+
73+
test_done

0 commit comments

Comments
 (0)