Commit graph

2839 commits

Author SHA1 Message Date
Jonas Greitemann
0a208d8cac merge-tools builtin: property-based test round-tripping partial changes
This adds another property-based test which tests for a different
property: after applying only some of the changes in a diff, does the
application of the remaining changes result in the "right" tree?

There are some caveats which changes can be safely selected without
breaking this "roundtrip" property, explained in the doc comment.

Inferring the complementary change selection in the format expected by
scm_report is also more tricky than one might naively expect.

The selection is also subject to random generation and is represented by
a `Vec<bool>` that is wrapped around the actual state machine and that
is successively drawn from to decide which change to select. Otherwise,
the presence of this selection mask does not affect the state machine.
I chose `Vec<bool>` other an actual numeric bitmask because proptest
shrinks the vector more sensibly.
2025-06-18 20:45:56 +00:00
Jonas Greitemann
94ba95bb4c merge-tools builtin: add property-based testing
This adds the proptest crate for property-based testing as well as the
proptest-state-machine crate as direct dev dependencies of jj-cli and as
dependencies of the internal testutils crate. 

Within testutils, a `proptest` module provides a reference state
machine which models the working copy as a map from path to `DirEntry`.
Directories are not represented explicitly, but are implicit in the
ancestors of entries.

The possible transitions of this state machine are for now limited to
the creation of new files (including replacements of existing files
or directories) and a `Commit` operation which the SUT can use to
snapshot a reference state. Additional transitions (moving files,
modifying file contents incrementally, ...) and states (symlinks,
submodules, conflicts, ...) may be added in the future.

This reference state machine is then applied to the builtin merge-tool's
test suite:
- The initial state is always an empty root directory.
- The `Commit` operation creates `MergedTree` from the current state.
- Each step of the way, the same test logic as in the manual
  `test_edit_diff_builtin*` tests is run to check that splitting off
  none or all of the changes results in the left or right tree,
  respectively. The "right" tree corresponds to the current state,
  whereas the "left" tree refers to the last "committed" tree.

Co-authored-by: Waleed Khan <me@waleedkhan.name>
2025-06-18 20:45:56 +00:00
Jonas Greitemann
da2835b86c merge-tools builtin: introduce assert_tree_eq!
This macro in the style of `assert_eq!` compares two trees based
on their `MergedTreeId`s. In case they do not compare equal, the
corresponding trees are dumped in the panic message.

Like `assert_eq!`, the macro accepts a custom format string which will
be included in the panic message.
2025-06-18 20:45:56 +00:00
Yuya Nishihara
0a60e85525 templater: add json() function
This was discussed in #3219 and #3262, and apparently, people don't agree
whether we should implement machine-readable formatting by using templater.
However, there are recurring requests about JSON output, and "jj api" is still
vaporware. So I think it's good to add some support for JSON output.

I'm not going to add syntax to build custom JSON object/list for now. Instead,
property types such as Commit will implement fixed serialization output. The
user might have to write ad-hoc JSON constructs to concatenate the default JSON
output and their custom JSON fields.

We'll need a documentation about which types are serializable. I think this can
be added later when more types get serialization support.
2025-06-18 18:34:37 +00:00
Yuya Nishihara
1d4b4c5278 templater: add function to convert property into serializable object
The conversion is implemented for basic types for now. More types will become
serializable.

Instead of .try_into_serialize(), we can add .try_into_json() method, which
wouldn't require erased-serde. However, I think the separation of serializable
types and serialization formats is nice. .try_serialize<S>(serializer: S) might
also work, but I haven't tried. It would be complicated since template property
is basically a thunk of Fn() -> Output type.
2025-06-18 18:34:37 +00:00
Yuya Nishihara
89b31ca6c4 cargo: add erased-serde dependency
This will help pass serializable objects around in templater.
2025-06-18 18:34:37 +00:00
Martin von Zweigbergk
c43ca3c07b address new mismatched_lifetime_syntaxes Clippy lint 2025-06-17 07:40:05 +00:00
Gilad Woloch
39ef764c10 Prepend backout description with '(deprecated; use revert) ' 2025-06-15 22:23:57 +00:00
Yuya Nishihara
26ba2a0975 cli: add "diff --template" argument
Since the commit template now supports diff.files(), it would probably make
sense that the diff command has the same functionality.

