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 <wmpalmer@gmail.com> Signed-off-by: Stephen P. Smith <ischis2@cox.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Will Palmer committed Jan 30, 2016 at 17:06 UTC 0769854f3db2c09c8b5993ea023ea07ddc1eb6eb
3 files changed +51 -11
Documentation/revisions.txt
+6 -5
@@ -151,11 +151,12 @@ existing tag object.
151 A colon, followed by a slash, followed by a text, names
152 a commit whose commit message matches the specified regular expression.
153 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.
160
161 '<rev>:<path>', e.g. 'HEAD:README', ':README', 'master:./README'::
162 A suffix ':' followed by a path names the blob or tree
sha1_name.c
+15 -5
@@ -824,8 +824,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned l
824 * through history and returning the first commit whose message starts
825 * the given regular expression.
826 *
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.
833 */
834
835 /* Remember to update object flag allocation in object.h */
@@ -854,12 +858,18 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
858 {
859 struct commit_list *backup = NULL, *l;
860 int found = 0;
861 + int negative = 0;
862 regex_t regex;
863
864 if (prefix[0] == '!') {
860 - if (prefix[1] != '!')
861 - die ("Invalid search pattern: %s", prefix);
865 prefix++;
866 +
867 + if (prefix[0] == '-') {
868 + prefix++;
869 + negative = 1;
870 + } else if (prefix[0] != '!') {
871 + die ("Invalid search pattern: %s", prefix);
872 + }
873 }
874
875 if (regcomp(&regex, prefix, REG_EXTENDED))
@@ -879,7 +889,7 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
889 continue;
890 buf = get_commit_buffer(commit, NULL);
891 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));
893 unuse_commit_buffer(commit, buf);
894
895 if (matches) {
t/t1511-rev-parse-caret.sh
+30 -1
@@ -26,7 +26,10 @@ test_expect_success 'setup' '
26 git branch expref &&
27 echo changed >>a-blob &&
28 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
33 '
34
35 test_expect_success 'ref^{non-existent}' '
@@ -99,4 +102,30 @@ test_expect_success 'ref^{/!!Exp}' '
102 test_cmp expected actual
103 '
104
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 +
131 test_done