Commit graph

4385 commits

Author SHA1 Message Date
Martin von Zweigbergk
073a1dea74 rewrite: make CommitRewriter::rebase() async 2025-08-06 03:12:05 +00:00
Martin von Zweigbergk
82d7182ef7 repo: take async callback to transform_descendants()
Maybe we can make `transform_descendants()` transform siblings
concurrently later.
2025-08-06 03:12:05 +00:00
Martin von Zweigbergk
6a7c0fb5ea merged_tree: respect Backend::concurrency() in merge_trees() 2025-08-05 14:29:57 +00:00
Martin von Zweigbergk
b14fad00be merged_tree: rewrite merge_trees() to be non-recursive and concurrent
We ran into a stack overflow in `merge_trees()` at Google due to
limited stack space and large stack frames caused by async code. This
patch fixes that by making `merge_trees()` non-recursive. Since I was
rewriting the algortithm anyway, I also made it concurrent, addressing
a TODO.
2025-08-05 14:29:57 +00:00
Martin von Zweigbergk
ca839f9d50 merged_tree: separate out trivially resolved entries when resolving
The goal of this change is to have the cheap, non-async code separated
from the async code.
2025-08-05 14:29:57 +00:00
Martin von Zweigbergk
2e249babe0 merged_tree: move construction of backend trees onto new type
Once we make the tree-merging concurrent, we are going to keep once
instance of this type per unfinished directory merge.
2025-08-05 14:29:57 +00:00
Martin von Zweigbergk
97ee86d513 merged_tree: build conflicted resolved trees from sorted conflicts
This addresses the TODO about creating the full tree entries by
merging the sorted list of conflicts with (one side of) the sorted
list of conflicts.
2025-08-05 14:29:57 +00:00
Martin von Zweigbergk
38bd4348c5 tests: slightly clarify a comment 2025-08-05 14:29:57 +00:00
Yuya Nishihara
1447791a74 revset_graph: use Rc to propagate edges without cloning
This saves memory usage.

In small repo:
- jj-1: with original remove_transitive_edges()
- jj-4: previous patch
- jj-5: this patch
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/git --ignore-working-copy log -r "tags()"'
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      1.511 s ±  0.091 s    [User: 1.296 s, System: 0.214 s]
  Range (min … max):    1.432 s …  1.674 s    10 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      1.142 s ±  0.055 s    [User: 1.043 s, System: 0.099 s]
  Range (min … max):    1.106 s …  1.287 s    10 runs

Benchmark 5: target/release-with-debug/jj-5 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      1.201 s ±  0.082 s    [User: 1.101 s, System: 0.100 s]
  Range (min … max):    1.095 s …  1.299 s    10 runs

Relative speed comparison
        1.32 ±  0.10  target/release-with-debug/jj-1 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
        1.00          target/release-with-debug/jj-4 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
        1.05 ±  0.09  target/release-with-debug/jj-5 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
```

In mid-size repo:
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"'
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     937.7 ms ±  68.8 ms    [User: 672.1 ms, System: 265.4 ms]
  Range (min … max):   868.3 ms … 1025.4 ms    10 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     979.9 ms ±  65.4 ms    [User: 802.6 ms, System: 177.2 ms]
  Range (min … max):   905.6 ms … 1055.0 ms    10 runs

Benchmark 5: target/release-with-debug/jj-5 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     879.4 ms ±  62.1 ms    [User: 754.0 ms, System: 125.4 ms]
  Range (min … max):   823.1 ms … 960.4 ms    10 runs

Relative speed comparison
        1.07 ±  0.11  target/release-with-debug/jj-1 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
        1.11 ±  0.11  target/release-with-debug/jj-4 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
        1.00          target/release-with-debug/jj-5 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
```
2025-08-05 01:31:32 +00:00
Yuya Nishihara
d8166b46f0 revset_graph: remove transitive edges of both internal and external commits
With changed-path index, I noticed "jj log PATH" in Linux repo freezes because
of out of memory. The problem can be mitigated by sharing edges values by Rc,
but the computation is still slow. It seems better to keep edges to propagate
small.

In small repo:
- jj-1: with original remove_transitive_edges()
- jj-4: this patch
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/git --ignore-working-copy log -r "tags()"'
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      1.511 s ±  0.091 s    [User: 1.296 s, System: 0.214 s]
  Range (min … max):    1.432 s …  1.674 s    10 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      1.142 s ±  0.055 s    [User: 1.043 s, System: 0.099 s]
  Range (min … max):    1.106 s …  1.287 s    10 runs