Closes #6681
2025-06-15 15:09:08 +00:00
Yuya Nishihara
91c2f842b9 cli: abandon: remove hidden revisions from the set to abandon
When experimenting on hidden revisions extension in revset #5871, I noticed "jj
abandon" attempted to re-abandon hidden revisions. This could be worked around
by repo.transform_descendants(), but I'm not sure whether the repo API should
ignore hidden revisions which are explicitly specified.

In any case, I don't think "jj abandon" should say hidden revisions get
abandoned.
2025-06-15 15:09:01 +00:00
Yuya Nishihara
b2bb063dbd cli: abandon: merge to_abandon and to_abandon_set variables
Instead of two, just look up commit object when necessary.
2025-06-15 15:09:01 +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
559df2cf5c graph: extend grouping iterator to support generic GraphNode<N, ID> types
This will allow us to sort evolution graph.
2025-06-15 01:59:33 +00:00
Yuya Nishihara
e6c4890a0a cli: evolog: accept multiple starting revisions
There's no technical reason to restrict the starting revisions to a single
commit, and it will be nice that we can see evolution history of duplicated,
split, or divergent commits.
2025-06-15 01:59:33 +00:00
Vincent Ging Ho Yim
e72f161189 cli: fix typo in jj diff -r doc comment 2025-06-13 12:39:15 +00:00
Yuya Nishihara
0a9ab49dc5 revset: do not reinterpret set&filter intersection as filter
We use a query `heads | (domain & ::heads & files(path))` in annotation process,
and it was silly that the whole expression was filtered within `all()`. We
should instead evaluate `heads | filter_within(domain & ::heads, files(path))`.

Fixes #6713
2025-06-13 00:23:59 +00:00
Yuya Nishihara
c555687e8a tests: demonstrate bug of annotation starting from hidden revision 2025-06-13 00:23:59 +00:00
Yuya Nishihara
11599c6be1 cli: op diff: show divergent commits individually
Since divergence doesn't mean the commit has been squashed from / split into
multiple commits, it doesn't make much sense to show divergent commits as a
single entry. So this patch turns "changes" into a map indexed by CommitId in
order to track divergent commits individually. The ModifiedChange type is also
reorganized accordingly.

If old commits had divergence, we can't reliably associate new commit with the
old one. In the original implementation, we worked around the problem by not
displaying diffs for such changes. In this patch, the latest "old" commit is
arbitrarily chosen. I think this is better than disabling diffs, and will help
integrating predecessors information. Change-id based deduction won't be used
if new commits exist in the predecessors map.
2025-06-12 10:33:57 +00:00
Yuya Nishihara
91bbbc6ef6 cli: op diff: inline revset::walk_revs() 2025-06-12 10:33:57 +00:00
Yuya Nishihara
cd173059dc cli: op diff: sort modified changes purely by commit ids
This basically means that the graph is sorted by "new" commits, then abandoned
commits follow. I'm thinking of integrating predecessors information into "op
diff", and it will make sense to identify changes by commit ids instead of
change ids there. Divergent commits will be displayed individually, and squashed
commits will have all predecessor commits including ones having different change
ids.

This should also fix "graph has cycle" issue. In the original implementation, we
mixed parent change ids of both new and old views, which could result in cycle.
2025-06-12 10:33:57 +00:00
Yuya Nishihara
25c028c5f0 tests: add "op diff" samples with divergent changes 2025-06-12 10:33:57 +00:00
Ilya Grigoriev
a96fa53101 cli git fetch: fix typo in error message 2025-06-12 00:28:41 +00:00
Yuya Nishihara
46dde130dd cli: op log/show: don't attempt to show diff from auto-merge parents
Unfortunately, we can't reproduce a merge in bit-exact manner. The shape of the
commits graphs should be identical, but commit/change ids differ. I don't think
we would want to see the list of "identical" changes, so this patch disables
show_op_diff() for merge operations. "op diff" is unchanged because it doesn't
fail, and the diff functionality might be useful for debugging problems.

Alternatively, we could show diff from the auto-merge state without rebasing
descendants, but I don't think that would be useful either. The state before
rebase_descendants() isn't visible to users, so it shouldn't be the stuff the
user would care about.

