From 28042dbcd60f3190d25cfeae0bf81d58a16f4771 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 12 Dec 2010 22:19:00 -0800 Subject: [PATCH 1/4] get_sha1_oneline: fix lifespan rule of temp_commit_buffer variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is trying to free only what we ourselves read (as opposed to what we borrowed from commit->buffer) but do so lazily only to work around the fact that the code has many irregular exit points, and doing it right makes it necessary to call free() from many different places in the loop. Rewrite the structure of the code inside the loop so that the variable has to live within a single iteration, ever. This should make the logic easier to follow as well. Also we didn't free a temporary commit list we kept to hold the original set of commits. Free it. Noticed-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- sha1_name.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 2c3a5fb363..2cc7a42390 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -693,8 +693,7 @@ static int handle_one_ref(const char *path, static int get_sha1_oneline(const char *prefix, unsigned char *sha1) { struct commit_list *list = NULL, *backup = NULL, *l; - int retval = -1; - char *temp_commit_buffer = NULL; + int found = 0; regex_t regex; if (prefix[0] == '!') { @@ -710,37 +709,40 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) for (l = list; l; l = l->next) 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(®ex, p + 2, 0, NULL, 0)) { + + p = strstr(p, "\n\n"); + matches = p && !regexec(®ex, p + 2, 0, NULL, 0); + free(to_free); + + if (matches) { hashcpy(sha1, commit->object.sha1); - retval = 0; + found = 1; break; } } regfree(®ex); - 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 { From 84baa31bcb6925b06dd1b9aaa1ed6f7a725e64cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 13 Dec 2010 10:01:14 +0700 Subject: [PATCH 2/4] get_sha1_oneline: make callers prepare the commit list to traverse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gives callers more control, i.e. which ref will be searched from. They must prepare the list ordered by committer date. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- sha1_name.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 2cc7a42390..aefae1f524 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -686,13 +686,13 @@ 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; + struct commit_list *backup = NULL, *l; int found = 0; regex_t regex; @@ -705,9 +705,10 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) if (regcomp(®ex, 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, *to_free = NULL; struct commit *commit; @@ -1090,9 +1091,11 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, int stage = 0; struct cache_entry *ce; 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]) From 32574b68c57f3b34e58216a4c9c6d570d11ec47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 13 Dec 2010 10:01:15 +0700 Subject: [PATCH 3/4] get_sha1: support $commit^{/regex} syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This works like ":/regex" syntax that finds a recently created commit starting from all refs, but limits the discovery to those reachable from the named commit. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/revisions.txt | 6 +++ sha1_name.c | 37 +++++++++++++------ t/t1511-rev-parse-caret.sh | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 11 deletions(-) create mode 100755 t/t1511-rev-parse-caret.sh diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 3d4b79c480..174fa8e90d 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 aefae1f524..1ba4bc3b68 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,30 @@ 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) { - hashcpy(sha1, o->sha1); - return 0; - } + + /* + * 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; + + 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; } 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 From 4322842acfaace812308f8aec0fe39a358f3c6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Wed, 15 Dec 2010 16:02:54 +0700 Subject: [PATCH 4/4] get_sha1: handle special case $commit^{/} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty regex pattern should always match. But the exact behavior of regexec() may vary. Because it always matches anyway, we can just return 'matched' without calling regex machinery. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- sha1_name.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sha1_name.c b/sha1_name.c index 1ba4bc3b68..c5c59ced7f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -599,6 +599,13 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) int ret; struct commit_list *list = NULL; + /* + * $commit^{/}. Some regex implementation may reject. + * We don't need regex anyway. '' pattern always matches. + */ + if (sp[1] == '}') + return 0; + prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1)); commit_list_insert((struct commit *)o, &list); ret = get_sha1_oneline(prefix, sha1, list);