Relative speed comparison
        1.32 ±  0.10  target/release-with-debug/jj-1 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
        1.00          target/release-with-debug/jj-4 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
```

In mid-size repo:
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"'
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     937.7 ms ±  68.8 ms    [User: 672.1 ms, System: 265.4 ms]
  Range (min … max):   868.3 ms … 1025.4 ms    10 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     979.9 ms ±  65.4 ms    [User: 802.6 ms, System: 177.2 ms]
  Range (min … max):   905.6 ms … 1055.0 ms    10 runs

Relative speed comparison
        1.07 ±  0.11  target/release-with-debug/jj-1 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
        1.11 ±  0.11  target/release-with-debug/jj-4 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
```
2025-08-05 01:31:32 +00:00
Yuya Nishihara
a523657525 revset_graph: add fast path for linear ranges
This patch itself isn't significant win, but will help us optimize both CPU and
memory use.

In small repo:
- jj-2: previous patch
- jj-3: this patch
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/git --ignore-working-copy log -r "tags()"'
Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      2.467 s ±  0.142 s    [User: 2.271 s, System: 0.196 s]
  Range (min … max):    2.246 s …  2.588 s    10 runs

Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      2.254 s ±  0.169 s    [User: 2.070 s, System: 0.184 s]
  Range (min … max):    2.055 s …  2.395 s    10 runs

Relative speed comparison
        2.16 ±  0.16  target/release-with-debug/jj-2 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
        1.97 ±  0.18  target/release-with-debug/jj-3 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
```

In mid-size repo:
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"'
Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):      1.185 s ±  0.066 s    [User: 0.920 s, System: 0.265 s]
  Range (min … max):    1.132 s …  1.310 s    10 runs

Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     909.7 ms ±  46.7 ms    [User: 683.6 ms, System: 226.1 ms]
  Range (min … max):   868.3 ms … 997.3 ms    10 runs

Relative speed comparison
        1.35 ±  0.12  target/release-with-debug/jj-2 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
        1.03 ±  0.09  target/release-with-debug/jj-3 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
```
2025-08-05 01:31:32 +00:00
Yuya Nishihara
b9cbfdedb8 revset_graph: make remove_transitive_edges() not recurse into edges_from*()
I'll make RevsetGraphWalk eliminate transitive edges from intermediate edges to
mitigate out-of-memory issue. This means that edges_from*() functions will call
remove_transitive_edges(). Maybe we could rewrite the whole process to not use
machine stack, but doing that would be complicated. The process wouldn't be
trivially-deterministic on where the look-ahead can terminate either.

The performance problem introduced by this patch will be fixed by later patches.

In small repo:
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/git --ignore-working-copy log -r "tags()"'
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      1.511 s ±  0.091 s    [User: 1.296 s, System: 0.214 s]
  Range (min … max):    1.432 s …  1.674 s    10 runs

Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
  Time (mean ± σ):      2.467 s ±  0.142 s    [User: 2.271 s, System: 0.196 s]
  Range (min … max):    2.246 s …  2.588 s    10 runs

Relative speed comparison
        1.32 ±  0.10  target/release-with-debug/jj-1 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
        2.16 ±  0.16  target/release-with-debug/jj-2 -R ~/mirrors/git --ignore-working-copy log -r "tags()"
```

In mid-size repo:
```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-1,jj-2,jj-3,jj-4,jj-5 \
  'target/release-with-debug/{bin} -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"'
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):     937.7 ms ±  68.8 ms    [User: 672.1 ms, System: 265.4 ms]
  Range (min … max):   868.3 ms … 1025.4 ms    10 runs

Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
  Time (mean ± σ):      1.185 s ±  0.066 s    [User: 0.920 s, System: 0.265 s]
  Range (min … max):    1.132 s …  1.310 s    10 runs

Relative speed comparison
        1.07 ±  0.11  target/release-with-debug/jj-1 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
        1.35 ±  0.12  target/release-with-debug/jj-2 -R ~/mirrors/linux --ignore-working-copy log -r "tags(v5)"
