Commit graph

1219 commits

Author SHA1 Message Date
Martin von Zweigbergk
721daef0b4 store: inline tree_builder() function to callers
`Store::tree_builder()` returns a `TreeBuilder`. Almost all callers
should be using the `MergedTreeBuilder` these days. This patch
therefore removes `tree_builder()` to reduce the risk of accidentally
using it.
2025-07-18 21:36:13 +00:00
Kaiyi Li
f1f1556731 local working copy: add support for EOL conversion 2025-07-17 15:36:28 +00:00
Kaiyi Li
74fb5a6096 working copy: pass UserSettings to WorkingCopyFactory
... so that later `TreeState` can query the EOL settings on
construction.
2025-07-17 15:36:28 +00:00
Martin von Zweigbergk
e982db8fd0 test_merged_tree: clear store caches before calling diff_stream()
This catches the bug introduced in 1b1edc7a90 (fixed in patch just
before this one).
2025-07-14 16:09:41 +00:00
Yuya Nishihara
84a456cc1f index: replace external users of revset_engine, make it private 2025-07-10 12:40:13 +00:00
Yuya Nishihara
e4f0942f26 index: replace external users of CompositeIndex 2025-07-10 12:40:13 +00:00
Yuya Nishihara
be094ef76e revset: don't resolve symbol expression to multiple revisions
It's surprising that a symbol expression may be resolved to multiple revisions,
and that's one of the reason we require all: modifier in some places. Let's make
a symbol resolution fail in that case so we can deprecate the all: syntax.

The new error hints are a bit less informative, but I don't want to implement
ad-hoc formatting for resolve_some_revsets_default_single(). The user will have
to review the graph anyway in order to resolve divergence/conflicts.

Closes #5632
2025-07-07 14:11:29 +00:00
Martin von Zweigbergk
3211e9c05c merged_tree: make diff_stream() sort foo before foo/bar
For most callers, the special sorting of directories before paths for
directory->file transitions is not needed. This patch changes
`diff_stream()` to not do that, and instead adds a new method
specifically for that behavior. Only `local_working_copy` uses it.
2025-07-04 01:12:18 +00:00
Martin von Zweigbergk
98af8dcf97 merged_tree: consider paths inside conflicted directories hidden
This makes `path_value()` and related functions more consistent with
how the merged tree is presented to the user in the working copy and
when showing a diff.
2025-07-04 01:12:18 +00:00
Yuya Nishihara
ea8aa1e17c index: don't preserve commits not referred to by operations/views
In this implementation, we assume that predecessor commits created by old jj are
reachable from at least one of the historical views. However, there are a couple
of commands which create transitive predecessors. For example, "jj squash" into
grandparent will rebase a rewritten source, so the pre-rebase source commit
won't be visible to any views. To work around the problem, all immediate
predecessors of historically visible commits are also preserved.

Note that this change should be considered forward-incompatible change. The
stored commits may have unreachable predecessors once we run "jj op abandon &&
jj util gc".

WalkPredecessors::flush_commits() doesn't need to guard against unreachable
commits. I was wondering whether values (or old ids) of op.commit_predecessors
map should be preserved, and I decided to keep both keys and values. It's nice
that we can get rid of index.has_id() calls when we drop support for legacy
commit.predecessors.
2025-07-03 09:06:21 +00:00
Yuya Nishihara
68ead52c5c hex_util: roll our own decode/encode_hex() functions
Since we have "reverse hex" functions, it's easy to implement the same set of
functions for "forward hex". I believe our implementation is slower than
highly-optimized versions provided by e.g. faster-hex, but we don't use hex
encoding/decoding where the performance matters.
2025-07-02 01:56:40 +00:00
Yuya Nishihara
f1b29510d3 object_id: rename HexPrefix::new() to ::try_from_hex() for consistency 2025-07-02 01:56:40 +00:00
Yuya Nishihara
8db40c7fa2 revset: add change_id/commit_id(prefix) predicates
Basically, these functions work in the same way as bookmarks()/tags(). They
restrict the namespace to search the specified symbol, and unmatched symbol
isn't an error. One major difference is that ambiguous prefix triggers an error.
That's because ambiguous prefix should logically select all matching entries,
whereas the underlying functions don't provide this behavior. It's also unclear
whether we would want to get all matching commits by commit_id(prefix:'').

