core
New features
-
New
:select[F]filter as the complement of:exclude[F](#2250)
:select[F]keeps only the paths present in F's result. It's the exact set-complement of
:exclude[F]. -
The insertion filter is no longer experimental (#2246)
:$path=<content>no longer requiresJOSH_EXPERIMENTAL_FEATURES=1.:$path=<oid>now
also accepts a tree oid, inserting a whole subtree (#2231), and:$.=<oid>replaces the
whole output tree instead of silently producing an empty one (#2251). -
history="no-splice"flag to suppress synthetic splice merges (#2209)
Thehistorymeta value now accepts a comma-separated list of flags;no-splicedrops
the synthetic merge parents that splice in the prior history of paths made visible by a
filter change.
Breaking changes
:pincan no longer be combined with history splicing (#2224)
The combination is undefined, so josh now errors out instead of producing an ill-defined
result. Filters using:pinmust sethistory="no-splice".
Bug fixes
-
Correct
:rev/:hookreversal per commit (#2240)
Reversing:rev(...)or:hookwent through the generic invert path, breaking roundtrips.
They are now reversed per commit like stored filters. -
Insert-filter oids are reachable from persisted filter trees (#2259)
The oid of an:$path=<oid>insert was persisted as a hex-string blob, so the object
could be missing when the filter tree is transferred. It is now a real tree entry, and
distributed cache refs are named after the persisted id. -
Cache backend errors no longer crash the process (#2261)
Transaction cache lookups turned any backend error into a panic viaexpect(); errors
now propagate asResultthrough the already-fallible callers. -
Removals of whole subtrees show up in tree diffs again (#2297)
A dead branch indiff_pathsmeant deleting an entire subdirectory reported no removed
paths, affecting downstack dependency tracking and the GraphQLchangedFilesdiff. -
Composing insertion filters with shared directory prefixes no longer drops entries (#2232, #2233, #2268)
A filter like:[:$x/a="1",:$x/b="2",:$y/a="3",:$y/b="4"]dropped every group but the
first.
Performance
-
Pack filtered objects through an in-memory ODB backend (#2212, #2217, #2295, #2294)
Loose-object writes dominated large rewrites; objects now buffer in a per-transaction
in-memory libgit2 ODB backend and are flushed to packfiles, bounded by a memory limit
(128 MiB by default).
Packfile writing is handed to a process-global background worker so the filter hot path
keeps running, with a blocking drain at transaction and subprocess boundaries. -
Leaner distributed cache storage format (#2293, #2260)
Cache entries are now gitlinks in a two-level fanout instead of hex-text blobs over three
levels, and cache commits no longer embed the filter tree. -
Distributed cache eligibility checks no longer read the ODB (#2284)
The parent count is encoded in the cached history-graph hint instead of read from the ODB
per query. Also fixes dropped entries for intermediate commits of chained filter specs. -
The distributed cache backend is read-only by default (#2296)
Plain clones and fetches no longer append flush commits that grow the shard chains;
josh cache buildremains the producer path. -
Cache 3-way merge results during downstacking (#2168)
The merged tree is a pure function of its three input trees, so results are memoized on
the transaction, collapsing identical merges across a stack into hashmap hits. -
Faster downstacking via a dedicated filter op (#2107, #2169, #2167)
Downstacking is now a filter op, written:_=<sha>(experimental), so results
participate in the filter cache; dependencies are computed by a direct tree walk,
memoized per transaction. -
Intern filter ops into hash-consed nodes (#2199)
The optimizer deep-cloned ops and re-serialized them to compute OIDs; ops are now
interned into hash-consed nodes that cache their OID. -
Skip futile trailing-Compose distribution in the optimizer (#2223)
Distributing a trailing compose out of a chain was immediately re-merged by a later step —
an O(files × pins) blow-up on wide pin filters. Hot paths now skip it. -
Disable libgit2 strict object creation (#2191)
Josh only writes objects whose referenced objects it has just produced or read, so
libgit2's per-write existence verification is pure overhead and is now turned off. -
Keep
git_odb__freshenoff the filter write path (#2202)
libgit2 stats/touches loose objects on every write to refresh mtimes against gc, which
josh never relies on during filtering; the in-memory backend now bypasses it. -
Build tree results with a single tree builder (#2254)
subtract,intersect,pathstreeand other tree operations re-seeded and wrote a
TreeBuilderper entry — quadratic on wide trees. Each now collects its children into a
single builder and writes the tree once. -
Skip reading the roots blob when only the sequence number is needed (#2257)
The cached hint carries the sequence number directly, skipping a reachable-roots blob
read per input commit. -
Build pin overlay with a single tree intersect (#2249)
The:pinstep that re-contents pinned paths from the parent's filtered tree is now a
single path-intersection pass usingtree::intersect.
josh-cli
New features
-
Wrap a pushed commit in a merge with
josh push --merge(#2206)
The CLI equivalent ofgit push -o merge: the reverse-filtered commit is wrapped in a
two-parent merge on top of the base, recording an explicit integration commit. Requires
--base=<ref>or an existing destination ref. -
Reparent orphan commits onto
--basewhen pushing (#2207)
Unrelated (orphan) history is overlaid onto the base instead of pushed as a disconnected
root, matching whatjosh-proxydoes forgit push -o base=<ref>. -
Global
--no-distributed-cacheflag (#2278)
Disables the distributed cache for a command: filtering neither reads nor writes
refs/josh/cache/*.
Bug fixes
- Semantic
:~()meta args of remote filters take effect
A remote filter like:~(history="keep-trivial-merges")[:/sub1]had its meta args ignored
when filtering.
josh-proxy
Improvements
- Better diagnostics in the GraphQL proxy's ref fetching (#2228)
The confusing "ref query not allowed" error shown when a remote did not return a
requested OID is replaced with a contextualized message.