Fixes #4465
2025-06-12 00:16:21 +00:00
Yuya Nishihara
de6dd1c1f6 cli: op diff: don't show summary of dummy merge operation
A dummy merge shouldn't be visible to users. Only the merged view should matter.
2025-06-12 00:16:21 +00:00
Yuya Nishihara
efe1885456 tests: demonstrate problem of reproducing merged operation
Since merge_operations() may rebase descendants of the rewritten commits, it
can't produce the exactly same results.

#4465
2025-06-12 00:16:21 +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
Benjamin Brittain
97c7edcafc store: Add Send bounds to the AsyncRead future returned by read_file
This change enables the jj-lib api to be used in multi-threaded executor
contexts.
2025-06-09 21:08:10 +00:00
Igor Velkov
6e0ceaa47b cargo: Add metadata to build .deb package with cargo-deb 2025-06-08 20:18:57 +00:00
Yuya Nishihara
b1503b4fe9 cli: add "debug revset --no-optimize" flag to evaluate original expression
This might be useful to test the exact evaluation result.
2025-06-08 04:44:12 +00:00
Yuya Nishihara
446156e139 cli: add "debug revset --no-resolve" flag to just print optimized expression
It was annoying that we had to use valid symbols to test tree transformation.
revset::optimize() works without resolving symbols.
2025-06-08 04:44:12 +00:00
Yuya Nishihara
744b8ff491 cli: calculate size of immutable descendants by check_rewritable()
Follows up 708e1c58cd "cli: improve hint message when suggesting
`--ignore-immutable`." Not all callers of find_immutable_commit() need
lower/upper bounds, and this seems better in API standpoint.
2025-06-08 00:15:25 +00:00
Yuya Nishihara
2c1be3be13 cli: simply pass &[CommitId] to inner find_immutable_commit() function 2025-06-08 00:15:25 +00:00
Yuya Nishihara
b0173c9aaf cleanup: use slice::from_ref() in non-test code 2025-06-07 22:03:51 +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
Martin von Zweigbergk
c90e1396b2 backend: add methods for reading and writing copy objects
This patch implements the methods only in the test backend. That
should enough for us to start implementing diffing and merging on top,
including tests for that functionality. Support in the Git backend can
come once we've seen that the model works.
2025-06-03 01:11:32 +00:00
Jonas Greitemann
1f3cf4a8bc merge-tools builtin: correctly set executable bit on binary files with selected changes
When splitting a commit which both changes a binary file and flips
the executable bit, selecting just the `Binary` change but not the
`FileMode` change still applied the mode change.

This is now fixed by overriding the file mode of a selected binary file
with whatever file mode scm-record tells us was selected.
2025-06-02 17:40:26 +00:00
Jonas Greitemann
aebf681511 merge-tools builtin: update mode of files with unchanged contents
If an empty file or a binary file has its file mode (specifically the
executable bit) changed but its contents remain the same, scm-record
reports its contents as `Unchanged`. By not doing anything for unchanged
_contents_, a potentially selected file mode change was lost.

For text file, scm_record 0.8.0 currently always reports
`SelectedContents::Text`, even if the file hasn't changed at all (or
only its executable bit has). I've created arxanas/scm-record#104
upstream to track this bug. This unexpected behavior caused file
mode-only changes to be handled correctly but it unnecessarily rewrote
the file object, where writing the tree would have sufficed. Thus,
fixing the upstream issue prior to this commit would exacerbate the
issue by also affecting text files.

This commit now addresses file mode changes by updating the `TreeValue`
without rewriting the files or generating new file IDs. It is sufficient
to do so only if a file mode change has been selected, reusing the same
`file_mode_change_selected` condition introduced by the previous commit.
2025-06-02 17:40:26 +00:00
Jonas Greitemann
f115662f68 merge-tools builtin: omit changes of binary files if hash stayed the same
When the contents of a binary file have not changed (but it appears in
the tree diff because its file mode has) the builtin diff tool still
presented a `Section::Binary` section to the user, showing identical
descriptions for both sides of the diff.

Selecting this section should not affect the selected contents, so it
does not make much sense.

This behavior was not captured by any of the existing snapshot tests but
it will be captured by a new regression test added in the next commit.
2025-06-02 17:40:26 +00:00
Jonas Greitemann
91ffb2a6b3 merge-tools builtin: only delete files from tree which used to exist
This adds a manual regression test for the scenario in issue #5189.

