Fix rules:exists directory lookup missing existing directories

What does this MR do and why?

Since 18.2, rules:exists treats a path ending in / as a directory check. That check could return false for a directory that exists, depending on which unrelated files happened to be in the repository.

The trailing-slash branch of exact_matches? used bsearch with a start_with? predicate. bsearch requires a predicate that is false for a prefix of the sorted array and true for the rest, but start_with?("docs/") is only true for the contiguous block of paths inside docs/, with false on both sides. A probe landing past that block tells the search to keep going right, so it can return nil without ever looking at the directory.

This MR searches for the first path that is not less than the glob instead, which is a monotonic predicate, and then checks whether that path is inside the directory. This keeps the lookup O(log n), matching the existing exact-file branch, which already relies on the paths being sorted (git tree order is byte-sorted).

A regression spec reproduces the issue's file layout: docs/ is truthy, and the prefix sibling doc/ is falsy. The spec fails on the previous code and passes with the fix.

References

Screenshots or screen recordings

Not applicable, backend-only change with no UI.

How to set up and validate locally

  1. Create a project with these files:

    .gitlab-ci.yml  Cargo.toml  README.md  a.txt  b.txt  c.txt  docs/guide.md  src/main.rs  z.txt
  2. Use this .gitlab-ci.yml:

    docs-dir:
      script: echo docs/ exists
      rules:
        - exists: [docs/]
    always:
      script: echo always
  3. Run a pipeline. Before this change, only always is created. After this change, both docs-dir and always are created.

  4. Alternatively, verify the predicate directly in a Rails console:

    paths = %w[.gitlab-ci.yml Cargo.toml README.md a.txt b.txt c.txt docs/guide.md src/main.rs z.txt]
    paths.bsearch { |path| path.start_with?("docs/") }                       # => nil (old)
    paths.bsearch { |path| path >= "docs/" }&.start_with?("docs/")           # => true (new)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading
Loading