#5632
2025-06-30 14:38:50 +00:00
Scott Taylor
bcde9ca728 revset: add parents(x, depth) and children(x, depth)
Resolves #3337.
2025-06-29 03:51:19 +00:00
Yuya Nishihara
8d846c864e workspace: leverage path_from/to_bytes() helper 2025-06-27 10:19:50 +00:00
Yuya Nishihara
782d9c29df git_backend: do not panic on non-UTF-8 Git repository paths 2025-06-27 10:19:50 +00:00
Yuya Nishihara
d5e3ee8819 cli: op diff: use predecessors information to pair old/new commits
This should produce better results at squash/split operations. Since "op diff"
targets can be flipped, this patch implements basic handling of reversed
predecessors graph. It should also work for sibling operations so long as there
are no multiple greatest common ancestors.

resolve_transitive_edges() takes additional "start" parameter. It might be
useful in order to omit transitive edges in evolution log. Since
walk_predecessors() usually tracks a single commit, it would be wasteful to
build a fully-resolved predecessors graph.
2025-06-27 00:48:06 +00:00
Yuya Nishihara
8f9e2b1697 op_walk: add roots..heads ancestors range iterator
There are a couple of hand-rolled implementations of roots..heads query, and I'm
going to add one more. It's wasteful to test these one by one.

The added iterator will have to scan roots::heads range eagerly instead of
::roots. I think this is good for typical use cases where roots are closer to
heads than the root().
2025-06-27 00:48:06 +00:00
Ilya Grigoriev
6323764f69 lib: add rustversion, turn off a clippy lint in nightly in tests
This turns off the `clippy::cloned_ref_to_slice_refs` lint in some tests
and fixes it in others, for Rust 1.89+. This seems to make `cargo clippy
--workspace --all-targets --all-features` work in stable, beta, and
nightly (1.89).

This depends on the `rustversion` crate. Other than that, it's based on
Austin's https://github.com/jj-vcs/jj/pull/6705.

Co-authored-by:  Austin Seipp <aseipp@pobox.com>
2025-06-24 01:01:25 +00:00
Yuya Nishihara
bc83eb0a48 id_prefix: check conflicting bookmarks/tags when calculating shortest length
Fixes #2416
2025-06-23 00:27:01 +00:00
Yuya Nishihara
d3e5f9d827 tests: add proptest that compares optimized revset output
Only the expression trees are generated, which I just needed to copy the doc
example. The commit DAG could also be generated, but doing that would mean the
commits would have to be created within a proptest loop, I think, and would make
shrinking process super slow.
2025-06-22 01:55:50 +00:00
Yuya Nishihara
68c23c3ccc revset: remove SymbolResolver abstraction, rename DefaultSymbolResolver
Since the extension point is now provided by SymbolResolverExtension, this
abstraction isn't useful anymore. This change will probably help implement
commit/change_id(pattern) functions. If the resolver were abstract type, we
would have to add resolve_commit/change_id() methods separately.
2025-06-22 00:48:10 +00:00
Yuya Nishihara
3451f468bb revset: remove FailingSymbolResolver, use DefaultSymbolResolver in tests
Since UserRevsetExpression is now a different type than the resolved one, we no
longer have to resolve internal revset by using the failing resolver.
2025-06-22 00:48:10 +00:00
Yuya Nishihara
360023bcff tests: extract helper to create DefaultSymbolResolver without extensions
I also removed resolve_symbol_with_extensions() as there are no callers who pass
in non-empty extensions.
2025-06-22 00:48:10 +00:00
Yuya Nishihara
123088b8e4 revset: expand all() set to include all referenced commits
The semantics is similar to experimental.directaccess=true in Mercurial. Hidden
revisions and their ancestors become temporarily available. This means all() is
not exactly the same as ::visible_heads(). The latter never includes hidden
revisions.

We could instead transform all() to (all() | referenced_commits). However, this
wouldn't work if expressions like ::hidden are intersected/united with filters,
all(), etc.

Fixes #5871
2025-06-19 11:21:49 +00:00
Yuya Nishihara
43668934df revset: add intersection node to backend filter predicates
This was originally suggested by Scott Taylor in #6679. I thought filter
intersections would have been externalized to the set intersections, but that
was wrong. Since union of filter intersections (e.g. `(f1 | f2 & f3) & s4`) is
evaluated as `filter_within(s4, f1 | f2 & f3)`, it doesn't make sense to convert
`f2 & f3` back to set intersection `filter_within(all(), f2) & f3`.
2025-06-15 14:45:21 +00:00
Yuya Nishihara
b0f3c98175 cli: evolog: group graph nodes topologically
The output looks better if the graph had long parallel history. "--limit=N" is
applied after sorting for consistency with "jj log". The doc also mentions that.

Since TopoGroupedGraphIterator emits predecessors in reverse order at squash
point, we no longer need to tweak the visiting order by walk_predecessors().
2025-06-15 01:59:33 +00:00
Yuya Nishihara
5d5962b67e evolution: fix cycle detection and graph sorting of transitive predecessors
This shouldn't usually happen, but we can craft unsimplified evolution history
by rewriting hidden commits, for example.
2025-06-14 23:54:12 +00:00
Scott Taylor
58adbc0f60 revset: optimize heads of a range with filter
Resolves #6656.

