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
 
  * 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 */
 {
        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(®ex, prefix, REG_EXTENDED))
                        continue;
                buf = get_commit_buffer(commit, NULL);
                p = strstr(buf, "\n\n");
-               matches = p && !regexec(®ex, p + 2, 0, NULL, 0);
+               matches = negative ^ (p && !regexec(®ex, p + 2, 0, NULL, 0));
                unuse_commit_buffer(commit, buf);
 
                if (matches) {
 
        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}' '
        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