Add .azdxignore support for azd x watch (#6152)#7697
Add .azdxignore support for azd x watch (#6152)#7697jongio wants to merge 5 commits intoAzure:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds .azdxignore (gitignore syntax) support to azd x watch and the internal watcher, enabling users to exclude files/directories from rebuild triggers while also respecting .gitignore.
Changes:
- Introduces reusable
pkg/ignorematcher that loads.azdxignore+.gitignore(additive), strips UTF-8 BOM, and is nil-safe. - Integrates the matcher into both watcher implementations to filter events and skip ignored directories during recursive watch setup.
- Adds/updates tests for ignore behavior and improves test helper usage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/pkg/ignore/ignore.go | New shared ignore matcher loading .azdxignore and .gitignore. |
| cli/azd/pkg/ignore/ignore_test.go | Unit tests covering matching behavior, BOM handling, negation, and edge cases. |
| cli/azd/pkg/watch/watch.go | Wires ignore matcher into event handling and recursive directory watching. |
| cli/azd/pkg/watch/watch_test.go | Adds integration tests ensuring watcher respects ignore files and delete behavior. |
| cli/azd/pkg/watch/watch_helpers_test.go | Simplifies substring search using strings.Index. |
| cli/azd/extensions/microsoft.azd.extensions/internal/cmd/watch.go | Integrates ignore matcher into azd x watch and updates recursive watch logic. |
| cli/azd/.vscode/cspell.yaml | Adds “azdxignore” to spellchecker dictionary. |
Comments suppressed due to low confidence (2)
cli/azd/extensions/microsoft.azd.extensions/internal/cmd/watch.go:137
event.Nameis very likely an absolute path (since directories are added viawatchRecursive(cwd, ...)), butglobIgnorePathscontains basename patterns like"package-lock.json"and folder globs like"node_modules/**/*". These patterns won’t match absolute paths such as.../project/package-lock.json, so the fast-path ignore is effectively bypassed and can cause unwanted rebuilds. Prefer matching against a root-relative path (e.g., computerelPath := filepath.Rel(cwd, event.Name)and rundoublestar.PathMatch(pattern, filepath.ToSlash(relPath))), or change patterns to include a prefix like"**/package-lock.json"/"**/node_modules/**".
// Fast path: ignore events matching hardcoded glob patterns.
shouldIgnore := false
for _, pattern := range globIgnorePaths {
matched, _ := doublestar.PathMatch(pattern, event.Name)
if matched {
shouldIgnore = true
break
}
}
cli/azd/pkg/watch/watch.go:106
- Same issue as the extension watcher: matching
fw.globIgnorePathsagainstevent.Name(likely absolute) means patterns like".git"/".git/**/*"won’t match.../project/.git/config. Even if.gitis usually skipped duringwatchRecursive, this undermines the “fast-path” intent and can regress if additional hardcoded patterns are added (especially file patterns). Match against a root-relative path (recommended) or update patterns to support absolute paths ("**/.git/**").
// Fast path: ignore events matching hardcoded glob patterns.
shouldIgnore := false
for _, pattern := range fw.globIgnorePaths {
matched, _ := doublestar.PathMatch(pattern, event.Name)
if matched {
shouldIgnore = true
break
}
}
Demo repo for Azure/azure-dev#7697. Contains source files that should trigger rebuilds and noisy directories (node_modules, dist, coverage, .idea) that .azdxignore should exclude from azd x watch.
wbreza
left a comment
There was a problem hiding this comment.
Code Review — PR #7697: .azdxignore support for azd x watch
Verdict: 💬 Comments — Solid feature with good test coverage and proper .gitignore library usage. A couple of items worth addressing.
Findings
🟠 1. Matcher precedence / negation semantics unclear
.azdxignore patterns are loaded before .gitignore into separate matchers. The comment says patterns are "additive," but this means .gitignore negation patterns (!) cannot override .azdxignore matches — they're evaluated in separate matcher instances. This differs from standard git behavior where later patterns override earlier ones within a single scope.
Suggestion: Document this explicitly: ".azdxignore matches take precedence; .gitignore negation patterns cannot un-ignore files matched by .azdxignore." If that's not the intended behavior, the matchers need to be merged differently.
🟠 2. Inconsistent root capture between two watchers
pkg/watch stores
oot in the struct (immutable, thread-safe). The extension watch command uses a cwd variable from function scope. Both work today, but it's a different pattern for the same logic. Suggest the extension also capture root once and store it.
🟡 3. Silent filepath.Rel() errors
Both watchers skip ignore checking silently if ilepath.Rel() returns an error. A debug-level log would help diagnose path resolution issues with symlinks or unusual mounts.
🟡 4. Rename event edge case untested
os.Stat() failure on removed/renamed paths defaults to checking both file and dir patterns. Behavior may vary by OS. Consider a test for rename events.
Note: Relationship to PR #7685
PR #7685 adds .azdignore for template init with its own loading in internal/repository/initializer.go. This PR creates a separate pkg/ignore package. Both use go-gitignore but don't share code. If both merge, consolidating into the shared package would reduce duplication.
What's Good
- ✅ Correct go-gitignore library usage with cross-platform ilepath.ToSlash() normalization
- ✅ Nil Matcher is safe (defensive coding)
- ✅ Comprehensive tests: empty, comments, negation, wildcards, BOM, backslash paths, unreadable files
- ✅ UTF-8 BOM stripping matches existing codebase pattern
- ✅ Clean refactoring of watcher test helpers
Nice work on the shared pkg/ignore abstraction — good foundation for reuse. 👍
|
Thanks for the thorough review @wbreza! All 4 findings addressed in 96ee0a2:
Re: PR #7685 consolidation note - agreed, if both merge we should consolidate into the shared |
wbreza
left a comment
There was a problem hiding this comment.
Re-Review — .azdxignore support for azd x watch
Re-reviewing to check status of prior findings.
Prior Findings Status
| # | Finding | Status |
|---|---|---|
| W1 | Matcher precedence / negation semantics | .gitignore negation (!) can't override .azdxignore matches (separate matchers, short-circuit logic) |
| W2 | Inconsistent root capture | ✅ Addressed |
| W3 | Silent filepath.Rel() errors |
❌ Not addressed — All 3 call sites still silently skip ignore checking on error. A debug-level log would help diagnose path resolution issues. |
| W4 | Rename event edge case untested | os.Stat() failure fallback path (isDir=true re-check) |
| C1 | GetFileChanges() draining assertion | ✅ Addressed |
| C2 | Vendor path absolute check | ✅ Addressed |
| C3 | Direct type assertion panic | ✅ Addressed |
4/7 fully addressed, 2 partial, 1 not addressed.
New Findings
🟡 1. No user-facing documentation for .azdxignore
The PR body is thorough, but there's no docs/ page, command help text, or README explaining the feature to end users — file format, placement, relationship to .gitignore.
🟡 2. Missing cross-file negation test
TestNewMatcher_Negation only tests negation within a single file. A test verifying that !pattern in .gitignore cannot un-ignore a path matched by .azdxignore would document the exact behavior from W1.
🟡 3. No test for os.Stat() failure fallback
📁 pkg/watch/watch.go
When os.Stat() fails (deleted file), code re-checks with isDir=true. This code path has no dedicated test.
🔵 4. filepath.Abs() error not wrapped
📁 pkg/ignore/ignore.go ~line 46
Error from filepath.Abs() returned without context wrapping. Should be fmt.Errorf("resolving root path: %w", err).
Overall
Good progress — 4/7 prior findings fully resolved, Copilot's 3 comments all addressed. Remaining items are medium/low severity. The main gap is W3 (silent filepath.Rel() errors) — even a single log.Printf at debug level would close this out. Nice work on the shared pkg/ignore package. 👍
|
Thanks for the re-review @wbreza! Pushed 00d8802 addressing the remaining items. A couple of corrections on the status table: W1 (Matcher precedence) - This is fully addressed. The W3 (Silent filepath.Rel) - This was addressed in 96ee0a2. All 3 call sites have New items addressed in 00d8802:
All 43 tests pass (17 ignore + 26 watch). |
00d8802 to
360f816
Compare
Add file ignore support so `azd x watch` can skip irrelevant paths (node_modules/, build output, IDE config) that cause unnecessary rebuilds. Changes: - New shared `pkg/ignore` package with Matcher type that loads both `.azdxignore` and `.gitignore` (additive, gitignore syntax) - Integrated into `pkg/watch/watch.go` (conversational agent watcher) - Integrated into `extensions/.../cmd/watch.go` (azd x watch) - Comprehensive tests (37 pass, 1 platform-skip) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix backslash path test to actually exercise path normalization - Add create-then-delete watcher test for delete-from-Created path - Replace hand-rolled indexOf with strings.Index - Add blank/whitespace line edge case test for ignore files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Combine GetFileChanges assertions into single Eventually poll to avoid drain bug - Use filepath.Rel for vendor path check instead of fragile substring match - Use comma-ok type assertion to avoid panics in tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Clarify additive/union semantics in NewMatcher doc comments - Add debug logging for filepath.Rel() errors in both watchers - Document cwd capture as immutable root in extension watcher - Add TestGetFileChanges_RenameFile for rename edge case coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rap, docs - Add TestNewMatcher_CrossFileNegationCannotOverride to codify that .gitignore negation cannot un-ignore .azdxignore matches (W1/New 2) - Add TestGetFileChanges_DeleteIgnoredDirFallback to test the os.Stat failure re-check with isDir=true for dir-only patterns (W4/New 3) - Wrap filepath.Abs() error with context in NewMatcher (New 4) - Add Long description to watch cobra command documenting .azdxignore usage with example (New 1) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
360f816 to
d49fa9e
Compare
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
Summary
Adds
.azdxignorefile support forazd x watch, allowing users to exclude files and directories from the file watcher. This eliminates excessive rebuilds caused by changes innode_modules/, build output, test artifacts, and other non-source files.Closes #6152 | Supersedes #6153 (stale draft, never merged)
What's New
Shared
pkg/ignorePackageA new reusable package at
cli/azd/pkg/ignore/that:.azdxignorefrom the project root (gitignore syntax).gitignore(additive — both files apply)go-gitignore(already a dependency, no new deps)Watch Integration
Both watch implementations now use the shared ignore matcher:
cli/azd/pkg/watch/watch.go(conversational agent)cli/azd/extensions/.../cmd/watch.go(azd x watchCLI)Usage Example
Create a
.azdxignorefile in your project root:The watcher will also respect patterns from your
.gitignore. Both files use standard gitignore syntax.Issues Fixed from Prior PR #6153
This is a fresh implementation that addresses all 5 review findings from #6153:
os.Statin event handler (redundant syscall)Absolute()instead ofRelative()time.Sleepin tests (flaky)require.Eventuallywith proper pollingfilepath.Relerrorpkg/ignorepackage used by both watchersAdditional Fixes (from MQ review)
os.Getenv("GOOS")toruntime.GOOS(GOOS is a build constant, not an env var)Scope Decision
Audited all
azd xcommands (build,pack,init,publish,release,version,metadata). Onlywatchbenefits from ignore support — other commands operate on flat/narrow directory scans with existing filters.Test Coverage
pkg/ignore, 24 inpkg/watch) — 39 pass, 1 skip (Windows file permission edge case)pkg/ignore: 85.2% coveragepkg/watch: 66.4% coverage (78% excluding deprecatedPrintChangedFiles)Files Changed
cli/azd/pkg/ignore/ignore.gocli/azd/pkg/ignore/ignore_test.gocli/azd/pkg/watch/watch.gocli/azd/pkg/watch/watch_test.gocli/azd/pkg/watch/watch_helpers_test.gocli/azd/extensions/.../cmd/watch.gocli/azd/.vscode/cspell.yaml