<feed xmlns='http://www.w3.org/2005/Atom'>
<title>projectile.git/projectile.el, branch master</title>
<subtitle>Unnamed repository; edit this file 'description' to name the repository.
</subtitle>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/'/>
<entry>
<title>Coalesce idle-timer flushes in projectile-cache-current-file</title>
<updated>2026-04-27T07:58:28+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-27T07:58:28+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=28014dae7034fff49f5740b013a426aade7873f3'/>
<id>28014dae7034fff49f5740b013a426aade7873f3</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Delete cache file in projectile-invalidate-cache</title>
<updated>2026-04-27T07:50:40+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-27T07:50:40+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=d8bbeedd275cea4765555c731b265ce7757ad9e5'/>
<id>d8bbeedd275cea4765555c731b265ce7757ad9e5</id>
<content type='text'>
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).
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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).
</pre>
</div>
</content>
</entry>
<entry>
<title>Persist disk cache in projectile-purge-dir-from-cache</title>
<updated>2026-04-27T07:48:07+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-27T07:48:07+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=fc8559850228f54efbc7ee4ea56b2e376cec5b04'/>
<id>fc8559850228f54efbc7ee4ea56b2e376cec5b04</id>
<content type='text'>
`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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
`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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Seed cache time when loading the project file cache from disk</title>
<updated>2026-04-27T01:27:35+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-27T01:27:35+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=a55eeb2b1821c5252a5ea3d8c7c79728a42763fe'/>
<id>a55eeb2b1821c5252a5ea3d8c7c79728a42763fe</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Skip git submodule scan when there is no .gitmodules</title>
<updated>2026-04-26T11:29:52+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-26T11:29:52+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=ccd8052beb84a889565ffd08a58cf643e2e439f3'/>
<id>ccd8052beb84a889565ffd08a58cf643e2e439f3</id>
<content type='text'>
`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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
`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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Speed up native + hybrid indexing's ignore filtering</title>
<updated>2026-04-26T11:12:54+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-26T11:12:54+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=a2e2a751885291ef9ec88aec7551e2b98a9b17a8'/>
<id>a2e2a751885291ef9ec88aec7551e2b98a9b17a8</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Batch hybrid indexing across `+' keep dirconfig entries</title>
<updated>2026-04-26T10:34:35+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-26T10:34:35+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=c10a86b74749b23fe1231915cad08163eb3038e1'/>
<id>c10a86b74749b23fe1231915cad08163eb3038e1</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Avoid recomputing VCS in projectile-dir-files-alien</title>
<updated>2026-04-26T10:17:16+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-26T10:17:16+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=1ed5e991582a07688d794a03fe648d71afb53771'/>
<id>1ed5e991582a07688d794a03fe648d71afb53771</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Share walk skeleton between locate-dominating-file helpers</title>
<updated>2026-04-26T06:50:16+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-26T06:50:16+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=5e66d26c45c8150abb17d40d0977b1fa20cc4bf8'/>
<id>5e66d26c45c8150abb17d40d0977b1fa20cc4bf8</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
<entry>
<title>Add projectile-discard-root-cache command (#1936)</title>
<updated>2026-04-26T06:49:03+00:00</updated>
<author>
<name>Bozhidar Batsov</name>
<email>bozhidar@toptal.com</email>
</author>
<published>2026-04-26T06:49:03+00:00</published>
<link rel='alternate' type='text/html' href='http://git.tews.dev/cgit/projectile.git/commit/?id=66b54e9038c8aadf5a515758ca3c0acb03977b0e'/>
<id>66b54e9038c8aadf5a515758ca3c0acb03977b0e</id>
<content type='text'>
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.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
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.
</pre>
</div>
</content>
</entry>
</feed>