As of #6411, this no longer results in a panic. However, the test still
fails and rightfully so: When following the reproduction for #5189 and
not selecting any change in `jj split`, the split-off (first) commit
will still record the deletion of `folder/.keep` but not the creation of
the file `folder`.

scm-record yields a selected change with `FileMode::Absent` in one of
two cases:
- when an existing file is deleted and this change is selected,
- when a new file is created and this change is not selected.

From the information provided by `File::get_selected_contents()`
alone, it is not possible to distinguish these two cases. In the first
case, the tree definitely needs to change to reflect the deletion, so
it was marked for deletion. In the second case, that deletion marker
("tombstone") usually was just ignored because no such file existed.

However, when the tombstone happens to coindice with a deleted directory
and a new file has been created in its place, but neither change is
selected, then the tombstone had the effect of deleting the directory
from the tree.

This is now fixed by only marking the file for deletion if a
corresponding file mode change to `Absent` has been "checked".
Otherwise, if the file mode change for the creation of a new file is not
"checked", the file can be merely skipped.
2025-06-02 17:40:26 +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
92cd17c095 cli: add missing "commit" label to tx.commit_summary_template() 2025-06-02 00:08:18 +00:00
Jonas Greitemann
d31c42f6c1 config-schema: validate schema defaults according to schema
While the existing test checks that the schema defaults are consistent
with the output of `jj config get` (with default config), it would not
find type errors like the ones fixed in e9da94e.

This add another test case which validates the synthetic TOML containing
the default values according to the schema against the schema itself.
2025-06-01 17:36:37 +00:00
Jonas Greitemann
e5878517b1 config-schema: rename schema test module used by datatest
Because the datatests don't use the default libtest harness, datatests
and normal tests cannot be mixed in the same module or even binary.

`test_config_schema` is renamed, both to reflect that it is unlike the
other `test_*` modules, but also to make the name available for "normal"
tests related to the config schema.
2025-06-01 17:36:37 +00:00
Jonas Greitemann
908fb6e84f config-schema: add test asserting schema defaults match default config
Extracts the `"default"` values from the schema and creates a synthetic
TOML file holding all the defaults according to the schema. This is done
through some `jq` magic and is not perfect but rather a best effort.
If `jq` is not available, the test is skipped; in CI `jq` is required.

The test then run `jj config get` in the test env for each key in that
defaults file and compares the resulting value with the schema default.
For a few keys, there are actually no defaults known to `jj config get`,
because they are hard-coded or dynamic. These exceptions are intercepted
and explained in the test.
2025-06-01 17:36:37 +00:00
Jonas Greitemann
d2c543864a config-schema: fix default for git.write-change-id-header
#6475 changed the default value for `git.write-change-id-header` to
`true` but this was not mirrored in the schema's default, causing the
new `test_config_get_yields_values_consistent_with_schema_defaults` to
fail.
2025-06-01 17:36:37 +00:00
Jonas Greitemann
869da38e43 config-schema: fix schema default for revset-aliases."immutable_heads()"
The default value became definitely outdated when "branches" were renamed
to "bookmarks" in 0.20 (and broke in 0.28).

The actual default of `revset-aliases."immutable_heads()"` is in fact
`builtin_immutable_heads()`, now. However, if one were to want override
to default `immutable_heads()`, `builtin_immutable_heads()` would likely
not be a helpful starting point. Therefore, it may make sense to keep
this "resolved" default value in the schema.
2025-06-01 17:36:37 +00:00
Jonas Greitemann
60d42d708e config-schema: fix schema default for split.legacy-bookmark-behavior
As the discussion in #3419 evolved, the old behavior for `jj split` was
enabled by default again in 2af5b60d32, but the default value in the
schema was missed.
2025-06-01 17:36:37 +00:00
Martin von Zweigbergk
98d884827e cli: for deprecated configs, say at which source level they were found
I had a report from a user at Google who was confused about a config
deprecation message because they had forgotten that they set a
repo-level config. This patch includes the source level in the warning
message. Hopefully that's sufficient. We could of course print the
path too, but that would make the message much longer so we would have
to split it up on two lines and I'm not sure it's worth it.
2025-05-28 20:01:51 +00:00
Josep Mengual
c732472f85 config: do not warn about deprecated path if manually configured 2025-05-28 01:48:18 +00:00