Git change detection and glob matching for pipeline activation.
Provides utilities to determine which files changed between commits, match file paths against glob patterns, and resolve which scopes should be activated based on changed files.
Glob Matching Rules
Patterns use ** and * wildcards:
**— matches any number of path segments (including zero), e.g."apps/api/**"matches"apps/api/lib/user.ex"*— matches anything except/, e.g."*.md"matches"README.md"- Patterns without
/also match against the file's basename, so"*.md"matches both"README.md"and"docs/guide.md"
Base Commit Resolution
The base commit for git diff is determined by priority:
- PR base branch (
BUILDKITE_PULL_REQUEST_BASE_BRANCH) —origin/<base> - Non-default branch —
origin/<default_branch> - Default branch —
HEAD~1
Examples
base = Pipette.Git.base_commit(ctx)
{:ok, files} = Pipette.Git.changed_files(base)
fired = Pipette.Git.fired_scopes(scopes, files)
Pipette.Git.matches_glob?("apps/api/lib/user.ex", "apps/api/**")
#=> true
Pipette.Git.matches_glob?("README.md", "*.md")
#=> true
Summary
Functions
Check if all changed files match the ignore patterns.
Determine the base commit for git diff comparison.
Get the list of files changed since the base commit.
Determine which scopes are fired by the given changed files.
Check if a file path matches a glob pattern.
Functions
Check if all changed files match the ignore patterns.
Returns true when every file in the list matches at least one ignore
pattern. Returns false for an empty file list (no changes means not ignored).
Examples
Pipette.Git.all_ignored?(["README.md", "docs/guide.md"], ["*.md", "docs/**"])
#=> true
Pipette.Git.all_ignored?(["README.md", "apps/api/lib/user.ex"], ["*.md"])
#=> false
Pipette.Git.all_ignored?([], ["*.md"])
#=> false
@spec base_commit(Pipette.Context.t()) :: String.t()
Determine the base commit for git diff comparison.
Resolution order:
- PR base branch —
origin/<BUILDKITE_PULL_REQUEST_BASE_BRANCH> - Non-default branch —
origin/<default_branch> - Default branch —
HEAD~1
Examples
iex> ctx = %Pipette.Context{pull_request_base_branch: "main", branch: "feature/x", default_branch: "main"}
iex> Pipette.Git.base_commit(ctx)
"origin/main"
iex> ctx = %Pipette.Context{pull_request_base_branch: nil, branch: "main", default_branch: "main"}
iex> Pipette.Git.base_commit(ctx)
"HEAD~1"
Get the list of files changed since the base commit.
Runs git diff --name-only <base> and returns the list of file paths.
Accepts a :runner option for testing (a function that takes the base
commit and returns {:ok, output} or {:error, reason}).
Examples
{:ok, files} = Pipette.Git.changed_files("origin/main")
files #=> ["apps/api/lib/user.ex", "apps/web/src/App.tsx"]
@spec fired_scopes([Pipette.Scope.t()], [String.t()]) :: MapSet.t(atom())
Determine which scopes are fired by the given changed files.
A scope fires when any changed file matches at least one of its files
patterns and none of its exclude patterns.
Examples
scopes = [
%Pipette.Scope{name: :api_code, files: ["apps/api/**"]},
%Pipette.Scope{name: :web_code, files: ["apps/web/**"]}
]
fired = Pipette.Git.fired_scopes(scopes, ["apps/api/lib/user.ex"])
fired #=> MapSet.new([:api_code])
Check if a file path matches a glob pattern.
Patterns without / also match against the file's basename.
Examples
iex> Pipette.Git.matches_glob?("apps/api/lib/user.ex", "apps/api/**")
true
iex> Pipette.Git.matches_glob?("README.md", "*.md")
true
iex> Pipette.Git.matches_glob?("apps/api/lib/user.ex", "apps/web/**")
false