Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e600e4e
Phase 1: Add gitignore infrastructure and basic tests
technicalpickles Oct 2, 2025
012ed5b
Improve test coverage following repository patterns
technicalpickles Oct 2, 2025
141cff7
Phase 2: Integrate gitignore support into walk_directory
technicalpickles Oct 2, 2025
313024d
Address critical test gaps for Phase 2 gitignore support
technicalpickles Oct 2, 2025
aa5dfd3
feat: Add comprehensive gitignore documentation
technicalpickles Oct 2, 2025
1c245d5
Fix formatting issues with cargo fmt
technicalpickles Oct 2, 2025
710ab29
Fix clippy warning: use writeln! without empty string for blank lines
technicalpickles Oct 2, 2025
26d6448
Fix CI race condition in test_respects_global_gitignore
technicalpickles Oct 2, 2025
8c775f6
Add test fixture file that was being ignored by git
technicalpickles Oct 2, 2025
20d723c
Address PR review comments and simplify global gitignore handling
technicalpickles Oct 3, 2025
c5d7f90
apply more copilot fixes
technicalpickles Oct 3, 2025
1ce2b17
format
technicalpickles Oct 3, 2025
31e8237
move imports up
technicalpickles Oct 3, 2025
85568be
Address code review feedback from PR #22
technicalpickles Nov 11, 2025
8387c3b
Optimize gitignore check to only run on directories during traversal
technicalpickles Nov 11, 2025
cbfe284
Fix cache directory creation in per_file_cache
technicalpickles Nov 11, 2025
25c5ee6
Pin ignore crate to 0.4.23 to avoid edition2024 requirement
technicalpickles Nov 11, 2025
29b79e2
Pin globset crate to 0.4.16 to avoid edition2024 requirement
technicalpickles Nov 11, 2025
a327031
Replace deprecated cargo_bin with cargo_bin! macro
technicalpickles Nov 12, 2025
342b704
Run cargo fmt to fix import ordering
technicalpickles Nov 12, 2025
3fb23c9
Use fully qualified path for cargo_bin! macro
technicalpickles Nov 12, 2025
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
269 changes: 268 additions & 1 deletion ADVANCED_USAGE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,274 @@
# 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. **Include patterns** - Files matching `include:` patterns in configuration
2. **Gitignore patterns** - Files/directories in `.gitignore` (if `respect_gitignore: true`)
3. **Exclude patterns** - Files matching `exclude:` patterns in configuration
4. **Default exclusions** - Hardcoded exclusions (e.g., `{bin,node_modules,script,tmp,vendor}/**/*`)

This precedence allows you to:
- Override gitignore by explicitly including files via `include:` patterns
- Add additional exclusions beyond what's in `.gitignore` via `exclude:` patterns
- Layer multiple levels of filtering for fine-grained control
Comment on lines +64 to +76
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation claims that "Include patterns" have highest priority and can override gitignore patterns (line 68-74), but this is incorrect based on the implementation.

In the code (src/packs/walk_directory.rs lines 305-310), gitignored files are skipped with a continue statement before the include patterns are checked (line 328). This means gitignored files cannot be overridden by include patterns.

The actual precedence order is:

  1. Gitignore patterns - Files/directories in .gitignore are skipped first (if respect_gitignore: true)
  2. Include patterns - Files must match include: patterns to be considered
  3. Exclude patterns - Files matching exclude: are filtered out
  4. Default exclusions - Applied during directory traversal

The documentation should be corrected to reflect this actual behavior, or the implementation should be changed to allow include patterns to override gitignore (though the current behavior seems more reasonable for most use cases).

Copilot uses AI. Check for mistakes.

### Example Configuration

```yaml
# packwerk.yml
# Enable gitignore support (this is the default)
respect_gitignore: true
# Include patterns (highest priority - override gitignore)
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment "highest priority - override gitignore" is incorrect. Based on the implementation, gitignore patterns are checked before include patterns, so gitignored files cannot be overridden by include patterns.

This comment should be corrected to:

# Include patterns (what file extensions to analyze)
Suggested change
# Include patterns (highest priority - override gitignore)
# Include patterns (what file extensions to analyze)

Copilot uses AI. Check for mistakes.
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 include it
```gitignore
# .gitignore
*.log
!important.log # This file should NOT be ignored
```

5. **Override with include patterns** - Add explicit include patterns in your `packwerk.yml`:
```yaml
include:
- "path/to/important/file.rb"
```
Comment on lines +169 to +173
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This troubleshooting advice incorrectly states that include patterns can override gitignore patterns. As noted in a previous comment on lines 64-76, the implementation actually checks gitignore first (with a continue statement), so gitignored files are skipped before include patterns are evaluated.

This suggestion should be removed or corrected to indicate that negation patterns in .gitignore (step 4) are the proper way to un-ignore specific files, not include patterns in packwerk.yml.

Copilot uses AI. Check for mistakes.

#### 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** - Include patterns override gitignore; files matching `include:` will be processed even if gitignored
```yaml
include:
- "**/*.rb" # This will include ALL .rb files, even if gitignored
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is incorrect. Based on the implementation, include patterns do NOT override gitignore patterns. The gitignore check happens before include pattern matching, so gitignored files will never reach the include pattern check.

The comment at line 189 "This will include ALL .rb files, even if gitignored" is misleading - gitignored .rb files will still be skipped regardless of this include pattern.

This section should be corrected to accurately reflect the actual behavior.

Suggested change
- "**/*.rb" # This will include ALL .rb files, even if gitignored
- "**/*.rb" # This will include all non-gitignored .rb files; gitignored files will still be excluded

Copilot uses AI. Check for mistakes.
```

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.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,20 @@ serde_magnus = "0.7.0" # permits a ruby gem to interface with this library
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.16" # globbing - pinned to avoid edition2024 requirement in 0.4.18
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.23" # for gitignore pattern matching - pinned to avoid edition2024 requirement in 0.4.25

[dev-dependencies]
assert_cmd = "2.0.10" # testing CLI
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 @@ -17,7 +17,7 @@ pub(crate) mod monkey_patch_detection;
pub(crate) mod pack;
pub(crate) mod parsing;
pub(crate) mod raw_configuration;
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