Skip to content

Commit

Permalink
object name: introduce '^{/!-<negative pattern>}' notation
Browse files Browse the repository at this point in the history
To name a commit, you can now use the :/!-<negative pattern> regex
style, and consequentially, say

    $ git rev-parse HEAD^{/!-foo}

and it will return the hash of the first commit reachable from HEAD,
whose commit message does not contain "foo". This is the opposite of the
existing <rev>^{/<pattern>} syntax.

The specific use-case this is intended for is to perform an operation,
excluding the most-recent commits containing a particular marker. For
example, if you tend to make "work in progress" commits, with messages
beginning with "WIP", you work, then it could be useful to diff against
"the most recent commit which was not a WIP commit". That sort of thing
now possible, via commands such as:

    $ git diff @^{/!-^WIP}

The leader '/!-', rather than simply '/!', to denote a negative match,
is chosen to leave room for additional modifiers in the future.

Signed-off-by: Will Palmer <wmpalmer@gmail.com>
Signed-off-by: Stephen P. Smith <ischis2@cox.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Will Palmer authored and Junio C Hamano committed Feb 1, 2016
1 parent 06b6b68 commit 0769854
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 11 deletions.
11 changes: 6 additions & 5 deletions Documentation/revisions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,12 @@ existing tag object.
A colon, followed by a slash, followed by a text, names
a commit whose commit message matches the specified regular expression.
This name returns the youngest matching commit which is
reachable from any ref. If the commit message starts with a
'!' you have to repeat that; the special sequence ':/!',
followed by something else than '!', is reserved for now.
The regular expression can match any part of the commit message. To
match messages starting with a string, one can use e.g. ':/^foo'.
reachable from any ref. The regular expression can match any part of the
commit message. To match messages starting with a string, one can use
e.g. ':/^foo'. The special sequence ':/!' is reserved for modifiers to what
is matched. ':/!-foo' performs a negative match, while ':/!!foo' matches a
literal '!' character, followed by 'foo'. Any other sequence beginning with
':/!' is reserved for now.

'<rev>:<path>', e.g. 'HEAD:README', ':README', 'master:./README'::
A suffix ':' followed by a path names the blob or tree
Expand Down
20 changes: 15 additions & 5 deletions sha1_name.c
Original file line number Diff line number Diff line change
Expand Up @@ -824,8 +824,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned l
* through history and returning the first commit whose message starts
* the given regular expression.
*
* For future extension, ':/!' is reserved. If you want to match a message
* beginning with a '!', you have to repeat the exclamation mark.
* For negative-matching, prefix the pattern-part with '!-', like: ':/!-WIP'.
*
* For a literal '!' character at the beginning of a pattern, you have to repeat
* that, like: ':/!!foo'
*
* For future extension, all other sequences beginning with ':/!' are reserved.
*/

/* Remember to update object flag allocation in object.h */
Expand Down Expand Up @@ -854,12 +858,18 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
{
struct commit_list *backup = NULL, *l;
int found = 0;
int negative = 0;
regex_t regex;

if (prefix[0] == '!') {
if (prefix[1] != '!')
die ("Invalid search pattern: %s", prefix);
prefix++;

if (prefix[0] == '-') {
prefix++;
negative = 1;
} else if (prefix[0] != '!') {
die ("Invalid search pattern: %s", prefix);
}
}

if (regcomp(&regex, prefix, REG_EXTENDED))
Expand All @@ -879,7 +889,7 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
continue;
buf = get_commit_buffer(commit, NULL);
p = strstr(buf, "\n\n");
matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
matches = negative ^ (p && !regexec(&regex, p + 2, 0, NULL, 0));
unuse_commit_buffer(commit, buf);

if (matches) {
Expand Down
31 changes: 30 additions & 1 deletion t/t1511-rev-parse-caret.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ test_expect_success 'setup' '
git branch expref &&
echo changed >>a-blob &&
git add -u &&
git commit -m Changed
git commit -m Changed &&
echo changed-again >>a-blob &&
git add -u &&
git commit -m Changed-again
'

test_expect_success 'ref^{non-existent}' '
Expand Down Expand Up @@ -99,4 +102,30 @@ test_expect_success 'ref^{/!!Exp}' '
test_cmp expected actual
'

test_expect_success 'ref^{/!-}' '
test_must_fail git rev-parse master^{/!-}
'

test_expect_success 'ref^{/!-.}' '
test_must_fail git rev-parse master^{/!-.}
'

test_expect_success 'ref^{/!-non-existent}' '
git rev-parse master >expected &&
git rev-parse master^{/!-non-existent} >actual &&
test_cmp expected actual
'

test_expect_success 'ref^{/!-Changed}' '
git rev-parse expref >expected &&
git rev-parse master^{/!-Changed} >actual &&
test_cmp expected actual
'

test_expect_success 'ref^{/!-!Exp}' '
git rev-parse modref >expected &&
git rev-parse expref^{/!-!Exp} >actual &&
test_cmp expected actual
'

test_done

0 comments on commit 0769854

Please sign in to comment.