```
2025-08-05 01:31:32 +00:00
Yuya Nishihara
b2d109634c revset_graph: make remove_transitive_edges() not visit uninteresting ancestors
This is similar to "entry.generation_number() < min_generation", but is cheaper
to test.
2025-08-04 11:20:49 +00:00
Yuya Nishihara
f386e9fa29 revset_graph: omit missing edges early in remove_transitive_edges()
We aren't interested in edge types here.
2025-08-04 11:20:49 +00:00
Yuya Nishihara
ad7c42e04b revset_graph: ignore missing edges thoroughly in remove_transitive_edges() 2025-08-04 11:20:49 +00:00
Yuya Nishihara
13a2bbe1b8 graph: add GraphEdge::is_<edge_type>() for convenience 2025-08-04 11:20:49 +00:00
Theo Buehler
525e795889 git: avoid division by zero in overall progress
When no progress has been made, indicate that as 0.0 progress rather
than dividing by zero. Avoids in particular a display issue where the
progress bar indicates NaN% progress when fetching from slow remotes.

Fixes #7155

Change-Id: I6a6a6964d6572d1c98b5fa25285d26c07ee27a40
2025-08-03 13:48:12 +00:00
Scott Taylor
c5b0b0b68f revset: insert HeadsRange in Ancestors for nested range/filter
This allows evaluating ancestors/ranges involving filters significantly
faster. For instance, naively evaluating `::mine()` requires reading
every commit in the repo to check `mine()` on each commit, then finding
the ancestors of that set. This optimization rewrites `::mine()` to
`::heads(mine())`, since `heads(mine())` can be evaluated more
efficiently by only reading commits until the first successful match.

If someone is unaware of how revsets are implemented, this case can come
up pretty easily, such as by including `~mine()` in `immutable_heads()`
to make other people's commits immutable. In my local `jj` repo, this
optimization reduces the runtime of `jj log` with `~mine()` in
`immutable_heads()` from about 800ms down to about 50ms.

Benchmark results on Git repo:

```
::(v1.0.0..v2.40.0)     55.5% faster
author(peff)..          96.8% faster
```
2025-08-02 01:40:23 +00:00
Scott Taylor
a60b2c6025 revset: extract to_heads_range() function 2025-08-02 01:40:23 +00:00
Scott Taylor
f9476426d5 revset: fold ancestor unions before filters
Folding ancestor unions creates new unions, and the filter pass lifts
`AsFilter` through unions, so this order allows better optimizations.
2025-08-02 01:40:23 +00:00
Yuya Nishihara
756078803a index: add segment type field to ReadonlyIndexLoadError
ReadonlyIndexLoadError could be a wrapper of PathError instead, but that isn't
compatible with the abstraction of the current load functions. The inner load
functions may be called with an in-memory buffer.
2025-08-02 01:27:55 +00:00
Yuya Nishihara
dbb660acf0 index: attach file path to DefaultIndexStoreError 2025-08-02 01:27:55 +00:00
Yuya Nishihara
d1aab57902 index: inline squash_and_save_in() into DefaultIndexStore
As I'm going to add two separate commit/changed-path segments directories, it's
easier to make DefaultIndexStore handle directory layout.
2025-08-01 01:12:32 +00:00
Yuya Nishihara
345dec5f9c index: rename segments_dir() to commit_segments_dir()
I'll add another directory for changed-path index. We could put them into the
same (content-addressed) directory, but that would just make debugging harder.
2025-08-01 01:12:32 +00:00
Yuya Nishihara
53f85e84e5 index: reimplement stats() to not depend on CompositeCommitIndex abstraction
This will help extend stats() for changed-paths index. Since there are no
non-test callers who need to calculate stats of mutable index, it should be good
to move the implementation to DefaultReadonlyIndex.
2025-08-01 01:12:32 +00:00
Yuya Nishihara
1a51f1a4fe index: let MutableIndex::add_commit() return error
DefaultMutableIndex will calculate diffs to index changed files if enabled. The
body of DefaultMutableIndex::add_commit() is moved to non-trait method. It will
become an async function.
2025-08-01 01:12:32 +00:00
Martin von Zweigbergk
4df2b5c008 merged_tree: write trees concurrently for single dir 2025-08-01 00:55:35 +00:00
Martin von Zweigbergk
cc335112c7 merged_tree: move writing of trees to end of merge_trees()
I'm slowly working to separate the async code from the synchronous
code so we can do the async stuff concurrently. This refactoring is
part of that.
2025-08-01 00:55:35 +00:00
Martin von Zweigbergk
f96ac352a7 merged_tree: rename new_tree_id which is a full tree (not an ID) 2025-08-01 00:55:35 +00:00
Scott Taylor
760ca1525b revset: add first_parent() function
Resolves #4579.
2025-07-31 22:17:05 +00:00
Kaiyi Li
7f88b13bfc eol: convert the EOL for conflict on update 2025-07-31 15:04:34 +00:00
Martin von Zweigbergk
28562f1b10 tests: remove CommitGraphBuilder
The `CommitGraphBuilder` type doesn't seem to carry its weight
anymore.
2025-07-31 04:56:34 +00:00
Martin von Zweigbergk
ca6edfaab0 tests: add a helper for writing random commit with given parents 2025-07-31 04:56:34 +00:00
Martin von Zweigbergk
592df6a28b tests: leverage write_random_commit() in a few more places 2025-07-31 04:56:34 +00:00
Yuya Nishihara
1fc8b97924 tests: add comment about PROPTEST_CASES=<N>
With the current test samples, it seems we need to run ~1000 cases to catch
`heads(first_ancestors())` bug that existed in the original PR. This parameter
can be set by default, but I don't have a good idea about reasonable "cases"
value, prop_recursive() parameters, etc.
2025-07-31 00:49:31 +00:00
Yuya Nishihara
ad387bf274 tests: add sample of feature branches to revset optimization proptest 2025-07-31 00:49:31 +00:00
Scott Taylor
3cc5b81f24 revset: don't reorder ~first_ancestors(x) to start
Negated `first_ancestors()` can't be converted to a range currently, so
it doesn't make sense to move it to the start when optimizing the order
of an intersection.
2025-07-30 12:01:25 +00:00
Scott Taylor
f65e42c60d revset: optimize heads(first_ancestors(x) & ...)
This optimization is already done for `heads(ancestors(x) & ...)`, so I
think it makes sense to extend it to `first_ancestors()` as well. This
is especially important if the expression involves a filter that
requires reading commits. For instance, in the nixpkgs repo, evaluating
`heads(first_ancestors(@) & description(jujutsu))` takes 2.6 seconds on
my machine before this optimization, and only 0.2 seconds after.
2025-07-30 11:57:33 +00:00
Scott Taylor
cf29b089d2 default_index: explicitly pass parent positions to shift_to_parents()
This will allow filtering the parent positions using a range before
calling this function.
2025-07-30 11:57:33 +00:00
phoebe
c9aa05de7b ssh-signing: add revocation-list option 2025-07-29 12:46:36 +00:00
Yuya Nishihara
31d12c9245 config: add workaround for implicit inline table insertion
<InlineTable as TableLike> API usually converts non-Value item automatically,
but .entry_format() doesn't.

Fixes #7097
2025-07-29 00:42:47 +00:00
Scott Taylor
1f8aede388 revset: add first_ancestors() function 2025-07-28 22:16:04 +00:00
Scott Taylor
5ac6327b29 revset: add parents_range to Ancestors and Range
Using a range for this is consistent with the filter for parent count,
which also uses a `Range<u32>`. This could also be useful to implement
an `nth_parent()` revset in the future.
2025-07-28 22:16:04 +00:00
Scott Taylor
82593dc1c7 rev_walk: allow filtering based on parent index ranges
The filtering is only used for the wanted queue but not the unwanted
queue, which means this can't be implemented using a custom index
implementation either. I think it only makes sense to filter the wanted
queue, because it's unlikely a user would want to exclude only the first
parents of a root commit for a range.
2025-07-28 22:16:04 +00:00
Austin Seipp
ba24140f1d cli, lib: move to Rust 2024 language edition
This applies a `cargo fmt` and fixes clippy lints to keep the build
properly working.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-07-28 17:05:41 +00:00
Austin Seipp
99ab453790 testutils: add + use<> bounds for impl Strategy
Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-07-28 17:05:41 +00:00
Austin Seipp
bee574354b testutils: set_env is unsafe in Rust 2024
`set_env` is, for various reasons, fundamentally unsafe on approximately
~all modern unicies, and seems like it will never ever be fixed. The
long and short of this is that it will result in segfaults or UB. Rust
2024 therefore marks this function (correctly) as `unsafe`.

The correct solution for 98% of use cases is just to use `envp` during
calls to `execv`, but for our simple cases here of making Git hermetic
there shouldn't be an issue, and a larger refactoring would be needed
for an alternative anyway.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-07-28 17:05:41 +00:00
Austin Seipp
2c2e974996 lib: fix 2024-incompatible usage of gen as a variable name
`gen` is now a reserved keyword; this also matches the surrounding use
of the word `generation` anyway, so IMO it's clearer.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-07-28 17:05:41 +00:00
Pablo Brasero
585a7426f5 everything: ensure consistent spelling of "behavior" 2025-07-28 16:19:31 +00:00
Scott Taylor
e9eb3a0142 git: write empty blob when setting intent to add
Fixes #7062.
2025-07-27 15:13:02 +00:00