Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
66be27e
Phase 1: Add gitignore infrastructure and basic tests
technicalpickles Oct 2, 2025
d97c055
Improve test coverage following repository patterns
technicalpickles Oct 2, 2025
01762ef
Phase 2: Integrate gitignore support into walk_directory
technicalpickles Oct 2, 2025
11fb9a4
Address critical test gaps for Phase 2 gitignore support
technicalpickles Oct 2, 2025
6f75e0f
feat: Add comprehensive gitignore documentation
technicalpickles Oct 2, 2025
6e6a0fc
Fix formatting issues with cargo fmt
technicalpickles Oct 2, 2025
e18ffc2
Fix clippy warning: use writeln! without empty string for blank lines
technicalpickles Oct 2, 2025
1b4b85c
Fix CI race condition in test_respects_global_gitignore
technicalpickles Oct 2, 2025
2d6bb7b
Add test fixture file that was being ignored by git
technicalpickles Oct 2, 2025
6e29276
Address PR review comments and simplify global gitignore handling
technicalpickles Oct 3, 2025
2d7c773
apply more copilot fixes
technicalpickles Oct 3, 2025
acd8071
format
technicalpickles Oct 3, 2025
b4c6e49
move imports up
technicalpickles Oct 3, 2025
9c2a51e
Address code review feedback from PR #22
technicalpickles Nov 11, 2025
b907c3f
Optimize gitignore check to only run on directories during traversal
technicalpickles Nov 11, 2025
f561ca5
Fix cache directory creation in per_file_cache
technicalpickles Nov 11, 2025
159798f
Pin ignore crate to 0.4.23 to avoid edition2024 requirement
technicalpickles Nov 11, 2025
aa530d5
Pin globset crate to 0.4.16 to avoid edition2024 requirement
technicalpickles Nov 11, 2025
5f905fa
Replace deprecated cargo_bin with cargo_bin! macro
technicalpickles Nov 12, 2025
f2850e8
Run cargo fmt to fix import ordering
technicalpickles Nov 12, 2025
5bb6164
Use fully qualified path for cargo_bin! macro
technicalpickles Nov 12, 2025
044f77a
Address PR #22 review feedback: docs, Arc double-wrap, fixture, RAII …
technicalpickles Jun 22, 2026
b82cda9
Unpin globset and ignore dependencies
technicalpickles Jun 22, 2026
9aefebf
Address dduugg concerns 1+2+4: CHANGELOG + eliminate subprocess
technicalpickles Jun 22, 2026
bd55e85
Merge remote-tracking branches 'origin/gitignore-walkbuilder' and 'or…
technicalpickles Jun 22, 2026
47acf00
cargo fmt: wrap long args slice in GitConfigGuard Drop impl
technicalpickles Jun 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 255 additions & 1 deletion ADVANCED_USAGE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,261 @@
# Packs first mode
# Advanced Usage

This document covers advanced configuration options and features in `pks`.

## Packs First Mode

Packs first mode can be used if your entire team is using `packs`. Currently, the only thing this does is change the copy in `package_todo.yml` files to reference `pks` instead of `packwerk`.

There are two ways to enable this:
1. Rename `packwerk.yml` to `packs.yml` and packs first mode will be automatically enabled.
2. Set `packs_first_mode: true` in your `packwerk.yml`

---

## Gitignore Support

### Overview

By default, `pks` automatically respects `.gitignore` files when analyzing your codebase. This means any files or directories listed in your `.gitignore` will be excluded from pack analysis.

This feature:
- ✅ Reduces noise by excluding generated files, temporary files, and vendor code
- ✅ Improves performance by skipping ignored directories during traversal
- ✅ Works automatically without any configuration
- ✅ Can be disabled if you need behavior identical to `packwerk`

### What Files Are Respected?

`pks` checks multiple gitignore sources in this order:

1. **Local `.gitignore`** - The `.gitignore` file in your repository root
2. **Global gitignore** - Your user-level gitignore file from `git config --global core.excludesFile`
3. **Git exclude file** - The `.git/info/exclude` file in your repository

All standard gitignore features are supported:
- Pattern matching (e.g., `*.log`, `tmp/`)
- Directory exclusion (e.g., `node_modules/`)
- Negation patterns (e.g., `!important.log`)
- Comments (lines starting with `#`)

