aboutsummaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-04-27Coalesce idle-timer flushes in projectile-cache-current-fileHEADmasterBozhidar Batsov
Each call to `projectile-cache-current-file' under persistent caching scheduled a fresh 30-second idle timer that closed over a snapshot of the file list at scheduling time. Opening N files in a session queued N timers; once Emacs went idle they all fired and serialized the cache N times, with the earlier ones writing stale (shorter) lists. Maintain a per-project pending timer in `projectile--pending-cache-flush-timers' instead, cancelling and rescheduling on each new file. The fired callback re-reads the current in-memory cache, so the disk write reflects the final state rather than whichever snapshot the last timer captured.
2026-04-27Delete cache file in projectile-invalidate-cacheBozhidar Batsov
Previously the persistent invalidate path called `(projectile-serialize nil ...)', leaving a four-byte file containing the literal "nil" on disk. Resolving the long-standing TODO: just delete the file when invalidating, so the on-disk state matches the intent of the command (and so an orphaned cache file isn't left behind on uninstall).
2026-04-27Persist disk cache in projectile-purge-dir-from-cacheBozhidar Batsov
`projectile-purge-file-from-cache' writes the updated file list to disk under persistent caching; the directory variant only mutated the in-memory hash, so the purged subtree would reappear on the next Emacs session. Mirror the persistence step here.
2026-04-27Seed cache time when loading the project file cache from diskBozhidar Batsov
Without this, `projectile-files-cache-expire' combined with persistent caching ended up re-reading the cache file from disk on every call to `projectile-project-files' and never reindexing: the TTL check at the top of `projectile-project-files' evicts the in-memory entry whenever its cache time is missing, and `projectile-load-project-cache' was populating only `projectile-projects-cache' on disk loads. Use the cache file's mtime as the recorded time, so the TTL check sees a real age and reindexing happens when (and only when) the data is actually stale.
2026-04-26Merge pull request #2000 from bbatsov/indexing-cleanup-finalBozhidar Batsov
Final indexing review batch: cleanup, tests, docs
2026-04-26Document remaining indexing semanticsBozhidar Batsov
Three doc gaps surfaced during the indexing review: * The `*' prefix on `projectile-globally-ignored-directories' entries is not a glob - it is what promotes a basename match from "anchored at the project root" to "anywhere in the tree", and it only takes effect under hybrid indexing. * The default `projectile-generic-command' picks `fd' when it's on PATH and falls back to a `find ... | tr ...' pipeline. The fallback does not exclude common build directories, which is a trap for non-VCS projects under alien on hosts without `fd'. * `projectile-git-use-fd' controls how Projectile handles deleted-but-unstaged files: with `fd' they disappear immediately, with `git ls-files' Projectile post-filters via `git ls-files -zd'.
2026-04-26Backfill tests for indexing command dispatchBozhidar Batsov
`projectile-get-ext-command' had no direct test coverage, so the non-git VCS branches (hg/svn/bzr/darcs/fossil/pijul/sapling/jj) were relying on indirect coverage that didn't actually exercise the dispatch. Add a `describe' block that pins each branch. Also extend the `projectile-dir-files-alien' tests with two cases that were previously uncovered: the fd path for git (verifying we don't also call `projectile-git-deleted-files') and the no-VCS fallback (verifying the generic command is used).
2026-04-26Skip git submodule scan when there is no .gitmodulesBozhidar Batsov
`projectile-get-immediate-sub-projects' was unconditionally shelling out to `git submodule --quiet foreach ...' on every indexing call for git projects, even when the project had no submodules at all. For monorepos that re-index the project root often this is pure overhead. Use `locate-dominating-file' to look for `.gitmodules' along the parent chain (PATH may be inside a git repo without being its toplevel) and skip the shell-out when none is found. Also tighten `projectile-discover-projects-in-directory' to filter `.' / `..' via `directory-files-no-dot-files-regexp' instead of a post-filter `member' check, matching the indexing walker's style.
2026-04-26Merge pull request #1999 from bbatsov/indexing-perfBozhidar Batsov
Speed up indexing's ignore filtering
2026-04-26Add tests for the indexing fast-path helpersBozhidar Batsov
Cover the new internal helpers (`projectile--list->set', `projectile--ignored-file-fast-p', `projectile--ignored-directory- fast-p') and add end-to-end tests for the previously uncovered behaviour: * `projectile-index-directory' honors dirconfig glob ignore patterns at every directory level - previously only the alien-mode dirconfig warning had any test coverage for this. * Ensure (`!') entries in `.projectile' override an ignore pattern. * `projectile-remove-ignored' drops basename matches, treats `*'- prefixed ignored-dir entries as any-segment matches, and treats plain entries as path-prefix matches.
2026-04-26Speed up native + hybrid indexing's ignore filteringBozhidar Batsov
Hash-set the ignored-files / ignored-directories / globally-ignored- directory-names lists once per indexing call, instead of repeating linear `member' scans on every file. On a project with N files and M ignore entries this drops the inner work from O(N*M) to roughly O(N). In the native walker (`projectile-index-directory') three additional changes: * Glob patterns for `.projectile' ignore/ensure are pre-expanded once per directory level, instead of once per (file, pattern) pair via `file-expand-wildcards' inside `projectile-check-pattern-p'. * Discovered files accumulate into a single mutable cell threaded through the recursion, so we don't pay for an `apply append' at each level (which copied every file once per level it bubbled through). * `default-directory' is rebound for glob expansion, but only after the directory listing is captured - so callers can still pass a relative `directory' argument. In the hybrid post-processor (`projectile-remove-ignored'), pre-split ignored-dirs into prefix-match and any-segment groups and hash the segment names. The per-file segment loop becomes a hash lookup; the basename match against ignored-files is now also a hash lookup. The public functions (`projectile-ignored-file-p', `projectile- ignored-directory-p', `projectile-ignored-rel-p', `projectile-check-pattern-p') keep their existing signatures and behaviour for external callers.
2026-04-26Merge pull request #1998 from bbatsov/indexing-keep-and-fdBozhidar Batsov
Batch hybrid indexing across + keep entries
2026-04-26Add tests for hybrid `+' keep batchingBozhidar Batsov
`+' keep entries had no end-to-end test coverage. Add tests that verify: * Multiple `+' keep entries trigger a single batched call to the external command, with the kept paths passed as the new pathspecs argument. * The single-keep-dir case keeps going through `projectile-dir-files' for each subdirectory (no behavioural change there). * `projectile--restricted-sub-projects-files' returns all submodule files when no subdirs are supplied, drops files outside the supplied subdirs when they are, and normalises subdirs without a trailing slash. Also add a focused test for the new `pathspecs' argument of `projectile-files-via-ext-command'.
2026-04-26Batch hybrid indexing across `+' keep dirconfig entriesBozhidar Batsov
When a project's `.projectile' declares multiple `+' keep entries, hybrid indexing used to walk each subdirectory individually, shelling out to the external indexing command once per entry. The TODO in `projectile-project-files' had been there for a while. Push the kept paths into the external command as positional pathspecs and run `projectile-adjust-files' once over the combined result. `git ls-files', `fd', `find', `hg locate', etc. all accept additional path arguments at the end of the command line. For Git submodules, `projectile-get-sub-projects-files' is queried once and the result is filtered to only those falling under one of the kept subdirectories. `projectile-files-via-ext-command' grows an optional `pathspecs' arg (shell-quoted before being appended); `projectile-dir-files-alien' grows a matching optional `subdirs' arg that threads through.
2026-04-26Merge pull request #1997 from bbatsov/indexing-cleanup-1Bozhidar Batsov
Indexing dispatch cleanups
2026-04-26Document hybrid indexing and add a per-method feature matrixBozhidar Batsov
The configuration page covered alien indexing in depth but mentioned hybrid only in passing, so users had to read the source to learn that hybrid is "alien with Projectile's filtering rules layered on top". Add a dedicated section explaining what hybrid does and when to reach for it, and a comparison table summarising which knobs (dirconfig, global ignores/unignores, sort order, default caching, Windows support) apply under each indexing method.
2026-04-26Add tests for hybrid indexingBozhidar Batsov
The hybrid path through `projectile-dir-files` had zero direct coverage. Add tests that verify it applies `projectile-globally-ignored-file-suffixes` and dirconfig ignore patterns on top of the alien result, and that the resolved VCS is threaded through to `projectile-dir-files-alien` exactly once. Also adds a focused test for `projectile-dir-files-alien` honoring an explicitly supplied VCS argument without re-running `projectile-project-vcs`.
2026-04-26Avoid recomputing VCS in projectile-dir-files-alienBozhidar Batsov
The dispatcher in `projectile-dir-files` already resolves the VCS for the hybrid path. Thread it through to `projectile-dir-files-alien` via a new optional argument so we don't pay for a second `projectile-project-vcs` call on every indexing run. Single-argument callers (and the spies in the test suite) keep working unchanged. Also drop the post-hoc `(member local-f '("." ".."))` filter in `projectile-index-directory` in favour of `directory-files`' `directory-files-no-dot-files-regexp`, which handles the same case at the C level.
2026-04-26Merge pull request #1996 from bbatsov/root-detection-fixesBozhidar Batsov
Root detection: fix #1211 and #1836, plus cleanup
2026-04-26Share walk skeleton between locate-dominating-file helpersBozhidar Batsov
projectile-locate-dominating-file (bottommost match) and projectile-locate-dominating-file-top-down (topmost match) only differed in two lines: the bottom-up version exits the walk on first match, the top-down version keeps walking and overwrites root at each level. Extract a shared projectile--locate-dominating-file with a first-match-only flag. The two public functions become one-liners that delegate. Loop semantics preserved - 277 tests still pass.
2026-04-26Add projectile-discard-root-cache command (#1936)Bozhidar Batsov
The existing projectile-invalidate-cache always tries to also clear the per-project files cache, which means it either prompts for a project (with prefix arg) or only operates when you're already in a project. Users who just created a marker file in a directory that Projectile previously considered rootless ended up picking an unrelated project from the prompt just to get the root cache flushed. projectile-discard-root-cache is a focused alternative that empties projectile-project-root-cache only. Wired into the menu next to the existing 'Invalidate cache' entry; deliberately not bound on the prefix map since the keymap is already dense.
2026-04-26Document the project root cache and marker semanticsBozhidar Batsov
- New 'Project root cache' subsection in projects.adoc covering invalidation, when negative results bite (the recurring 'I created .projectile and Projectile still doesn't see it' confusion from #1936), and how to reset programmatically. - Note in 'Customizing Project Detection' that projectile-root-top-down only matches regular files while projectile-root-bottom-up matches both - this difference is what forces VCS markers like .git onto the bottom-up list. - Expand the file-local override section with a concrete example and call out that overrides bypass the cache. - Rewrite projectile-project-root's docstring to mention caching, the tramp-archive unwrap, and the disconnected-remote behaviour that callers used to have to read the source to discover.
2026-04-26Add tests for previously untested root-detection pathsBozhidar Batsov
- projectile-root-marked: default and custom dirconfig file, miss case - projectile-root-local: returns the buffer-local var, ignores DIR - projectile-root-bottom-up: matches a regular-file marker (e.g. git worktree's .git file) - projectile-ensure-project: all three projectile-require-project-root branches (nil / t / 'prompt) plus the dir-non-nil pass-through - projectile-acquire-root: the success path and the no-root delegation - projectile-project-root: tramp-archive path unwrap, verified by observing the directory handed to a stub root function (a defvar-backed variable is needed because byte-compiled lexical closures don't propagate setq through cl-letf-mocked dispatch)
2026-04-26Memoize per-function nil results in the root cache (#1836)Bozhidar Batsov
Previously a root function returning nil stored nil in the cache, which is indistinguishable from a missing entry, so on the next call the function was re-run. When a project's root is found by the third function in projectile-project-root-functions, the first two re-walked the tree on every call - and projectile-project-root is hot in mode-line / company / spaceline updates. Store the symbol 'none for unsuccessful entries and treat it as a cache hit on lookup. The overall-failure marker already used 'none in a separate slot, so this just extends the same convention to the per-function entries.
2026-04-26Skip cache for projectile-root-local (#1211)Bozhidar Batsov
projectile-root-local reads the buffer-local projectile-project-root variable rather than inspecting its DIR argument, so caching its result by (FUNC . DIR) was wrong: two buffers visiting the same directory but with different file-local overrides would share the first buffer's cached answer. Skip the cache for this function specifically - it's a single variable read, no FS work to amortize. Adds a regression test that visits two buffers with different file-local roots from the same default-directory and confirms each sees its own override.
2026-04-26Use cons cells as projectile-project-root-cache keysBozhidar Batsov
Per-function entries are now keyed on (FUNC . DIR), and the overall failure marker on ('none . DIR), instead of formatted strings. Same equal semantics, no per-call format allocation, and lambda functions in projectile-project-root-functions hash by identity instead of by their printed repr.
2026-04-26Merge pull request #1995 from bbatsov/dirconfig-polishBozhidar Batsov
Polish: align dirconfig warnings, robust cache key, small cleanups
2026-04-25Small dirconfig cleanupsdirconfig-polishBozhidar Batsov
- projectile-get-project-directories: replace the (or keep '("")) trick with an explicit (if keep ... (list project-dir)). The behavior is unchanged but the empty-keep case no longer reads as a string-concat with the empty string. - projectile-dirconfig-file: expand the docstring to spell out the dual marker/config role — empty file is enough to mark a project, non-empty content drives parsing.
2026-04-25Include dirconfig path in the parse-cache hit checkBozhidar Batsov
The cache value used to be (MTIME . PARSED-RESULT) keyed on the project root. If the user changed `projectile-dirconfig-file' to point at a different file mid-session, the old entry kept getting returned because the key didn't depend on the path. Store the path alongside the mtime in the cached value and compare both before considering a hit. The hash key remains the project root so `projectile-invalidate-cache' continues to work as-is.
2026-04-25Fire the + glob keep warning once per projectBozhidar Batsov
The other two dirconfig warnings (alien-mode bypass and prefix-less entries) are gated by per-project hash sets so they fire at most once per Emacs session. The glob-keep warning was the odd one out — it lived inside the uncached parser and re-fired on every cache miss, and it emitted one display-warning per offending entry rather than a single consolidated message. Move it into the cached wrapper alongside the others, gate it on projectile--glob-keep-warned-projects, and roll multiple offending entries into one warning that lists them.
2026-04-26Merge pull request #1994 from bbatsov/dirconfig-refactorBozhidar Batsov
Refactor dirconfig parser: struct, pure classifier, deprecation warning
2026-04-25Soft-deprecate prefix-less dirconfig entriesdirconfig-refactorBozhidar Batsov
The implicit "any unprefixed line is an ignore pattern" rule is the last source of subtle parser surprises — it's the reason why a single leading space silently changes a +-keep into a literal ignore pattern, and it makes typo'd comments slip through as ignores. Mark these lines as :legacy-ignore in the classifier, record them in a new prefixless-ignore slot on the dirconfig struct, and emit a one-time warning per project listing the offending entries. The behavior is unchanged — the lines still go into the ignore list — but users now get a nudge to write them as -entry. The warning can be silenced via projectile-warn-on-prefixless-dirconfig-lines.
2026-04-25Split the dirconfig parser into a pure line classifierBozhidar Batsov
The old parser walked a temp buffer with point and pcase'd on char-after, mixing IO, prefix dispatch, and bucket bookkeeping into one function. Pull the dispatch out into projectile--dirconfig-classify-line, which takes a string and returns a (BUCKET . VALUE) tag. The pure function is unit-testable without buffer plumbing, the IO wrapper shrinks to a one-shot read + dispatch, and the awkward (pcase ((pred (lambda ...)) ...)) for the comment-prefix check becomes a straightforward cond. No behavior change.
2026-04-25Return a projectile-dirconfig struct from the parserBozhidar Batsov
Replace the positional (KEEP IGNORE ENSURE) triple with a cl-defstruct. Every internal call site used car/cadr/caddr to pull out a slot, which is unreadable and error-prone — slot accessors make the intent explicit and let cl-defstruct grow a fourth field later without touching every consumer. Existing callers that compared against the raw triple (a couple of internal helper tests) are updated to construct the struct with make-projectile-dirconfig.
2026-04-26Merge pull request #1993 from bbatsov/dirconfig-improvementsBozhidar Batsov
Improvements to .projectile (dirconfig) parsing and ergonomics
2026-04-25Warn when a + keep entry contains glob metacharactersdirconfig-improvementsBozhidar Batsov
The parser silently turns every keep entry into a directory via file-name-as-directory, which means a user-typed +*.json or +/foo.txt becomes "*.json/" or "foo.txt/" and quietly never matches anything. Spot the obvious misuses (anything containing *, ?, or [) at parse time and emit a warning so the user can correct the file or move the pattern to an ignore/ensure rule.
2026-04-25Add real-file integration tests for the dirconfig parserBozhidar Batsov
The existing parser tests stub insert-file-contents; nothing exercises the full IO path. Add three sandbox-based tests: a mixed +/-/!/no-prefix file, a non-ASCII round-trip, and a file with no trailing newline. These guard against IO-layer regressions that the stubbed tests can't see.
2026-04-25Warn when alien indexing bypasses a populated .projectileBozhidar Batsov
Under alien indexing the dirconfig file is silently ignored, which is the most common confusion in the issue tracker (#1322, #1075, #1534, #1941). Show a one-shot display-warning the first time we index a project where alien mode meets a non-empty .projectile. The new projectile-warn-when-dirconfig-is-ignored option lets users who already understand the trade-off silence the warning.
2026-04-25Document the dirconfig format more preciselyBozhidar Batsov
The parser docstring only described + and - prefixes even though the function returns a 3-tuple including the ! ensure bucket. Fill that in, and pull the path-vs-glob distinction out of the prose in projects.adoc into a subsection of its own — that ambiguity is at the root of recurring confusion (#740, #1109, #680, #1941).
2026-04-25Add tests for the dirconfig cacheBozhidar Batsov
The mtime-keyed cache around projectile-parse-dirconfig-file had no direct coverage. Cover the four invariants that matter: a cache hit on repeat calls, a re-parse when mtime advances, no caching of a nil result for a missing file, and entry removal via projectile-invalidate-cache (the path that previously crashed in #1854).
2026-04-25Skip leading whitespace in dirconfig prefix dispatchBozhidar Batsov
A user accidentally typing " -path/" or " # comment" in .projectile would have the line silently routed to the ignore bucket with the prefix character left intact, because the parser dispatched on the first character of the line without trimming. Skip leading spaces and tabs before the pcase so the +/-/! and comment-prefix markers are matched regardless of indentation. Reported in #1508.
2026-03-10Disambiguate process buffer names for same-named projectsBozhidar Batsov
Fixes #1815 Fixes #1715 When two projects share the same name (e.g. both named "src"), projectile-generate-process-name now detects the collision and falls back to using the abbreviated project path in the buffer name instead of just the project name.
2026-03-10Check globally-ignored-file-suffixes in native indexingBozhidar Batsov
Fixes #1959 projectile-ignored-file-p now checks projectile-globally-ignored-file-suffixes in addition to projectile-globally-ignored-files and file patterns. Previously the suffix check only happened in projectile-remove-ignored which is used by hybrid indexing but not native indexing.
2026-03-10Allow projectile-dired commands to prompt for a projectBozhidar Batsov
Closes #1684 With a prefix argument, projectile-dired, projectile-dired-other-window, and projectile-dired-other-frame now prompt for a known project to open in dired, rather than always using the current project.
2026-03-10Filter projectile-replace results through project ignore rulesBozhidar Batsov
Closes #1227 projectile-replace now intersects the files found by rg/ag/grep with the project's file list from projectile-dir-files, ensuring that files ignored via .projectile or other ignore rules are excluded from replacement. Previously the external search tool's results were used directly, which could include backup files and other ignored entries.
2026-03-10Add projectile-find-file-all to find files ignoring VCS rulesBozhidar Batsov
Closes #1001 Add a new command that lists all files under the project root using a generic file listing command (fd or find), bypassing .gitignore, .projectile, and other ignore mechanisms. This is useful when you need to find files that are normally excluded from the project.
2026-03-10test: add mercurial switch-project root regressionkovan
2026-03-05Fix compilation-find-file advice for dirs without direct filesBozhidar Batsov
Fixes #1923 When compilation output references a file via a relative path, the advice now also checks if the file exists relative to the project root and adds its parent directory to the search path. This handles the edge case where a directory only contains subdirectories (no files directly) and therefore was not included in the file-derived directory list from projectile-current-project-dirs.
2026-03-04Update CHANGELOG with round 4 codebase fixesBozhidar Batsov
2026-03-04Add tests for round 4 codebase fixesBozhidar Batsov
- Test projectile-ignored-project-p truename normalization - Test projectile-determine-find-tag-fn fallback behavior - Update projectile-files-via-ext-command test for empty string guard