Skip to content

Commit 0769854

Browse files
wpalmergitster
authored andcommitted
object name: introduce '^{/!-<negative pattern>}' notation
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 <[email protected]> Signed-off-by: Stephen P. Smith <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 06b6b68 commit 0769854

File tree

3 files changed

+51
-11
lines changed

3 files changed

+51
-11
lines changed

Documentation/revisions.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,12 @@ existing tag object.
151151
A colon, followed by a slash, followed by a text, names
152152
a commit whose commit message matches the specified regular expression.
153153
This name returns the youngest matching commit which is
154-
reachable from any ref. If the commit message starts with a
155-
'!' you have to repeat that; the special sequence ':/!',
156-
followed by something else than '!', is reserved for now.
157-
The regular expression can match any part of the commit message. To
158-
match messages starting with a string, one can use e.g. ':/^foo'.
154+
reachable from any ref. The regular expression can match any part of the
155+
commit message. To match messages starting with a string, one can use
156+
e.g. ':/^foo'. The special sequence ':/!' is reserved for modifiers to what
157+
is matched. ':/!-foo' performs a negative match, while ':/!!foo' matches a
158+
literal '!' character, followed by 'foo'. Any other sequence beginning with
159+
':/!' is reserved for now.
159160

160161
'<rev>:<path>', e.g. 'HEAD:README', ':README', 'master:./README'::
161162
A suffix ':' followed by a path names the blob or tree

sha1_name.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -824,8 +824,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned l
824824
* through history and returning the first commit whose message starts
825825
* the given regular expression.
826826
*
827-
* For future extension, ':/!' is reserved. If you want to match a message
828-
* beginning with a '!', you have to repeat the exclamation mark.
827+
* For negative-matching, prefix the pattern-part with '!-', like: ':/!-WIP'.
828+
*
829+
* For a literal '!' character at the beginning of a pattern, you have to repeat
830+
* that, like: ':/!!foo'
831+
*
832+
* For future extension, all other sequences beginning with ':/!' are reserved.
829833
*/
830834

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

859864
if (prefix[0] == '!') {
860-
if (prefix[1] != '!')
861-
die ("Invalid search pattern: %s", prefix);
862865
prefix++;
866+
867+
if (prefix[0] == '-') {
868+
prefix++;
869+
negative = 1;
870+
} else if (prefix[0] != '!') {
871+
die ("Invalid search pattern: %s", prefix);
872+
}
863873
}
864874

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

885895
if (matches) {

t/t1511-rev-parse-caret.sh

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ test_expect_success 'setup' '
2626
git branch expref &&
2727
echo changed >>a-blob &&
2828
git add -u &&
29-
git commit -m Changed
29+
git commit -m Changed &&
30+
echo changed-again >>a-blob &&
31+
git add -u &&
32+
git commit -m Changed-again
3033
'
3134

3235
test_expect_success 'ref^{non-existent}' '
@@ -99,4 +102,30 @@ test_expect_success 'ref^{/!!Exp}' '
99102
test_cmp expected actual
100103
'
101104

105+
test_expect_success 'ref^{/!-}' '
106+
test_must_fail git rev-parse master^{/!-}
107+
'
108+
109+
test_expect_success 'ref^{/!-.}' '
110+
test_must_fail git rev-parse master^{/!-.}
111+
'
112+
113+
test_expect_success 'ref^{/!-non-existent}' '
114+
git rev-parse master >expected &&
115+
git rev-parse master^{/!-non-existent} >actual &&
116+
test_cmp expected actual
117+
'
118+
119+
test_expect_success 'ref^{/!-Changed}' '
120+
git rev-parse expref >expected &&
121+
git rev-parse master^{/!-Changed} >actual &&
122+
test_cmp expected actual
123+
'
124+
125+
test_expect_success 'ref^{/!-!Exp}' '
126+
git rev-parse modref >expected &&
127+
git rev-parse expref^{/!-!Exp} >actual &&
128+
test_cmp expected actual
129+
'
130+
102131
test_done

0 commit comments

Comments
 (0)