Currently, evaluating an expression like `heads(::@ ~ empty())` requires
iterating over all commits in `::@` and checking the predicate against
each commit, which can be very slow. This PR implements an optimization
to convert expressions to the form `heads(x..y & filter)` when it is
possible to do so efficiently. Expressions of this form can be evaluated
using a specialized algorithm which terminates iteration early without
checking all commits.

Benchmark results on Git repo:

```
revsets/heads(author(peff))     97.248% improvement
revsets/heads(::v2.40.0)        98.467% improvement
```
2025-06-14 15:55:23 +00:00
Yuya Nishihara
523b9132c8 revset: remove redundant intersection/union with none()
Since we now have separate stage to resolve user symbols, we can simply rewrite
expressions like `x & none()` to `none()`.
2025-06-10 22:47:07 +00:00
Martin von Zweigbergk
5ae5f9f75d async: avoid some async blocks by making whole functions async
This reduces indentation and makes it easier to add more async calls
to the functions.
2025-06-10 20:19:47 +00:00
Yuya Nishihara
0dc9eb5187 commit_builder: error out if newly-created commit was already known to repo
This can happen if we remove predecessors from the commit object, and if
committer timestamp wasn't updated (like in some jj-lib tests.) It's not wrong
for the store to "overwrite" existing objects, but the repo expects
new/rewritten commits have unique ids. If they didn't, cycle would be created in
mut_repo.commit_predecessors/parent_mapping.
2025-06-05 10:15:09 +00:00
Martin von Zweigbergk
a363e6b1e6 backend: add CopyId to TreeValue::File
This patch adds a `TreeValue::File::copy_id` field. The copy ids are
always empty for now. I preserved the copy id where it was easy to do
so, plus in a few non-trivial cases. In other places, however, I made
the code use a new copy id.  I added a `CopyId::placeholder()`
function for creating a new copy id where we need one. We should
eventually fix all callers to either preserve an existing copy or to
generate a new one.
2025-06-03 01:11:32 +00:00
Jonas Greitemann
9c165d6db3 tests: supplement create_tree() with a builder-style API
The original form of `create_tree()` is limited to creating (valid
UTF-8) text files but cannot create binary files, executable
files, or symlinks. Dedicated helpers like `write_executable_file()` or
`write_symlink()` partially compensated for this, but required manually
assembling the tree in the test code.

This commit introduces `TestTreeBuilder` which provides an API to
successively add entries to a tree which can represent all of the above.
`TestTreeBuilder` can then create either a single `Tree`, or a resolved
`MergedTree`.

In addition to using `TestTreeBuilder` directly, `create_tree_with()`
and `create_single_tree_with()` accept a closure which receives a
`TestTreeBuilder`. This allows test code to quickly describe the tree
without requiring the a named builder at caller scope. Riffing off
the familiar function names should help in discovering the new builder
facilities. However, it is completely possible to use `TestTreeBuilder`
directly, if preferred.
2025-06-02 17:40:26 +00:00
Yuya Nishihara
6c0cd68cf1 str_util: parse regex-i:pattern as case-insensitive regex
Just for completeness. Case-insensitive search can be achieved by (?i), but doc
and hint suggest that the -i suffix works for all pattern types.

Closes #6653
2025-06-01 00:25:56 +00:00
Yuya Nishihara
a44c413d54 commit: remove commit.predecessor*() accessors
Now we should use the evolution API which queries op.commit_predecessors first.
2025-05-27 23:43:59 +00:00
Yuya Nishihara
60f8bdda7a evolution: walk operations to build predecessors graph
This is slower than traversing commit.predecessor_ids, but allows us to show
associated operations alongside the commits. Since operations are (mostly)
traversed in chronological order, the evolution graph is also emitted in that
order.
2025-05-27 13:28:09 +00:00
Yuya Nishihara
9612347497 op_store: load/store commit predecessors
Operation ids in test_op_abandon*() are changed because reparented operations
now preserve the predecessors mapping.

Since this patch will affect whether "jj evolog" can show the associated
operations, I added a changelog entry. It's still a bit vague, but I think we
can add a more detailed explanation later.
2025-05-26 10:23:29 +00:00
Yuya Nishihara
42a2a2dd95 repo: store commit predecessors in Operation object
There are two major goals:
1. garbage-collect predecessor commits referenced by immutable commits.
2. show operations alongside predecessors in "jj evolog".

The predecessors field will be removed from the Commit object. Maybe we can
also remove (writer side of) the extras table from GitBackend.

