Submit requirements: Inability to express “non-matching file paths” (negative path predicate)

109 views
Skip to first unread message

David Horton

unread,
Mar 23, 2026, 2:34:47 AMMar 23
to Repo and Gerrit Discussion

Hi all,

I’ve run into a limitation in Gerrit submit requirements when trying to express path-dependent validation rules and wanted to check whether this is expected and/or if there’s interest in improving it.

Use case

We have a single repository with a subdirectory (aviary/) that has its own CI pipeline and verification requirements, separate from the rest of the repo.

We would like to express the following behaviour via submit requirements:

  • Changes affecting only aviary/** → require Aviary checks only

  • Changes affecting only non-aviary/** paths → require non-Aviary checks only

  • Changes affecting both → require both sets of checks

The positive case works fine: applicableIf = file:^aviary/.*

Problem

There does not appear to be a way to express:

“there exists a changed file that is not under aviary/”

The natural regex form would be:

file:^(?!aviary/).*

However, this fails during submit-requirement validation:

no viable alternative at character '?'

This appears to be because the expression is first parsed by Gerrit’s query parser, which rejects the ? before the file:operator sees the pattern.

I also tried:

  • quoting: file:"^(?!aviary/).*"

  • brace grouping: file:{^(?!aviary/).*}

These are accepted syntactically in some contexts, but do not behave correctly for applicability.  I also tried variations of double and higher escaping.

NOTE:

This is semantically different from negating the existing file: predicate.

NOT file:^aviary/.*

means:
      "no changed file is under aviary/"

whereas:

fileNot:^aviary/.*

means:
      "there exists at least one changed file not under aviary/"

These differ for mixed changes (touching both aviary and non-aviary paths), which is exactly the case we need to support.


Current workaround

The only viable workaround seems to be moving this logic into CI orchestration (e.g. an aggregator build that decides which labels to apply), rather than expressing it in submit requirements. This works, but loses the declarative nature of submit requirements and introduces ordering/race concerns.

Proposal

It would be very useful to have a first-class way to express the negative case, for example:

fileNot:^aviary/.*

or

pathNot:^aviary/.*

with semantics:

  • file:^aviary/.* → at least one changed path matches

  • fileNot:^aviary/.* → at least one changed path does not match

This would allow clean expression of mixed-path requirements without relying on regex lookahead or CI-side logic.

Why a new operator?

While it might be possible to extend the query parser to support more complex regex constructs, this would require changes to the parser and could introduce unintended compatibility issues.

A dedicated operator seems:

  • clearer to users

  • easier to document

  • safer from a backward-compatibility perspective

Questions
  • Is there an existing way to express this that I’ve missed?

  • Would a fileNot: / pathNot: predicate be acceptable?

  • If so, I’m happy to work on this.  I’ve attached a draft patch to illustrate the approach — happy to revise or rework this based on feedback.

Thanks!

David


gerrit_submit_requirements_fileNot.patch
gerrit_submit_requirements_fileNot.msg

Vitaliy L.

unread,
Mar 24, 2026, 8:39:47 AMMar 24
to Repo and Gerrit Discussion

There does not appear to be a way to express:

“there exists a changed file that is not under aviary/”

What you are looking is probably is complement operator for path/file search operator.

Can't comment on proposal.

David Horton

unread,
Mar 24, 2026, 9:33:38 PMMar 24
to Repo and Gerrit Discussion
Interesting, thanks for pointing this out.

It looks like this allowed similar semantics to what I'm after, but for general searches for change sets, rather than explicitly within submit requirement config for a project.

From what I can tell lucene doesn't have any usage in the submit requirements config and these are processed only with java.util.regex.
Interesting nonetheless though.

David Horton

unread,
Mar 24, 2026, 11:09:35 PMMar 24
to Repo and Gerrit Discussion

Hi all,

I wanted to follow up with a correction and some additional findings after digging further into the implementation.

Correction

In my original message, I suggested that the file: operator uses Java regex matching. That is only partially true.

The implementation behaves differently depending on whether the input matches the FILE_EDITS_PATTERN:

  • If it matches:

    '<filePattern>',withDiffContaining='<contentPattern>'

    then Gerrit uses FileEditsPredicate, which does rely on java.util.regex.Pattern.

  • Otherwise, it falls back to the standard path handling:

    return super.file(value);

    which ultimately uses RegexPathPredicate → RegexListSearcher → dk.brics.automaton.RegExp.

The dk.brics regex engine does not support lookahead constructs, so patterns such as:  ^(?!aviary/).*  cannot be expressed via the normal file: operator, even if they pass parsing.

Attempt to use FILE_EDITS_PATTERN

I also attempted to force the FileEditsPredicate path using:

file:{'^(?!aviary/).*',withDiffContaining='(?s)^.*$'}

This was intended to:

  • ensure the expression matches FILE_EDITS_PATTERN
  • allow Java regex (and thus lookahead)
  • make the content pattern match anything

However:

  1. This does not appear to work correctly in practice.
  2. Even if it did, it is not a complete substitute for fileNot: semantics.

FileEditsPredicate evaluates both:

  • path matching, and
  • whether the file contains edits matching the content pattern

This means its behaviour differs from file: in important ways, for example:

  • files with no textual edits (or edge cases around adds/deletes/renames) may not behave identically
  • it requires diff computation rather than just path inspection
  • it introduces a different cost and evaluation model

So while it uses Java regex, it does not provide a clean way to express “there exists a changed file not matching X”.

Thanks,
David

Reply all
Reply to author
Forward
0 new messages