### Configuration

#### Disabling Gitignore Support

If you need to disable automatic gitignore support, add this to your `packwerk.yml` or `packs.yml`:

```yaml
# Disable automatic gitignore support
respect_gitignore: false
```

#### When to Disable?

You might want to disable gitignore support if:
- You have files in `.gitignore` that should still be analyzed by `pks`
- You're migrating from `packwerk` and want identical behavior
- You have custom `exclude:` patterns that you prefer to manage manually
- You need to analyze generated code that's normally gitignored

#### Default Behavior

If not specified, `respect_gitignore` defaults to `true` (enabled).

### Precedence of Ignore Rules

When determining whether to process a file, `pks` applies rules in this order (highest priority first):

1. **Gitignore patterns** - Files/directories in `.gitignore` (if `respect_gitignore: true`)
2. **Exclude patterns** - Files matching `exclude:` patterns in configuration
3. **Default exclusions** - Hardcoded exclusions (e.g., `{bin,node_modules,script,tmp,vendor}/**/*`)
4. **Include patterns** - Files must match `include:` patterns to be analyzed

This means gitignore takes precedence: a gitignored file is skipped even if it would otherwise match an `include:` pattern. Use `.gitignore` negation patterns (e.g., `!path/to/file.rb`) if you need a gitignored file to be analyzed.

### Example Configuration

```yaml
# packwerk.yml

# Enable gitignore support (this is the default)
respect_gitignore: true

# Include patterns (what file extensions to analyze)
include:
- "**/*.rb"
- "**/*.rake"
- "**/*.erb"

# Exclude patterns (lower priority than gitignore)
exclude:
- "{bin,node_modules,script,tmp,vendor}/**/*"
- "test/fixtures/**/*"
```

**Example scenario:**

Given this configuration and a `.gitignore` containing `debug.log`:

- `app/models/user.rb` → ✅ Analyzed (matches include pattern)
- `tmp/cache/foo.rb` → ❌ Skipped (matches default exclusion)
- `debug.log` → ❌ Skipped (matches gitignore)
- `test/fixtures/data.rb` → ❌ Skipped (matches exclude pattern)

### How It Works

When `respect_gitignore: true` (default):
- ✅ Files in `.gitignore` are automatically skipped during directory walking
- ✅ Directories in `.gitignore` are not traversed (improves performance)
- ✅ Global gitignore patterns are applied
- ✅ Gitignore negation patterns (e.g., `!important.log`) are respected
- ✅ `.git/info/exclude` patterns are applied

When `respect_gitignore: false`:
- ❌ `.gitignore` files are completely ignored
- ✅ Only `include:` and `exclude:` patterns from configuration are used
- ✅ Behavior matches `packwerk` exactly

### Performance Implications

Enabling gitignore support typically has **neutral to positive** performance impact:
- ✅ Ignored directories are skipped entirely (faster directory walking)
- ✅ Fewer files need to be processed
- ✅ Pattern matching is highly optimized (uses the same engine as `ripgrep`)
- ✅ Gitignore matcher is built once and reused throughout the walk

In practice, this means:
- Large ignored directories like `node_modules/`, `tmp/`, or `vendor/` are skipped immediately
- No time wasted parsing or analyzing files that would be ignored anyway
- Memory usage is lower due to fewer files being tracked

### Troubleshooting

#### Files Are Unexpectedly Ignored

If files are being ignored that shouldn't be:

1. **Check your `.gitignore`** - Run `git check-ignore -v path/to/file.rb` to see which pattern is matching
```bash
git check-ignore -v app/models/user.rb
# Output: .gitignore:10:*.rb app/models/user.rb
```

2. **Check global gitignore** - See where your global gitignore is configured:
```bash
# Check if core.excludesFile is set
git config --global core.excludesFile
# Output: /Users/you/.config/git/ignore (or your custom path)

# View its contents if set
cat $(git config --global core.excludesFile)
```

3. **Disable temporarily** - Set `respect_gitignore: false` to confirm it's a gitignore issue
```yaml
# packwerk.yml
respect_gitignore: false
```