"jj evolog" will traverse the operation history to build an evolution
graph. This will be slower than the current implementation, but it seems
tolerable for mid-size repository stored in a local disk.

    (when the page cache is warm)
    % time jj op log -T'"."' --no-graph | wc -c
    50459
    jj op log -T'"."' --no-graph  0.12s user 0.31s system 103% cpu 0.418 total

Suppose we're interested in recent modifications, the traversal can often be
terminate early. I also have an idea for indexing originating operations.
https://github.com/jj-vcs/jj/pull/6405

For old operations which didn't record predecessors, "jj evolog" will fall back
to commit.predecessor_ids(). That's why commit_predecessors is Option<_>.
2025-05-26 10:23:29 +00:00
Vincent Ging Ho Yim
763be4e4c5 lib: return correct op set subexpression when reporting multiple operations found 2025-05-20 02:39:48 +00:00
Vincent Ging Ho Yim
55f2b1bb52 test: demonstrate that OpsetResolutionError::MultipleOperations reports incorrect intermediate expression
The `expr` and `candidates` values do not match, because currently `expr` is always the
full op set expression being resolved.
2025-05-20 02:39:48 +00:00
Vincent Ging Ho Yim
f113e28f7b test: extract helper function to assert the contents of OpsetResolutionError::MultipleOperations 2025-05-20 02:39:48 +00:00
Vincent Ging Ho Yim
3bb9f11dd5 test: add assertions for contents of OpsetResolutionError::MultipleOperations
We don't want to derive `PartialEq` on `OpsetResolutionError` because we may need an
`Other` variant in the future that wraps a type without a `PartialEq` implementation.
Pattern-matching is used instead to assert the contents.
2025-05-20 02:39:48 +00:00
Scott Taylor
c406b3a929 rebase: skip duplicate divergent commits by default
This serves a similar purpose to Git's patch ID mechanism, however it is
slightly different in that it only compares commits which have the same
change ID as each other (divergent changes), and it does a full
comparison of the commits to see if they would have identical trees if
rebased onto the same parents. Since most changes aren't divergent, I
believe this should have a negligible performance cost in most cases.

I think skipping these commits by default makes sense for `jj rebase`,
since usually this will be a helpful behavior for the user. With this
behavior, a safe first step when encountering divergent changes would be
to rebase one branch on top of the other, since that will abandon any
divergent changes that have identical contents to existing commits,
leaving behind any non-trivial divergent changes for the user to resolve
manually.
2025-05-19 22:23:32 +00:00
Martin von Zweigbergk
84f188b91e tests: leverage tests_utils::read_file() 2025-05-18 02:45:43 +00:00
Benjamin Tan
194ee8761c tests: gpg: reduce length of temporary homedir path
Apparently `gpg-agent` will fail to start if the `--homedir` path is too
long:

```
> gpg-agent --homedir=/private/tmp/nix-build-jujutsu-0.29.0-unstable-8769c5c.drv-0/jj-gpg-signing-test-4I0aI0/ --daemon
gpg-agent[6617]: directory '/private/tmp/nix-build-jujutsu-0.29.0-unstable-8769c5c.drv-0/jj-gpg-signing-test-4I0aI0/private-keys-v1.d' created
gpg-agent[6617]: socket name '/private/tmp/nix-build-jujutsu-0.29.0-unstable-8769c5c.drv-0/jj-gpg-signing-test-4I0aI0/S.gpg-agent.extra' is too long
```

I only discovered this after reading a comment on StackOverflow [0].

[0]: https://superuser.com/questions/1087554/when-adding-a-gpg-key-with-homedir-parameter-error#comment1684440_1130507
2025-05-11 13:13:26 +00:00
Martin von Zweigbergk
acd824269f git: write change-id header by default
We haven't had any reports of problems from people who opted in. Since
it's early in the release cycle now, let's now test it on everyone who
builds from head, so we get almost a month of testing from those
people before it's enabled by default in a released version.

This impacts lots of test cases because the change-id header is added
to the Git commit. Most are uninteresting. `test_git_fetch` now sees
some divergent changes where it used to see only divergent bookmarks,
which makes sense.
2025-05-08 23:05:38 +00:00
Martin von Zweigbergk
ed8dcc82b3 revset: make test a little easier to update if commit id changes
This should make it easier to find the right magic numbers to produce
commit ids with the same prefix.
2025-05-08 23:05:38 +00:00
Martin von Zweigbergk
2c39ba316b revset: use simpler constants for arbitrary numbers
AFAICT, it only matters that one of the numbers results in a commit ID
that starts with "040".
2025-05-08 23:05:38 +00:00
Emily
4f6e79a22a git: remove unused parameter from GitFetch::get_default_branch 2025-05-07 19:29:20 +00:00