4. **Use negation patterns** - Add `!path/to/file.rb` to your `.gitignore` to explicitly un-ignore it
```gitignore
# .gitignore
*.log
!important.log # This file should NOT be ignored
```

#### Files Are Still Analyzed Despite Being in .gitignore

If gitignored files are still being analyzed:

1. **Check configuration** - Ensure `respect_gitignore: true` (or not set, since it defaults to `true`)
```yaml
# packwerk.yml should have either:
respect_gitignore: true
# or nothing (defaults to true)
```

2. **Check include patterns** - Note that `include:` patterns do NOT override gitignore. Gitignored files are skipped even if they match an `include:` pattern. To un-ignore a specific file, use a `.gitignore` negation pattern (`!path/to/file.rb`) or set `respect_gitignore: false`.

3. **Check file location** - Only files within the project root can be affected by gitignore
- Files must be relative to the repository root
- Symlinked files outside the repo may not respect gitignore

4. **Verify .gitignore syntax** - Ensure your patterns are valid
```bash
# Test if git itself recognizes the pattern
git status # Should not show the file if properly ignored
git check-ignore path/to/file.rb # Should output the path if ignored
```

#### Debugging Commands

Useful commands for debugging gitignore behavior:

```bash
# List all files that pks will analyze
pks list-included-files

# Check if a specific file would be ignored by git
git check-ignore -v path/to/file.rb

# See your global gitignore location
git config --global core.excludesFile

# View your global gitignore contents (if core.excludesFile is set)
cat $(git config --global core.excludesFile)

# View repository-specific exclusions
cat .git/info/exclude

# Test gitignore patterns
echo "path/to/file.rb" | git check-ignore --stdin -v
```

### Compatibility with Packwerk

This feature is a **new addition** in `pks` and does not exist in `packwerk`.

#### Migrating from Packwerk

If you're migrating from `packwerk` to `pks`:

1. **Default behavior change**: `pks` will automatically respect `.gitignore` files, while `packwerk` does not
2. **Files that may be affected**: Any files in your `.gitignore` that were previously analyzed by `packwerk` will now be skipped
3. **To get identical behavior**: Set `respect_gitignore: false` in your configuration
4. **Recommended approach**: Try the default behavior first; it usually works better and is faster

#### Example Migration

```yaml
# packwerk.yml - for packwerk-identical behavior
respect_gitignore: false

# Or accept the new default (recommended)
# respect_gitignore: true # This is the default, no need to specify
```

---

## Custom Error Messages

The error messages resulting from running `pks check` can be customized with mustache-style interpolation. The available variables are:
- `violation_name`
- `referencing_pack_name`
- `defining_pack_name`
- `constant_name`
- `reference_location`
- `referencing_pack_relative_yml`

Layer violations also have:
- `defining_layer`
- `referencing_layer`

Example:
```yaml
# packwerk.yml
checker_overrides:
privacy_error_template: "{{reference_location}}Privacy violation: `{{constant_name}}` is private to `{{defining_pack_name}}`. See https://go/pks-privacy"
dependency_error_template: "{{reference_location}}Dependency violation: `{{constant_name}}` belongs to `{{defining_pack_name}}`. See https://go/pks-dependency"
```

See the main [README.md](README.md) for more details.
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Changelog

## Unreleased

### Breaking Changes

#### `respect_gitignore` defaults to `true`

pks now respects `.gitignore` files by default. Files and directories matched by
`.gitignore`, `.git/info/exclude`, or your global gitignore (`core.excludesFile`)
are excluded from analysis.

**Who is affected:** any project that previously relied on pks analyzing gitignored
paths — for example, vendored code checked into `.gitignore`-excluded directories,
or generated files that matter for boundary checking.

**What changes:** pks silently produces different (smaller) results without any
configuration change. This is intentional: most projects want gitignored files
excluded, and the old behavior (analyze everything) was rarely desired.

**Opt out:** add the following to `packwerk.yml` to restore the previous behavior:

```yaml
respect_gitignore: false
```
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ serde_magnus = "0.7.0" # permits
tracing = "0.1.37" # logging
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } # logging
glob = "0.3.1" # globbing
globset = "0.4.10" # globbing
globset = "0.4" # globbing
lib-ruby-parser = "4.0.6" # ruby parser
md5 = "0.7.0" # md5 hashing to take and compare md5 digests of file contents to ensure cache validity
line-col = "0.2.1" # for creating source maps of violations
ruby_inflector = '0.0.8' # for inflecting strings, e.g. turning `has_many :companies` into `Company`
petgraph = "0.6.3" # for running graph algorithms (e.g. does the dependency graph contain a cycle?)
fnmatch-regex2 = "0.3.0"
strip-ansi-escapes = "0.2.0"
ignore = "0.4" # for gitignore pattern matching

[dev-dependencies]
assert_cmd = "2.1.1" # testing CLI
Expand All @@ -56,3 +57,4 @@ rusty-hook = "^0.11.2" # git hooks
predicates = "3.0.2" # kind of like rspec assertions
pretty_assertions = "1.3.0" # Shows a more readable diff when comparing objects
serial_test = "3.1.1" # Run specific tests in serial
tempfile = "3.8.0" # for creating temporary directories in tests
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Currently, it ships the following checkers to help improve the boundaries betwee

See [Checkers](CHECKERS.md) for detailed descriptions.

## Automatic Gitignore Support
- Automatically respects `.gitignore` files (both local and global)
- Improves performance by skipping ignored directories during traversal
- Can be disabled via `respect_gitignore: false` configuration if needed

# Fork
This repo was forked directly from https://github.com/alexevanczuk/packs

Expand Down Expand Up @@ -90,6 +95,18 @@ There are still some known behavioral differences between `pks` and `packwerk`.
- `package_paths` must not end in a slash, e.g. `pks/*/` is not supported, but `pks/*` is.
- A `**` in `package_paths` is supported, but is not a substitute for a single `*`, e.g. `pks/**` is supported and will match `pks/*/*/package.yml`, but will not match `pks/*/package.yml`. `pks/*` must be used to match that.

## Gitignore Support (pks-specific feature)
`pks` automatically respects `.gitignore` files when analyzing your codebase. This means:
- Files listed in `.gitignore` are automatically excluded from analysis
- Respects global gitignore from `core.excludesFile` git config
- Respects `.git/info/exclude`
- Improves performance by skipping ignored directories entirely
- Can be disabled by setting `respect_gitignore: false` in `packwerk.yml`

This feature is **enabled by default**. If you need behavior identical to `packwerk`, set `respect_gitignore: false`.

For detailed configuration, precedence rules, and troubleshooting, see [ADVANCED_USAGE.md](ADVANCED_USAGE.md).

## Default Namespaces
`pks` supports Zeitwerk default namespaces. However, since it doesn't have access to the Rails runtime, you need to explicitly specify the namespaces in `packwerk.yml`.

Expand Down
2 changes: 1 addition & 1 deletion src/packs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub(crate) mod parsing;
pub(crate) mod raw_configuration;
pub(crate) mod template;
pub(crate) mod text;
pub(crate) mod walk_directory;
pub mod walk_directory;

mod constant_dependencies;
mod file_utils;
Expand Down
11 changes: 11 additions & 0 deletions src/packs/caching/per_file_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ impl Cache for PerFileCache {

let cache_data = serde_json::to_string(&cache_entry)
.context("Failed to serialize references")?;

// Ensure parent directory exists
if let Some(parent) = empty_cache_entry.cache_file_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
anyhow::Error::new(e).context(format!(
"Failed to create cache directory {:?}",
parent
))
})?;
}

let mut file = File::create(&empty_cache_entry.cache_file_path)
.map_err(|e| {
anyhow::Error::new(e).context(format!(
Expand Down
8 changes: 8 additions & 0 deletions src/packs/raw_configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ pub(crate) struct RawConfiguration {

#[serde(default)]
pub checker_overrides: Option<CheckerOverrides>,

// Whether to automatically respect .gitignore files
#[serde(default = "default_respect_gitignore")]
pub respect_gitignore: bool,
}
/// Customize violation names and error messages
#[derive(Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -173,6 +177,10 @@ fn default_cache_directory() -> String {
String::from("tmp/cache/packwerk")
}

fn default_respect_gitignore() -> bool {
true
}

fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
Expand Down
Loading
Loading