Compare commits

...

1630 Commits

Author SHA1 Message Date
dependabot[bot]
489f693c47 github: bump the github-dependencies group with 4 updates
Bumps the github-dependencies group with 4 updates: [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain), [taiki-e/install-action](https://github.com/taiki-e/install-action), [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `dtolnay/rust-toolchain` from 56f84321dbccf38fb67ce29ab63e4754056677e0 to b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](56f84321db...b3b07ba8b4)

Updates `taiki-e/install-action` from 2.50.3 to 2.50.7
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ab3728c7ba...86c23eed46)

Updates `astral-sh/setup-uv` from 6.0.0 to 6.0.1
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](c7f87aa956...6b9c6063ab)

Updates `github/codeql-action` from 3.28.16 to 3.28.17
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](28deaeda66...60168efe1c)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-version: b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
  dependency-type: direct:production
  dependency-group: github-dependencies
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-version: 3.28.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 19:42:25 +00:00
dependabot[bot]
663dc0eef6 cargo: bump the cargo-dependencies group with 6 updates
Bumps the cargo-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.47` | `4.5.48` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.40` | `0.4.41` |
| [hashbrown](https://github.com/rust-lang/hashbrown) | `0.15.2` | `0.15.3` |
| [insta](https://github.com/mitsuhiko/insta) | `1.43.0` | `1.43.1` |
| [rustix](https://github.com/bytecodealliance/rustix) | `1.0.5` | `1.0.7` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.22.25` | `0.22.26` |


Updates `clap_complete` from 4.5.47 to 4.5.48
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.47...clap_complete-v4.5.48)

Updates `chrono` from 0.4.40 to 0.4.41
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.40...v0.4.41)

Updates `hashbrown` from 0.15.2 to 0.15.3
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/commits/v0.15.3)

Updates `insta` from 1.43.0 to 1.43.1
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.43.0...1.43.1)

Updates `rustix` from 1.0.5 to 1.0.7
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.5...v1.0.7)

Updates `toml_edit` from 0.22.25 to 0.22.26
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.25...v0.22.26)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-version: 4.5.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: chrono
  dependency-version: 0.4.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: hashbrown
  dependency-version: 0.15.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: insta
  dependency-version: 1.43.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: rustix
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: toml_edit
  dependency-version: 0.22.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 19:42:05 +00:00
Yuya Nishihara
d46c384db2 changelog: remove redundant entry about macOS config path
It's documented as deprecation, and is not actually a "bug".
2025-05-05 13:15:10 +00:00
Daniel Luz
87f6db0a70 completion: revset expression completer 2025-05-05 02:54:51 +00:00
Yuya Nishihara
8936a7bc4b templater: unify property conversion traits
We could define separate traits for conversion "from" and "to", but there aren't
many users who benefit from precise trait bounds.
2025-05-05 01:16:41 +00:00
Yuya Nishihara
780f9e547d templater: call property wrap_() functions directly, delete old forwarding fns
This patch replaces single-char L type aliases with P, and renames the L aliases
where that makes sense. Many of the P aliases will be removed later by
introducing generic wrap<T>() trait.
2025-05-05 01:16:41 +00:00
Yuya Nishihara
b611c313aa templater: move non-core wrap_*() functions to property types, leverage macro
Since the return type is now Self, we can reuse impl_wrap_property_fns!() macro
for non-core types.
2025-05-05 01:16:41 +00:00
Yuya Nishihara
7643364a76 templater: move core wrap_*() functions to property wrapper type
I think this was remainder of the old design where wrap_*() functions took
&TemplateLanguage as self argument. Since the wrapper type implements accessor
functions like .try_into_boolean(), it makes sense that the same type implements
::wrap_boolean(), etc.

These .wrap_<T>() functions will become generic over T.
2025-05-05 01:16:41 +00:00
Yuya Nishihara
577a484d1d templater: remove unneeded trait bound from build_lambda_expression()
This function just passes P down to the build_body() callback transparently.
2025-05-05 01:16:41 +00:00
Nils Koch
deb4f1ba79 docs: update git compatibility docs for commit signing 2025-05-04 22:52:24 +00:00
Martin von Zweigbergk
103d05149d cli: add "$schema" line when creating new config file
It seems like a small usability improvement if users don't need to
enter the "$schema" link manually when they create a new config file.

This doesn't help existing users.
2025-05-04 17:14:10 +00:00
Nicole Patricia Mazzuca
05fa4bc0a3 docs: add some more info on signing
I was asked about this in Discord, so I wanted to make sure to have the
information available in the docs and not just the code.
2025-05-03 11:27:36 +00:00
Ilya Grigoriev
4f3d890bee docs cli reference: update and pin clap-markdown, include aliases in reference
clap-markdown doesn't follow semver, so I pinned the version exactly.
2025-05-03 00:35:12 +00:00
Nicole Patricia Mazzuca
b568bb67f0 config: deprecate macOS legacy platform configs 2025-05-02 20:05:24 +00:00
Nicole Patricia Mazzuca
6f6496ba83 config: default to XDG config files on macOS
Support existing users with the "legacy" config directory, as well.
This will be deprecated in a latter commit.
2025-05-02 20:05:24 +00:00
Nicole Patricia Mazzuca
f9966a644b config: switch to etcetera from dirs 2025-05-02 20:05:24 +00:00
Nicole Patricia Mazzuca
19f997a466 config: change how config paths are represented
This change, from an enum to a struct, is a more accurate representation
of the actual way that a ConfigPath works; additionally, it lets us add
different information without modifying every single enumeration field.
2025-05-02 20:05:24 +00:00
Steve Fink
0eceed9832 restore, diffedit: do not output "Created ..." message, which dates back to before we couldn't lookup by change id. 2025-05-01 21:11:31 +00:00
Yuya Nishihara
1b300fefa2 templater: add type alias for Box<dyn TemplateProperty<..>>
It's verbose to type.
2025-05-01 00:33:59 +00:00
Yuya Nishihara
b96924d17f templater: convert property to trait object by caller
I'm trying to refactor property wrapping functions, and noticed that it's odd
that .wrap_<property>() does boxing internally whereas .wrap_template() doesn't.

Also, it sometimes makes sense to turn property into trait object earlier. For
example, we can deduplicate L::wrap_boolean() in build_binary_operation().
2025-05-01 00:33:59 +00:00
Yuya Nishihara
96b633a091 templater: add property.into_dyn() helper
I'm going to move Box<dyn _> conversion to callers, so there will be more places
to use this helper.
2025-05-01 00:33:59 +00:00
Yuya Nishihara
533fb5e4bb generic_templater: remove unneeded lifetime bounds
Perhaps, these bounds were needed because the context type were compiled into
the template object before.
2025-05-01 00:33:59 +00:00
Théo Daron
3ab9e098d7 cli config edit: Rollback to previous config when invalid TOML is saved 2025-04-29 16:26:11 +00:00
Jonas Greitemann
928984019f completion: fix completion of arguments for aliases/default-command in bash and zsh
This also adds a test case for the completion of arguments following
multi-argument aliases, to cover the bug reported in issue #5377.

The default command is like a special kind of alias, one which is
expanded from zero tokens. Consequently, it also triggers the bug
#5377 on bash/zsh, even if the `default-command` is just a single token.

The fix is along the lines sketched out by the "TODO" comment. Bash and
Zsh don't behave identical, so the padding ([""]) still needs to be
applied (and removed) conditionally in a disciplined manner.
2025-04-29 14:38:25 +00:00
Jonas Greitemann
eaaaf058a8 completion tests: parameterize test case over different shells
The completion mechanism works differently in different shells:

For example, when the command line `jj aaa bb ccc` is completed at the
end of the `bb` token, bash and zsh pass the completer the whole line
`-- jj aaa bb ccc` and an index of 2 which refers to the `bb` token;
they are then expected to complete `bb`. Meanwhile, fish and Powershell
only pass the command up to the completion point, so `-- jj aaa bb`;
the completer is always expected to complete the last token. In all
cases, the shell ultimately decides what to do with the completions,
e.g. to complete up to a common prefix (bash), to show an interactive
picker (zsh, fish), or to insert the completion if it is the only one
(all shells). Remaining tokens (`ccc`) are also always appended by the
shell and not part of the completion.

While this is mostly handled by the clap_complete crate, we do expand
aliases and present clap_complete with a fake view of the real command
line, thereby reaching into its internals by wrapping the interface
between the completion shell script that is provided by clap_complete
and its Rust code in `CommandEnv::try_complete()`. If we get this wrong,
completion might yield unexpected results, so it is worth testing
completion for both flavors of shells whenever aliases are potentially
in the mix.

To avoid redundancy, the shell-specific invocation of `jj` is factored
into a `complete_at()` function of the test fixture. The `test-case`
crate is then used to instantiate each test case for different values of
clap_complete's `Shell` enum.

filter

bash/zsh specific behavior

move impl
2025-04-29 14:38:25 +00:00
Gaëtan Lehmann
2c4a0328f9 templates: add self.trailers().contains_key(key)
as a simpler and more readable alternative to

    self.trailers().filter(|t| t.key() == "Change-Id")
2025-04-29 06:36:12 +00:00
Martin von Zweigbergk
b7489aac81 CliRunner: don't require hook functions to have static lifetime
I want to pass in a closure that captures some variables in the local
scope of my `main()` function.
2025-04-29 04:22:47 +00:00
Martin von Zweigbergk
4435fae3be CliRunner: replace start hook by dispatch hook
The `CliRunner` lets a custom binary add processing that should happen
before running a command. This patch replaces that by a hook that can
do processing before and/or after. I thought I would want to use this
for adding telemetry (such as timings) to our custom binary at
Google. I ended up adding that logging outside of `CliRunner::run()`
instead, so it gives a more accurate timing of the whole invocation. I
think this patch is still an improvement, as it's more generic than
the start hook.
2025-04-29 02:49:12 +00:00
dependabot[bot]
517292fd46 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [insta](https://github.com/mitsuhiko/insta), [syn](https://github.com/dtolnay/syn) and [toml_edit](https://github.com/toml-rs/toml).


Updates `insta` from 1.42.2 to 1.43.0
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.42.2...1.43.0)

Updates `syn` from 2.0.100 to 2.0.101
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.100...2.0.101)

Updates `toml_edit` from 0.22.24 to 0.22.25
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.24...v0.22.25)

---
updated-dependencies:
- dependency-name: insta
  dependency-version: 1.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-version: 2.0.101
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: toml_edit
  dependency-version: 0.22.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 19:24:18 +00:00
dependabot[bot]
b2636dd9fa github: bump the github-dependencies group with 5 updates
Bumps the github-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [taiki-e/install-action](https://github.com/taiki-e/install-action) | `2.49.50` | `2.50.3` |
| [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) | `16` | `17` |
| [actions/setup-python](https://github.com/actions/setup-python) | `5.5.0` | `5.6.0` |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `5.4.2` | `6.0.0` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.28.15` | `3.28.16` |


Updates `taiki-e/install-action` from 2.49.50 to 2.50.3
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](09dc018eee...ab3728c7ba)

Updates `DeterminateSystems/nix-installer-action` from 16 to 17
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](e50d5f73bf...21a544727d)

Updates `actions/setup-python` from 5.5.0 to 5.6.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](8d9ed9ac5c...a26af69be9)

Updates `astral-sh/setup-uv` from 5.4.2 to 6.0.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](d4b2f3b6ec...c7f87aa956)

Updates `github/codeql-action` from 3.28.15 to 3.28.16
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](45775bd823...28deaeda66)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.50.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-version: '17'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
- dependency-name: actions/setup-python
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-version: 3.28.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 19:24:08 +00:00
Yuya Nishihara
4f3e7e972d templater: don't attribute method table functions as pub
It's unlikely that these functions are used by external callers.
2025-04-28 01:37:49 +00:00
Yuya Nishihara
27a35a79b1 merge-tools: builtin: refactor mapping of binary files
We no longer need this, but I think this is easier to follow than destructuring
FileContents in larger match block.
2025-04-28 01:37:45 +00:00
Mateus Auler
968806bc64 templates: implement Commit.trailers() 2025-04-27 18:29:25 +00:00
Yuya Nishihara
6e67d79c10 merge-tools: builtin: parse conflict hunks back to merge value
Closes #4963
2025-04-27 01:33:48 +00:00
Yuya Nishihara
acb4e27bd9 merge-tools: builtin: split apply_diff_builtin() function
In builtin diff editor, we materializes conflicts, so we need to parse them
back to reproduce the original (or partially-resolved) contents. OTOH, the
merge editor should write the merged contents transparently.

This change also revealed that binary hunks wouldn't be processed correctly in
the merge editor.
2025-04-27 01:33:48 +00:00
Yuya Nishihara
105c892ce4 tests: do not shell out taplo to gather forgotten test files
It would be annoying if forgotten tests wouldn't be reported locally.
2025-04-27 01:33:23 +00:00
Ilya Grigoriev
5a735182ac docs conflicts.md: clarify the revert example a bit
Yuya's suggestion from
<https://github.com/jj-vcs/jj/pull/6415#discussion_r2061122640>.

Co-authored-by:  Yuya Nishihara <yuya@tcha.org>
2025-04-27 00:40:27 +00:00
Ilya Grigoriev
1f14f7a0ff docs conflicts.md: fix confusing typo
Fix #6414.
2025-04-27 00:40:27 +00:00
Martin von Zweigbergk
13477940af local_working_copy: avoid a block_on() in already async function 2025-04-26 02:22:23 +00:00
Yuya Nishihara
a3dcd8b659 merge-tools: builtin: leverage materialized_diff_stream() 2025-04-26 02:05:40 +00:00
Yuya Nishihara
be37209423 merge-tools: builtin: do not include unchanged entry in test diffs, sort paths
This matches the edit_diff_builtin() behavior. "unused" path is removed since
it's the same as "unchanged".
2025-04-26 02:05:40 +00:00
Yuya Nishihara
9c723a1c76 merge-tools: builtin: extract wrapper functions in tests
make_diff_files() will be async function that uses materialized_diff_stream()
internally. apply_diff_builtin() will take callbacks to handle binary/conflict
files.
2025-04-26 02:05:40 +00:00
Jacob Hayes
1cdd79071e git: tolerate unknown git.fetch remotes if others are available
If `git.fetch` contains remotes that are not available, we currently error even
if other remotes are available. For common fork workflows with separate
`upstream` and `origin` remotes (for example), this requires a user to either
set both remotes in their user config and override single-remote repos or set
only one in their user config and override all multi-remote repos to fetch from
`upstream` (or both).

This change updates fetching to only *warn* about unknown remotes **if** other
remotes are available. If none of the configured remotes are available, an error
is still raised as before.
2025-04-25 14:06:53 +00:00
Martin von Zweigbergk
92629ded4c docs: make technical conflicts doc better match our recent thinking
These days, we usually think of conflicts as one base state and series
of diffs between other states. The base state is normally the parent
when rebasing.

Also, we're deprecated `jj backout` in favor of `jj revert`, so let's
use that terminology.
2025-04-25 13:53:28 +00:00
Martin von Zweigbergk
146900a071 cli: put editor-*.jjdescription file in /tmp instead of .jj/repo/
When we ask the user to prodive a commit description, we currently
write a file to `.jj/repo/` with the draft description and then pass
that to the editor. If the editor exits with an error status, we leave
the file in place and tell the user about the path so they can recover
the description. I'm not sure I've ever used one of these files. I
have certainly never used a file that's not from the most recent
edit. I have, however, cleaned up old such files. This patch changes
the code so we write them to /tmp instead, so we get the cleanup for
free.
2025-04-25 02:08:30 +00:00
Martin von Zweigbergk
d41c83e96a merged_tree: make merge_trees() async
`merge_tree_values()` was already marked async, but it was calling the
blocking `merge_trees()`, so it could still block.
2025-04-24 16:29:24 +00:00
Jonas Greitemann
7bb8e17e88 tests: factor out utility function is_external_tool_installed
A pattern has emerged where a integration tests check for the
availability of an external tool (`git`, `taplo`, `gpg`, ...) and skip
the test (by simply passing it) when it is not available. To check this,
the program is run with the `--version` flag.

Some tests require that the program be available at least when running
in CI, by calling `ensure_running_outside_ci` conditionally on the
outcome. The decision is up to each test, though, the utility merely
returns a `bool`.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
bf2c01e74b config-schema: allow "command-env"-style for editors and fix tools
The `CommandNameAndArgs` struct is used in multiple places to specify
external tools. Previously, the schema only allowed for this in
`ui.pager`.

This commit adds a few sample configs which define variables editors and
fix tools as commands with env vars.

The schema has also been updated to make these valid.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
d35dd31c87 config-schema: require both "command" and "env" keys in structured tool config
Not sure if that is intentional or should rather be considered a bug,
but currently the "structured" option for specifying an external tool
requires that both the command "command" and the "env" keys are
specified. The "command" key makes sense; for the "env" key it came as
a surprise, but it can be argued that this form should only be used when
environment variables need to be specified and the plain string or array
form should be used otherwise.

Either way, the schema did not accurately reflect the current behavior;
now it does. Two sample configs have been added as schema test cases.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
4a1754bc46 config-schema: deny empty arrays for command-like config options
Anytime an external tool is referenced in the config, the command can be
provided as a string or as a token array. In the latter case, the array
must not be empty; at least the command name must be provided.

The schema didn't previously object to an empty array, though; this has
now been rectified. I've added more sample configs to cover this case.
Those same configs can also be used to illustrate that this is indeed
jj's current behavior:

$ jj --config-file cli/tests/sample-configs/invalid/ui.pager_empty_array.toml show
Config error: Invalid type or value for ui.pager
Caused by: data did not match any variant of untagged enum CommandNameAndArgs
                                                                                                                                                                                  
$ jj --config-file cli/tests/sample-configs/invalid/ui.pager.command_empty_array.toml show
Config error: Invalid type or value for ui.pager
Caused by: data did not match any variant of untagged enum CommandNameAndArgs
                                                                                                                                                                                  
$ jj --config-file cli/tests/sample-configs/invalid/ui.editor_empty_array.toml config edit --user
Config error: Invalid type or value for ui.editor
Caused by: data did not match any variant of untagged enum CommandNameAndArgs
                                                                                                                                                                                  
$ jj --config-file cli/tests/sample-configs/invalid/ui.diff-editor_empty_array.toml split
Error: Failed to load tool configuration
Caused by:
1: Invalid type or value for ui.diff-editor
2: data did not match any variant of untagged enum CommandNameAndArgs
                                                                                                                                                                                  
$ jj --config-file cli/tests/sample-configs/invalid/ui.merge-editor_empty_array.toml resolve
Error: Failed to load tool configuration
Caused by:
1: Invalid type or value for ui.merge-editor
2: data did not match any variant of untagged enum CommandNameAndArgs
                                                                                                                                                                                  
$ jj --config-file cli/tests/sample-configs/invalid/ui.diff.tool_empty_array.toml diff
Config error: Invalid type or value for ui.diff.tool
Caused by: data did not match any variant of untagged enum CommandNameAndArgs
                                                                                                                                                                                  
$ jj --config-file cli/tests/sample-configs/invalid/fix.tools.command_empty_array.toml fix
Config error: Invalid type or value for fix.tools.black
Caused by: data did not match any variant of untagged enum CommandNameAndArgs
in `command`

As a notable exception, `ui.default-command` *is* allowed to be an empty
array. In that case, `jj` will print a usage message. This is also
covered by a valid sample config.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
5444067c37 config-schema: schema wrongly allowed ui.pager.command to be a string
While `ui.pager` can be a string which will be tokenized on whitespace,
and argument token array, or a command/env table, the `command` key
within that table currently must be an array. The schema previously
explicitly also allowed it to be a string but that does not actually
work, as exemplified by running:
```sh
$ jj --config-file cli/tests/sample-configs/invalid/ui.pager_command-env_string.toml config list
Config error: Invalid type or value for ui.pager
Caused by: data did not match any variant of untagged enum CommandNameAndArgs
```

`CommandNameAndArgs` should potentially be changed to allow strings.
For now, the schema has been updated to reflect the status quo. A new
sample toml has been added to the `invalid` directory to cover this;
prior to updating the schema, this new test case failed. Once the
behavior is changed to allow string, the file merely needs to be moved
to `valid`.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
d4024e0d92 config-schema: fix 2 default values which weren't booleans, but strings
These are two more instances where the default values were wrong and in
fact not even consistent with the schema itself.

I've found these by running
```sh
jq -r 'paths(type == "object" and has("default")) as $p | getpath($p).default | tojson as $v | $p | map("\"\(select(. != "properties"))\"") | join(".") as $k | "\($k) = \($v)"' cli/src/config-schema.json | taplo check --schema=file://$PWD/cli/src/config-schema.json -
```
which uses `jq` to filter the default values from the schema definition
to create a rudimentary TOML file containing all the defaults according
to the schema and then uses `taplo` the validate this TOML against the
schema.

This approach could be developed further to also parse the intermediate
TOML file and compare the result with the default config (from parsing
an empty config). That would not only test for self-consistency of the
schema's proclaimed defaults but also for consistency with the actual
defaults as assumed by jj.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
8c4586ab09 config-schema: add sample config files to exercise schema tests
Adds a bunch of additional sample config toml files. Via the
`datatest_runner`, these each correspond to a test case to check that
the toml is correctly (in-)validated according to the schema.

The `valid/*.toml` files typically define multiple related config
options at once. Where there's some overlap with the default configs in
`cli/src/config`, the aim was to choose different allowed values, e.g.
hex colors, file size in bytes (numeric), etc.

The `invalid/*.toml` files typically only define a single offending
property such as to not obscure individual false negatives. All of the
"invalid" files are still valid toml as the aim is not to test the
`toml_edit` crate or Taplo.

The sample files all contain a Taplo schema directive. This allows them
to be validated against the schema on the fly by Taplo's LSP and derived
IDE plugins to speed up editing and immediately highlight offending
options.

Closes #5695.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
d286b406e7 config-schema: use datatest-stable crate to instantiate tests
The `datatest-stable` crate allows to dynamically instantiate test cases
based on available files. This is applied to `test_config_schema` to
create one test case per config file. As case in point, the test case
for `hints.toml` was missing previously, hence the total number of tests
is up one.

This will become useful when adding more config examples to somewhat
exhaust the schema.

`datatest-stable` uses a custom test harness and thus cannot be used in
the same integration test binary that all of the other test modules run
in. However, if data-driven tests are to be used for other applications,
they can share in the same binary, so the module structure is already
set up to mirror the central "runner" approach.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
8882f0016d tests: allow multiple integration tests in check for forgotton test files
The previous implementation of `assert_no_forgotten_test_files`
hard-coded the name of the `runner` integration test and required all
other source files to appear in matching `mod` declarations. Thus, this
approach cannot handle multiple integration tests.

However, additional integration tests may be desirable
- to support tests using a custom test harness (see upcoming commits)
- to balance the trade-off between test run time and compile time as
  the test suite grows in the future.

The new implementation first uses `taplo` to parse the `[[test]]`
sections of the manifest to identify integration test main modules,
and then searches in those for `mod` declarations. This is then compared
to the list of source files in the tests directory. Like the previous
implementation, the new one does not attempt to recurse into submodules
or to handle directory-style modules; just like before it only treats
source files without a module declaration as an error and relies on the
compiler to complain about the other way around.

When `taplo` is not installed, the check is skipped unless it is running
in CI where we require `taplo` to be available.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
69cf7b38fc config-schema: add missing default for core.fsmonitor 2025-04-24 15:48:08 +00:00
Jonas Greitemann
b99361cbab config-schema: fix wrong "default" for ui.streampager.interface
The schema said that the default was "never" which is actually not one
of the allowed enum values.
2025-04-24 15:48:08 +00:00
Jonas Greitemann
47fba33dcd config-schema: fix a typo in the $comment field 2025-04-24 15:48:08 +00:00
Yuya Nishihara
757a08cd21 cli: rebase: consolidate call sites of move_commits() 2025-04-24 13:16:33 +00:00
Yuya Nishihara
fdaf8b9ca5 cli: rebase: remove redundant check for empty operation
move_commits() exits early if the target commits are empty.
2025-04-24 13:16:33 +00:00
Yuya Nishihara
bdedf4a958 cli: rebase: use slice pattern when constructing tx descriptions 2025-04-24 13:16:33 +00:00
Vincent Ging Ho Yim
5ba4173b45 mkdocs: enable instant loading
This allows text typed into the search box to persist across page navigation, which is
useful when users look up a term but aren't sure on which page they will eventually find
the relevant information. See the [`mkdocs-material` docs][mm-docs] for more
information.

[mm-docs]:
https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#instant-loading
2025-04-23 19:19:25 +00:00
Sam
001801a3b1 cli: add config option for built-in pager's ruler 2025-04-23 03:16:58 +00:00
Yuya Nishihara
6e5f0297e2 cli: evolog: rename variable to match the actual type
iter_nodes isn't an iterator.
2025-04-22 16:27:02 +00:00
Yuya Nishihara
853992facf cli: evolog: remove redundant Result wrapping 2025-04-22 16:27:02 +00:00
Benjamin Tan
f9c83541d6 docs: releasing: use gh api --paginate to construct contributor list 2025-04-22 15:03:23 +00:00
Yuya Nishihara
0e17c0e17d copies: ignore existing tree when testing copy/rename operation type
In this context, a tree value should be considered absent.
2025-04-22 00:44:13 +00:00
Yuya Nishihara
b6a861b529 git_backend: do not include renamed trees in copy records
We could instead make CopiesTreeDiffStream reject bad copy records, but it's
pretty much wrong to construct a FileId for tree.

Fixes #6390
2025-04-22 00:44:13 +00:00
George Christou
69135abf5b docs: update meld install command 2025-04-22 00:28:33 +00:00
Vincent Ging Ho Yim
08f9a9af43 docs/contributing: remove explicit enabling of copy button in code snippets 2025-04-21 23:12:43 +00:00
Vincent Ging Ho Yim
1de9cfe540 mkdocs: enable code copy button globally
This makes it easier to try out configs and commands mentioned in the docs.
2025-04-21 23:12:43 +00:00
Vincent Ging Ho Yim
f300371398 docs/contributing: add instructions to build the docs for offline distribution 2025-04-21 23:11:21 +00:00
Vincent Ging Ho Yim
0bb65b9082 mkdocs: enable offline plugin selectively with MKDOCS_OFFLINE environment variable
This removes the need to keep the mkdocs plugins config in sync in a separate
`mkdocs-offline.yml`.
2025-04-21 23:11:21 +00:00
Vincent Ging Ho Yim
7a281a23a6 mkdocs: enable readable anchor links for content tabs
For example, [this URL][1] becomes [this][2].

[1]: https://jj-vcs.github.io/jj/prerelease/contributing/#__tabbed_1_1
[2]: https://jj-vcs.github.io/jj/prerelease/contributing/#macoslinux
2025-04-21 19:18:11 +00:00
Vincent Ging Ho Yim
6a7f72f9fa mkdocs: sort markdown_extensions nodes lexicographically 2025-04-21 19:18:11 +00:00
dependabot[bot]
31faed3ee1 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [clap](https://github.com/clap-rs/clap) and [rpassword](https://github.com/conradkleinespel/rpassword).


Updates `clap` from 4.5.36 to 4.5.37
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.36...clap_complete-v4.5.37)

Updates `rpassword` from 7.3.1 to 7.4.0
- [Release notes](https://github.com/conradkleinespel/rpassword/releases)
- [Commits](https://github.com/conradkleinespel/rpassword/compare/v7.3.1...v7.4.0)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: rpassword
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 19:17:24 +00:00
Yuya Nishihara
af9c608ca5 config: migrate ui.default-description to template alias
Since we now have the template alias, it's easy to migrate the config value
programatically. However, there are a couple of minor behavior changes:

 a. default description won't be used if user customized the
    draft_commit_description template.
 b. default description won't be inserted if trailer is added

For (a), I assumed user would inline the default description in the draft
template if they customized the template. (b) should be okay because the
trailers template is a new feature.
2025-04-21 14:39:47 +00:00
Yuya Nishihara
1525740247 docs: fix copy-paste error in draft_commit_description example 2025-04-21 14:39:47 +00:00
Martin von Zweigbergk
5ccd0dbe32 merge: make to_tree_merge() read trees concurrently 2025-04-21 13:31:45 +00:00
Yuya Nishihara
fc5f90c4ac annotate: reorganize functions as restartable process
I don't have any plan to implement incremental UI for file annotation, but I
think the new API is nicer in that they have fewer function arguments.

Note that this wouldn't help implement interactive UI to go ancestor annotation
by clicking annotated line. To achieve that cheaply, we'll need weave-like data.
2025-04-21 12:38:38 +00:00
Yuya Nishihara
5feed28f49 annotate: reorganize initialization of state to make process restartable 2025-04-21 12:38:38 +00:00
Yuya Nishihara
4a8074759c annotate: leave unresolved commits in commit_source_map
We can visit ancestors from these commits to continue annotation process.
2025-04-21 12:38:38 +00:00
Yuya Nishihara
00dd6df4d9 annotate: pack intermediate state into struct 2025-04-21 12:38:38 +00:00
Vincent Ging Ho Yim
24ab60b1c2 docs: split notes on jj commands into new column in Git command table 2025-04-21 00:45:01 +00:00
Vincent Ging Ho Yim
e90727b34d docs: convert Git command table from HTML to YAML
The YAML format is much easier to maintain and allows using Markdown instead of having
to escape symbols such as < and >.
2025-04-21 00:45:01 +00:00
Vincent Ging Ho Yim
8e15a2634a mkdocs: add table-reader plugin
This is used to render Markdown tables from YAML files.
2025-04-21 00:45:01 +00:00
Vincent Ging Ho Yim
e660846817 mkdocs: sort plugins nodes lexicographically 2025-04-21 00:45:01 +00:00
Vincent Ging Ho Yim
b9f83261db mkdocs: add 'Style guide' to nav sidebar 2025-04-21 00:38:01 +00:00
Martin von Zweigbergk
3c84a23b13 merge: add and use a function for mapping async functions concurrently
This adds `Merge::try_map_async()` and adds some callers.
2025-04-20 06:44:00 +00:00
Martin von Zweigbergk
ed99e21bc4 revset: rename StoreError variants to Backend
I haven't checked, but I think "store" is a holdover from a long time
ago.
2025-04-20 02:28:36 +00:00
Gaëtan Lehmann
7073811fe0 squash: don't add trailers to an empty description
ref: #6265
2025-04-19 16:25:03 +00:00
Martin von Zweigbergk
15351a2d92 templates: extract a template alias as hook for default commit description
This makes it easier to override just the default description without
having copy the whole default template (and having to keep it up to
date with new versions).
2025-04-19 05:42:52 +00:00
Yuya Nishihara
6cba6d7ec2 cli: git-push: do not move existing local bookmarks by --change
An existing "push-*" bookmark should usually be in position. If it wasn't
because of e.g split, I think the user should be aware of that and take an
explicit action.
2025-04-19 01:26:40 +00:00
Yuya Nishihara
03b37c6646 cli: git-push: extract helper function that checks bookmark name to create 2025-04-19 01:26:40 +00:00
Vincent Ging Ho Yim
183d4e1882 docs/index: use sentence case consistently for docs page names
Follow-up to 73e87bc.
2025-04-18 22:25:36 +00:00
Josh Steadmon
405331ba62 paid_contributors: sort contributor list
Make it easier to scan the list by sorting it (case-insensitive).
2025-04-18 20:30:59 +00:00
Gaëtan Lehmann
fae66ace9b split: add trailer support 2025-04-18 16:48:01 +00:00
Gaëtan Lehmann
6d240be777 squash: add trailer support 2025-04-18 16:35:16 +00:00
TimerErTim
ae1e831aa6 docs: mention openSUSE installation method 2025-04-17 18:34:12 +00:00
Yuya Nishihara
39f481f2da absorb: add basic support for file deletion
This works if the file was added and wasn't modified within the destination
range.

Closes #6140
2025-04-17 18:10:23 +00:00
Martin von Zweigbergk
e61971c1f3 repo: propagate error from edit() in update_wc_commits() 2025-04-17 14:41:50 +00:00
Gaëtan Lehmann
2a7b0211eb new: add trailer support 2025-04-17 13:29:49 +00:00
Martin von Zweigbergk
5ad7ea24d3 repo: propagate erros from update_heads() 2025-04-17 12:20:29 +00:00
Martin von Zweigbergk
4a8cbe43a2 repo: mark view dirty after setting local bookmark target
We were not calling `view.mark_dirty()` after adding heads, which
means that we would not enforce the usual view invariants, such that
ancestors of heads should not also be in the set of heads. This patch
fixes that and updates the test (which actually shows the broken
invariant in its current form).
2025-04-17 03:25:29 +00:00
Winter
af60f3d674 cli: deprecate ui.default-description
Closes https://github.com/jj-vcs/jj/issues/6298.
2025-04-17 02:03:48 +00:00
Austin Seipp
b1576c5c4e github: make dependabot updates weekly
A not-insignificant amount of our PR traffic is from Dependabot, even
with the grouped update feature (something like 20% of all PRs in total
are from Dependabot, last I checked.)

We don't really need daily updates, and with the the current crate
dependency graph we practically get updates *every* day. Bump it to
weekly instead to stem the tide a little.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-04-16 21:16:24 +00:00
Winter
001b6df26c docs: add edit link to pages
This leads users directly to GitHub's edit page for the page they're viewing.
2025-04-16 18:30:34 +00:00
Winter
96694a6891 docs: add GitHub repo link to header 2025-04-16 18:30:34 +00:00
dependabot[bot]
bd882fda0e cargo: bump proc-macro2 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [proc-macro2](https://github.com/dtolnay/proc-macro2).


Updates `proc-macro2` from 1.0.94 to 1.0.95
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.94...1.0.95)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-version: 1.0.95
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 16:05:55 +00:00
dependabot[bot]
182d276f69 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.49.49 to 2.49.50
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](be7c31b674...09dc018eee)

Updates `astral-sh/setup-uv` from 5.4.1 to 5.4.2
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](0c5e2b8115...d4b2f3b6ec)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-version: 5.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 16:05:39 +00:00
Gaëtan Lehmann
5f7159d4b1 commit: add trailer support 2025-04-16 14:21:35 +00:00
dependabot[bot]
bb4d0b9ac2 cargo: bump libc from 0.2.171 to 0.2.172 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [libc](https://github.com/rust-lang/libc).


Updates `libc` from 0.2.171 to 0.2.172
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.172/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.171...0.2.172)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.172
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 20:58:57 +00:00
Yuya Nishihara
81344e6828 merge: clarify expectation of with_new_file_ids() 2025-04-15 15:08:47 +00:00
Martin von Zweigbergk
7cab444313 repo_path: remove assertion from constructors
We ran into a crash on our server at Google today because we
accidentally called `RepoPathBuf::from_internal_string()` with a
string starting with a '/', which resulted in a the assertion in that
function failing. This patch changes that constructor and its siblings
to return a `Result` instead.
2025-04-15 14:42:23 +00:00
Martin von Zweigbergk
f0545ee25c test: introduce test helpers for creating repo path types
I'm about to make the constructors return a `Result`. The helpers will
hide the unwrapping.
2025-04-15 14:42:23 +00:00
Martin von Zweigbergk
094ab0b2bb tree: delete entries() method, update MergedTree test
`Tree::entries()` was only used in one test case. We can just as well
use `MergedTree::entries()` there.
2025-04-15 13:14:46 +00:00
Martin von Zweigbergk
0f92978231 tree: delete unused entry() method 2025-04-15 13:14:46 +00:00
Gaëtan Lehmann
f6e3f38b94 templates: add commonly used trailers
namely Signed-off-by and Change-Id

`format_signed_off_by_trailer` will be formatted properly if the author
name is not set, but will contain the email placeholder if the author
email is not set, as I haven't found a way to make the template
generation fail.

`format_gerrit_change_id_trailer` is based on jj's change id, but it
needed to be padded to reach 40 characters. Zero-padding is kind of
boring so I've used `6a6a6964`, the hexadecimal representation of `jjid`
in ascii.

Because the trailer value runs up to the end of the line, they are
all terminated with a new line. This way it's also convenient to
define these trailers in the `commit_trailers` template:

  [templates]
  commit_trailers = '''
    format_signed_off_by_trailer(self)
    ++ format_gerrit_change_id_trailer(self)
  '''
2025-04-15 05:16:16 +00:00
dependabot[bot]
49570b3ef0 cargo: bump clap from 4.5.35 to 4.5.36 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.35 to 4.5.36
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.35...clap_complete-v4.5.36)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 03:10:06 +00:00
dependabot[bot]
bacc609f2b github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.47 to 2.49.49
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](a48a50298f...be7c31b674)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 02:00:13 +00:00
Yuya Nishihara
26ffaf4ac2 repo: reimplement merge of working-copy commits in the same way as other refs
I don't think we need to optimize it for new_id == self_id, so I implemented the
fallback path as precise as the documentation.
2025-04-14 01:07:30 +00:00
Yuya Nishihara
a1d88980a9 revset: compare conflict file contents without materializing as conflicts
The logic is similar to the color-words diff's. We first resolve trivial
conflicts, then compare each hunk of Merge<&BStr> type. We also apply the same
optimization as the resolved case to minimize lines to be merged and diffed.
2025-04-14 01:07:24 +00:00
Winter
f3c4cc2155 cli: duplicate: add setting for templating the new commit descriptions
This allows the customization of the duplicated commit descriptions.

An ideal use case for this is emulating `git cherry-pick -x`, as
illustrated in the tests.
2025-04-13 21:49:47 +00:00
Gaëtan Lehmann
ee0e86fa63 lib: only allow non-trailer when the trailers contain a git generated trailer
namely Signed-off-by and cherry-pick trailers. The cherry-pick trailer
doesn't appear in the parsed trailer though, to match the
`git interpret-trailers` implementation.

see https://github.com/git/git/blob/master/trailer.c for reference
2025-04-13 20:36:11 +00:00
Gaëtan Lehmann
71a0194ad1 describe: add trailer support
Add a new `template.commit_trailer` configuration option. This template
is used to add some trailers to the commit description.

A new trailer paragraph is created if no trailer paragraph is found in
the commit description.
The trailer is not added to the trailer paragraph when the trailer is
already present, or if the commit description is empty.
2025-04-13 20:36:11 +00:00
Gaëtan Lehmann
3c233c0f45 lib: add trailer module for commit trailers
To be used for parsing `Change-Id`, `Signed-off-by` and other
`Co-authored-by` trailers from commits

Co-authored-by: Austin Seipp <aseipp@pobox.com>
2025-04-13 20:36:11 +00:00
Martin von Zweigbergk
c259eb7413 revset: don't crash if revset engine returns non-BackendError
We call `RevsetEvaluationError::expect_backend_error()` in lots of
places. If the revset engine had returned a non-`BackendError`, it
would result in a panic. For example, a revset engine might return
something like `RevsetEvaluationError::Other("not supported".into())`
for some revsets. We shouldn't crash when this happens.

We may want to create some higher-level `RepoError` to return here,
but for now let's just avoid the crash.
2025-04-13 18:50:39 +00:00
Emily
af67d1d8c4 git: work around gix global remote configuration issue
Closes: #6324
2025-04-13 13:55:09 +00:00
Emily
045b17e38e tests: test behaviour of global Git remote configuration 2025-04-13 13:55:09 +00:00
Emily
027350122f tests: allow overriding more environment variables 2025-04-13 13:55:09 +00:00
Yuya Nishihara
a2bd7b30ad merge: remove redundant .clone() from .simplify()
It no longer makes sense that .simplify() requires ownership. It clones values
internally.
2025-04-13 05:18:16 +00:00
Yuya Nishihara
81b2f1a1fb merge-tools: fix builtin merge editor to not ignore executable bit
Exec-bit conflicts are resolved separately, but we still need to copy them to
UI to get back the original state.
2025-04-13 05:18:11 +00:00
Yuya Nishihara
fa00775f94 cli: resolve: do not use .with_new_file_ids() to construct resolved file value
This reverts a01d0bf7738f "cli: resolve: leave executable bit unchanged when
using external tool", and updates handling of resolved content.

Fixes #6250
2025-04-13 02:02:58 +00:00
Yuya Nishihara
ace0a52eb1 cli: resolve: error out early if conflict in exec bit couldn't be resolved
As Martin suggested, "we should instead make it an error to try to resolve the
content-level conflict before resolving the executable-bit-level conflict. I
think we should do the same when merging copy records."

https://github.com/jj-vcs/jj/pull/6288#pullrequestreview-2751367755
2025-04-13 02:02:58 +00:00
Yuya Nishihara
b28bc420ab merge: do not map absent tree entry to non-executable
We're going to make "jj resolve" error out on exec bit conflict. This behavior
should be more consistent with tree::try_resolve_file_conflict(), which exits
early if the tree value had absent entries.

Since unchanged exec bit shouldn't be lost when resolving change-delete content
conflict, the resolution function falls back to the original state if the
exec-bit conflict is resolved to None.
2025-04-13 02:02:58 +00:00
Yuya Nishihara
ecee441fb3 merge: use .try_map() internally to transform Merge<TreeValue>
This seems a bit easier to follow than nesting Option types.
try_resolve_file_conflict() doesn't have this problem, but it is the only caller
of maybe_map(), so I've gone ahead and replaced it, too.

The return type is unchanged because it looked equally bad if to_tree_merge()
returned Result<Result<Merge<Tree>, ()>, BackendError>.
2025-04-13 02:02:58 +00:00
Winter
0c53293a9e nix: fix tests in darwin sandbox 2025-04-12 21:54:58 +00:00
Nils Koch
fbaa51b4ce revset: add signed function 2025-04-12 14:14:26 +00:00
Yuya Nishihara
1e94ee263b cli: resolve: update "not normal files" error message and formatting
try_materialize_file_conflict_value() and to_file_merge() shouldn't fail because
of exec bit changes. And error.to_string() shouldn't include trailing newline.
2025-04-12 08:58:05 +00:00
Yuya Nishihara
d2caf6f1d9 cli: resolve: use try_materialize_file_conflict_value()
The executable bit handling will be a bit more involved in order to get around
#6250.
2025-04-12 08:58:05 +00:00
Yuya Nishihara
e4bbb5b14d conflicts: store unsimplified file ids in MaterializedFileConflictValue 2025-04-12 08:58:05 +00:00
Yuya Nishihara
a856494ab4 conflicts: extract function that materializes file conflict
This will be used in "jj resolve".
2025-04-12 08:58:05 +00:00
Yuya Nishihara
f047addfe0 conflicts: pack MaterializedTreeValue::FileConflict fields into struct
I'll add a function that returns Option<MaterializedFileConflictValue> instead
of MaterializedTreeValue.
2025-04-12 08:58:05 +00:00
Yuya Nishihara
8bcb806eed op_store: attach path context to read/write errors
This will probably help debug weird problem like #6287. File names are a bit
redundant for ReadObject errors (which include ObjectId), but that should be
okay.
2025-04-12 06:56:13 +00:00
Yuya Nishihara
5c6ab172b8 transaction: propagate error from tx.write()
#6287
2025-04-12 06:56:13 +00:00
Yuya Nishihara
3d98f59215 transaction: narrow scope of local variables needed to create Operation object
I feel this is easier to follow.
2025-04-12 06:56:13 +00:00
Yuya Nishihara
f587374d37 transaction: wrap tx.commit() error in enum
This helps propagate error from tx.write(). I made the error variants
non-transparent because it's easier than manually implementing From<> that maps
each inner error to the other inner error.
2025-04-12 06:56:13 +00:00
Yuya Nishihara
1305912fcf git_backend: load "git gc" executable name from settings 2025-04-12 01:43:35 +00:00
Yuya Nishihara
bae809a572 git_backend: pass GitSettings in to GitBackend::new()
The GitSettings object is passed by value as we're going to extract
executable_path from it, and GitBackend::new() is private.
2025-04-12 01:43:35 +00:00
Yuya Nishihara
60b96122f2 cleanup: use new array methods instead of .collect_tuple()/.next_tuple()
It's more obvious that the element types are the same.
2025-04-12 00:58:14 +00:00
Winter
601548061d config-schema: add templates 2025-04-12 00:20:46 +00:00
Caleb White
3efb59426f abandon: convert deleted bookmark warning to hint
Bookmarks are no longer deleted on the next `jj git push` command, so
this updates the language and downgrades the warning to a hint.
2025-04-11 01:35:07 +00:00
Winter
8eb4eb74d6 nix: disable incremental builds for the dev shell
Latest rustc nightlies seem to have broken incremental compilation [0].

I apparently have the worst luck in this department, as I've gotten multiple
ICEs within the last few hours alone. So, I'm putting future me out of
her misery.

[0]: https://github.com/rust-lang/rust/issues/139110
2025-04-10 23:06:48 +00:00
Martin von Zweigbergk
42bf17936f docs: add a style guide
#5685
2025-04-10 22:01:48 +00:00
Winter
4bd6dbe83c nix: flake update 2025-04-10 20:08:09 +00:00
Emily
8c506006b2 docs: add diagrams for jj new --after and --before 2025-04-10 19:00:34 +00:00
dependabot[bot]
6dbc1eba42 cargo: bump itertools in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [itertools](https://github.com/rust-itertools/itertools).


Updates `itertools` from 0.13.0 to 0.14.0
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: itertools
  dependency-version: 0.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 17:49:02 +00:00
Yuya Nishihara
5dd64ae978 git_backend: minor cleanup in tests
We don't have to create separate temporary directories.
2025-04-10 05:10:47 +00:00
Yuya Nishihara
d16da967c9 git_backend: rename variables per git.write-change-id-header config name 2025-04-10 05:10:47 +00:00
Yuya Nishihara
c4767c3de4 git_backend: do not ignore config error at loading path 2025-04-10 05:10:47 +00:00
dependabot[bot]
22128972b0 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.45 to 2.49.47
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](d4635f2de6...a48a50298f)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.47
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 18:13:45 +00:00
Martin von Zweigbergk
2ce150b6c9 cargo: run cargo update to update off of yanked crossbeam-channel 2025-04-09 17:23:29 +00:00
Benjamin Brittain
0b6d0a7a75 git_backend: derive the change ID from the git change-id header
When read/writing commits from the git-backend, populate the git commit
header with a backwards hash of the `change-id`. This should enable
preserving change identity across various git remotes assuming a
cooperative git server that doesn't strip the git header.

This feature is behind a `git.write-change-id-header` configuration flag
at least to start.
2025-04-09 16:42:56 +00:00
Emily
e5478bbf7b cargo: use gix/zlib-rs feature
This uses `zlib-rs`, a native Rust library that is comparable in
performance to `zlib-ng`. Since there’s no complicated C build
and gitoxide only has one hashing backend now, this lets us drop our
`packaging` feature without adding any awkward build requirements.

`zlib-rs` is generally faster at decompression than
`zlib-ng`, and faster at compression on levels 6 and 9; see
<https://trifectatech.org/blog/zlib-rs-is-faster-than-c/>
for details.

I couldn’t get reliable‐looking benchmark results out of my
temperamental laptop; `hyperfine` seemed to think that some random
`jj` workloads I tested might be slightly slower than with `zlib-ng`,
but it wasn’t unambiguously distinguishable from noise, so I’d
like to see measurements from others.

It’s certainly a lot faster than the previous default, and I
think it’s likely that `zlib-rs` will continue to get faster
and that it’s more than worth avoiding the headaches of a native
library with a CMake build dependency. (Though on the other hand,
if distributions move in the direction of shipping `zlib-ng` by
default, maybe there will be more motivation to make `libz-ng-sys`
support system libraries.)
2025-04-08 22:12:25 +00:00
Philip Metzger
1716b7f8d3 docs: add some more testimonials
This is cobbled together from the "new" appreciation channel and something I saw on the Rust Zulip.
2025-04-08 20:55:09 +00:00
dependabot[bot]
d92b782dad github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.28.14 to 3.28.15
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](fc7e4a0fa0...45775bd823)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 18:40:05 +00:00
Emily
333d47f5e5 git: add deprecation warnings for git.subprocess 2025-04-08 10:36:46 +00:00
Yuya Nishihara
b63ab33b10 diff: add option to render color-words diffs without materializing conflicts
The original idea was to flatten left/right conflict trees and pair up adjacent
negative/positive terms. For example, diff(A, B-C+D) could be rendered as
diff(A, B) and diff(C, D). The problem of this formalization is that one of the
diff pairs is often empty (because e.g. A=B), so the context is fully omitted.
The resulting diff(C, D) doesn't provide any notion why the hunk is conflicted,
and how it is different from A.

Instead, this patch implements diffs in which each left/right pair is compared.
In the example above, the left terms are padded, and the diffs are rendered as
diff(A, B), diff(-A, -C), diff(A, D). This appears to be working reasonably well
so long as either side is resolved or both sides have the same numbers of terms.

Closes #4062
2025-04-08 09:12:39 +00:00
Yuya Nishihara
b24efa8a8d diff: parameterize "removed"/"added" labels in color-words diffs 2025-04-08 09:12:39 +00:00
Yuya Nishihara
8af238f68a diff: test identical contents early when rendering color-words diffs
This helps extract hunk rendering function for non-materialized color-words
diffs. In conflict hunk, an identical diff pair will be omitted with "..."
marker.

This patch introduces a subtle behavior change when "ignore whitespace" options
are used. Before, "..."  wouldn't be printed if contents differ only in
whitespace. I don't think the new behavior is bad because the file header says
"Modified regular file" in that case.
2025-04-08 09:12:39 +00:00
Yuya Nishihara
4ae00a6cb4 tests: add samples of conflict diff rendering 2025-04-08 09:12:39 +00:00
Yuya Nishihara
51d4f49b4c files: add iterator adaptor that splits hunks into left/right merges 2025-04-08 09:12:39 +00:00
Yuya Nishihara
6d009b3453 diff: make FileContent generic
I'll make color-words diffs optionally compare Merge<BString> without
materializing conflicts.
2025-04-08 09:12:39 +00:00
Aleksey Kuznetsov
3eaff83bca cli: Add config setting for bookmark list sort order
This is a conclusion of #5849.
Config setting to list bookmarks in specified order.

Closes #3831
2025-04-08 08:06:29 +00:00
Martin von Zweigbergk
3e51038dc5
release: merge 0.28 release branch into main 2025-04-07 22:04:11 -07:00
Winter
e77f5cea6d github: don't explicitly set contents: read for zizmor check
It's implied for public repositories.
2025-04-08 03:26:10 +00:00
Yuya Nishihara
af4f7a7811 cli: describe: reuse author field set to temporary builders
I originally thought we could remove args.reset_author tests in later pass, but
we can't because author timestamp may be updated if a commit is discardable.
Still it's nice that we can deduplicate some logic.
2025-04-08 02:11:53 +00:00
Yuya Nishihara
a7227b959f cli: describe: build temporary commit builders unconditionally
This should help implement trailer handling, in which the trailer template
should see the new author as well as the committer.
2025-04-08 02:11:53 +00:00
Yuya Nishihara
38ecdcf62e files: add functions that return merge result as Merge<_>/Option<_>
The version that returns Merge<_> will be used in diff functions. The added
helper functions will also be used in order to apply word-level merging.

Since we cannot express FnOnce(impl IntoIterator<..>) where the argument type is
controlled by callee, this patch adds helper trait to bridge collect_*()
functions.
2025-04-08 01:35:55 +00:00
Yuya Nishihara
624cb66210 files: rename merge() to merge_hunks()
I'll add new merge() function that returns the result as Merge<BString>.
2025-04-08 01:35:55 +00:00
Yuya Nishihara
8b32eafb9a files: extract iterator from merge_hunks()
This helps implement variants of file::merge() that return Merge<BString> or
Option<BString>. collect_hunks() could be implemented as FromIterator for
MergeResult. It would enable .collect(), but I don't think this abstraction
would generally be useful. The other types to which we want to collect the
outcome is Option<BString> and Merge<BString>. They could technically implement
FromIterator, but it would be weird if .collect() into Merge<BString> panicked
because of incompatible merge shapes.

Merge::resolved() is marked as const fn to clarify it is a trivial constructor.
2025-04-08 01:35:55 +00:00
Yuya Nishihara
7a7e62c0c4 files: document merge() function and result type, rename parameter
I'm going to add variants of this function.
2025-04-08 01:35:55 +00:00
Yuya Nishihara
10328967a1 tests: remove files::merge() wrapper, construct Merge in alternate form 2025-04-08 01:35:55 +00:00
Yuya Nishihara
8b949f7e93 tests: use indoc! {""} to format multi-line merge inputs 2025-04-08 01:35:55 +00:00
Remo Senekowitsch
5a50e49d70 docs: update list of builtin merge tools 2025-04-08 01:17:27 +00:00
Austin Seipp
907c62018b cargo: update prost to 0.13.5
We haven't updated `prost` in a while, and the reason for that is
probably because the code generation output slightly changed, which
would have caused dependabot to exclude that package from its updates as
a failure. (At least, I suspect that's what happened.)

This lets us drop a dependency on `itertools 0.12.x` because the prost
0.13.5 release weakened its constraints, allowing `itertools 0.13.x`.
Now we only depend on two major versions of itertools (0.10 + 0.12)
instead of three (previously four).

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-04-08 01:02:25 +00:00
Ilya Grigoriev
5dc9da3c2b cargo: general cargo update 2025-04-07 19:38:29 +00:00
dependabot[bot]
977ea23cb6 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.49.44 to 2.49.45
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](f1390fd0d8...d4635f2de6)

Updates `github/codeql-action` from 3.28.13 to 3.28.14
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b549b9259...fc7e4a0fa0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-version: 3.28.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 19:02:15 +00:00
dependabot[bot]
2314acf710 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [indexmap](https://github.com/indexmap-rs/indexmap) and [tokio](https://github.com/tokio-rs/tokio).


Updates `indexmap` from 2.8.0 to 2.9.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.8.0...2.9.0)

Updates `tokio` from 1.44.1 to 1.44.2
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.1...tokio-1.44.2)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-version: 2.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: tokio
  dependency-version: 1.44.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 18:21:05 +00:00
Martin von Zweigbergk
40d9256a11 fix: make FileFixer::fix_files() take &mut self
It can be useful for fixers to record some information about the
inputs they processed. For example, the `FileFixer` we use in our
server at Google get more detailed error information back from its
formatter tools. We may want to record these errors in our
`FileFixer`'s state so we can then return that to the client.
2025-04-07 15:43:47 +00:00
Yuya Nishihara
b9ebe2f03c release: 0.28.2 2025-04-07 08:42:32 -07:00
Yuya Nishihara
75d3a8c953 cargo: bump tokio to 1.44.2
https://rustsec.org/advisories/RUSTSEC-2025-0023
2025-04-07 08:42:32 -07:00
Yuya Nishihara
a5d672ca0f cargo: bump gix and its dependencies recursively
This should fix git::import_refs() issue with gix 0.71.0. Old commits could be
repopulated by importing stale refs stored in packed-refs.

https://github.com/GitoxideLabs/gitoxide/issues/1928

The Zlib license is added to the allow list because foldhash appears in the
dependency chain.
2025-04-07 08:42:32 -07:00
Philip Metzger
1b7fda946e docs: Add Jujutsu's Developers core values
This was taken out of the "Jujutsu from first principles" doc in another PR. It represents some of the common ideas
which the project had around a year ago.

I think this can be modernized if the maintainers want it.
2025-04-07 13:44:02 +00:00
Yuya Nishihara
30134e523b cargo: bump gix and its dependencies recursively
This should fix git::import_refs() issue with gix 0.71.0. Old commits could be
repopulated by importing stale refs stored in packed-refs.

https://github.com/GitoxideLabs/gitoxide/issues/1928

The Zlib license is added to the allow list because foldhash appears in the
dependency chain.
2025-04-06 10:59:40 +00:00
Emily
75bad02aec github: give the dragon a promotion
I think this check will only be useful if it actually blocks the
build. Since the resolution is simple (just bump the limit), I think
it’s okay to add a small amount of friction and ask people to take
a moment to consider other options first.
2025-04-05 17:53:39 +00:00
George Christou
47b7717e69 cli show: add --no-patch flag 2025-04-05 16:41:32 +00:00
Benjamin Tan
4b03108c19 rewrite: move_commits: use MutableRepo::transform_commits
This allows the removal of some duplicated code which performs the
topological sorting of commits to be moved.
2025-04-05 16:03:03 +00:00
Benjamin Tan
2a647e6911 repo: MutableRepo: add transform_commits function
This function is similar to `transform_descendants_with_options`, but
only rewrites the given commits and not their descendants. This will
allow `rewrite::move_commits` to transform a given set of commits
without iterating through their descendants twice (once when computing
new parents and once when rewriting commits).
2025-04-05 16:03:03 +00:00
Benjamin Tan
61ef7d9af3 repo: MutableRepo::transform_descendants_with_options: add new_parents_map
The `new_parents_map` will allow the parents of each commit to be
customized before the `transform_descendents` callback is invoked. This
is useful when the order of commits needs to be changed, whereby setting
the new parents in the default callback might lead to repeated rebasing
and cycles if the new parent has not been visited in the reverse
topological order traversal.
2025-04-05 16:03:03 +00:00
Benjamin Tan
17386f59ed repo: MutableRepo: split find_descendants_to_rebase into two functions
`MutableRepo::find_descendants_to_rebase` was finding descendants to
rebase, and ordering them. This is now split into two functions.
2025-04-05 16:03:03 +00:00
Remo Senekowitsch
b85bfa7221 cli: bookmark: list: update hint about deleted bookmarks
Deleting bookmarks now requires the --deleted flag.
(since: fb1a27ff09d2466f96316c0b894e2116dfb80b23 )
2025-04-05 14:04:54 +00:00
Emily
97c6fb979b changelog: add missing release highlights section
It would be tragic if 0.29 didn’t have any of these.
2025-04-05 07:30:10 +00:00
Waleed Khan
3aac8d21e6 release: 0.28.1 2025-04-05 03:45:18 +00:00
Ilya Grigoriev
bedfb1ff51 tests: make git push --named tests demo differences in subprocess and libgit2 logic
Follows up on https://github.com/jj-vcs/jj/pull/5698 and
https://github.com/jj-vcs/jj/pull/6236.

The behavior of `--named` in corner cases differs. This wasn't detected
in #5698 because of a bug in the test that was fixed in #6236. This
commit better illustrates the more important case where the behavior is
the same, and the less important case where it differs.
2025-04-05 03:18:36 +00:00
Ilya Grigoriev
bd3cc7828a tests: add one more commit to git push --named test
Makes the diff in the following commit simpler.
2025-04-05 03:18:36 +00:00
Ilya Grigoriev
91e2ce69dc github: show counts and allow offline counting for dependency-counting check
Follow-up to b1bb5e1

This creates a `.github/scripts/count-cargo-lock-packages` script to
count packages with our methodology that one can run outside CI.

I also renamed the check so that it's clearer what it does.
2025-04-05 02:21:17 +00:00
Aleksey Kuznetsov
29f24ad2d8 docs: Mention Helix with taplo for config.toml validation
Not sure if Helix is considered popular but I thought
it should be mentioned anyway :)
2025-04-04 21:19:45 +00:00
Austin Seipp
b1bb5e1cf9 github: ward off future dependency bloat via dragon
After some discussion on Discord yesterday, Emily floated this idea to
have a check that fails if `Cargo.lock` has too many dependencies, where
"too many" means "more than a random number I made up and sounds good."

This implements that, as a non-required check, and to do so it invokes
the power of an extremely evil and annoying Dragon. We could also ask
this Dragon to do other things too I suppose (pending future contract
negotiations).

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-04-04 21:19:23 +00:00
dependabot[bot]
a31c811265 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.43 to 2.49.44
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](575f713d02...f1390fd0d8)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.49.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 16:27:08 +00:00
Nicole Patricia Mazzuca
02687ac7e5 docs: standardize on .method() syntax
In docs/templates.md, the Commit type and Operation type looked like:

`method() -> ReturnType`

rather than everything else, which looks like:

`.method() -> ReturnType`

This commit changes the Commit and Operation types to look more like the
rest of the documentation.
2025-04-04 13:58:33 +00:00
Emily
370c624518 tests: add TestEnvironment::with_git_subprocess helper 2025-04-04 09:50:20 +00:00
Emily
350da7d013 cargo: bump gix to 0.71.0
Fix GHSA-794x-2rpg-rfgr.

`gix::Repository::work_dir` was renamed to `workdir` (though strangely
not the `gix::ThreadSafeRepository` version), and `lossy_config`
is now off by default in all configurations.
2025-04-04 04:28:42 +00:00
Emily
c488a4030f tests: fix subprocess toggle in git push --named tests 2025-04-04 03:02:01 +00:00
Yuya Nishihara
7146a99029 cargo: remove redundant default-features=false from testutils
There are no feature flags anymore.
2025-04-04 02:08:54 +00:00
Yuya Nishihara
a027f8f659 tests: port test_workspaces.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Yuya Nishihara
147b181368 tests: port test_working_copy.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Yuya Nishihara
37fd0f1256 tests: port test_util_command.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Yuya Nishihara
1e5d33beab tests: port test_undo.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Yuya Nishihara
0ada5bd868 tests: port test_templater.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Yuya Nishihara
8d8b760299 tests: port test_tag_command.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Yuya Nishihara
16c788aac5 tests: port test_status_command.rs to TestWorkDir API 2025-04-04 01:48:39 +00:00
Scott Taylor
12add9b35e merge_tools: fix :ours and :theirs merge tools
These merge tools didn't work properly on conflicted trees with more
than two sides since they didn't simplify the conflict before resolving.
The new implementation is more similar to how external merge tools are
executed, which should give a more consistent behavior.
2025-04-04 01:38:33 +00:00
Scott Taylor
a7b112ecb8 merge_tools: rename content to simplified_file_content
This makes it more clear that this is the only field in `MergeToolFile`
which is simplified.
2025-04-04 01:38:33 +00:00
Scott Taylor
466d237a06 merge_tools: demo issue with :ours and :theirs 2025-04-04 01:38:33 +00:00
Emily
71a619c19f tests: don’t use git2 in testutils 2025-04-03 19:03:44 +00:00
Emily
f14b3bf9a8 git: respect GIT_* environment variables in git2 tests
Previously, this was calling `git_repository_open()`,
which is equivalent to `git_repository_open_ext()` with
`flags = GIT_REPOSITORY_OPEN_NO_SEARCH` and `ceiling_dirs =
NULL`. This changes `ceiling_dirs` to an empty string, and adds
`GIT_REPOSITORY_OPEN_FROM_ENV` to `flags` when we’re in test code.

`GIT_REPOSITORY_OPEN_FROM_ENV` is used to respect the Git configuration
path environment variables, which is what we want for the test
hermeticity code. It works like this:

* `config_path_system` will use `$GIT_CONFIG_SYSTEM` because `use_env`
  will be set.
  
* `config_path_global` will use `$GIT_CONFIG_GLOBAL` because `use_env`
  will be set.
  
* `git_config__find_xdg` and `git_config__find_programdata` will find
  impure system paths and load them even when `$GIT_CONFIG_GLOBAL` is
  set, contrary to Git behaviour, so we need to set `$XDG_CONFIG_HOME`
  and `$PROGAMDATA`.

It has a few other effects, which I will exhaustively enumerate to
show that they are benign:

* It respects `$GIT_WORK_TREE` and `$GIT_COMMON_DIR`. These would
  already break our tests, I think, so we’re assuming they’re
  not set. (Possibly we should set them explicitly.)

* When opening a repository, it will:

  * Set the starting path for the search to `$GIT_DIR` if it’s
    `NULL`, but we do set it, so no change.

  * Initialize `ceiling_dirs` to `$GIT_CEILING_DIRECTORIES` if it’s
    `NULL`, but we do set it, so no change.

  * Respect `$GIT_DISCOVERY_ACROSS_FILESYSTEM` and set the
    `GIT_REPOSITORY_OPEN_CROSS_FS` flag appropriately. However,
    this is only checked on subsequent iterations of the loop in
    `find_repo_traverse`, and we set `GIT_REPOSITORY_OPEN_NO_SEARCH`
    which causes it to never enter a second iteration.

  * Use `ceiling_dirs` in `find_ceiling_dir_offset`, but the result is
    ignored when `GIT_REPOSITORY_OPEN_NO_SEARCH` is set, so changing
    from `NULL` to the empty string doesn’t affect behaviour. (It
    also would always return the same result for either value, anyway.)
2025-04-03 19:03:44 +00:00
Yuya Nishihara
0bcdedc159 tests: add missing insta::allow_duplicates! { .. }
Fixes #6230
2025-04-03 15:42:07 +00:00
Ilya Grigoriev
fa0b901c07 cli external merge tools: add tracing when reparsing conflicts
I was trying to debug `mergiraf` behavior, and having such diagnostic
would have been helpful.
2025-04-03 02:11:21 +00:00
Yuya Nishihara
ea6a768517 tests: port test_squash_command.rs to TestWorkDir API 2025-04-03 01:43:02 +00:00
Yuya Nishihara
87a35d5bb2 tests: port test_split_command.rs to TestWorkDir API 2025-04-03 01:43:02 +00:00
Yuya Nishihara
f2ca6b7740 tests: set up fake editor early in test_{split,squash}_command.rs
This helps avoid borrowing issue.
2025-04-03 01:43:02 +00:00
Yuya Nishihara
4faf07c872 tests: port test_sparse_command.rs to TestWorkDir API 2025-04-03 01:43:02 +00:00
Yuya Nishihara
87a78b6953 tests: port test_simplify_parents_command.rs to TestWorkDir API 2025-04-03 01:43:02 +00:00
Yuya Nishihara
3ce077de61 tests: inline create_repo() to callers in test_simplify_parents_command.rs
TestWorkDir will have to be borrowed from TestEnvironment, and the remaining
codes would be just "git init repo".
2025-04-03 01:43:02 +00:00
Yuya Nishihara
27824420aa tests: port test_sign_unsign_commands.rs to TestWorkDir API 2025-04-03 01:43:02 +00:00
Yuya Nishihara
b1bc934bad tests: port test_show_command.rs to TestWorkDir API 2025-04-03 01:43:02 +00:00
Waleed Khan
3a98c4e849 release: 0.28.0 2025-04-02 23:52:10 +00:00
Waleed Khan
5a63fd9628 docs: update contributor command
- Now computes the latest tag automatically in the first suggested command.
- Now excludes dependabot.

I used the command to generate the list of contributors in the v0.28.0 release commit.
2025-04-02 23:52:10 +00:00
Scott Taylor
a902e63e65 merge_tools: add ":ours" and ":theirs" merge tools
We may want to implement these as options to `jj resolve` instead in the
future, but this is a simpler solution for now.
2025-04-02 22:09:15 +00:00
Scott Taylor
238a5587b0 merge_tools: split DiffTool and MergeTool enums 2025-04-02 22:09:15 +00:00
Kenyon Ralph
a1d2246cc2 FAQ: fix typo 2025-04-02 21:05:05 +00:00
dependabot[bot]
fb91f4ae6f github: bump rui314/setup-mold in the github-dependencies group
Bumps the github-dependencies group with 1 update: [rui314/setup-mold](https://github.com/rui314/setup-mold).


Updates `rui314/setup-mold` from f80524ca6eeaa76759b57fb78ddce5d87a20c720 to e16410e7f8d9e167b74ad5697a9089a35126eb50
- [Commits](f80524ca6e...e16410e7f8)

---
updated-dependencies:
- dependency-name: rui314/setup-mold
  dependency-type: direct:production
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 19:03:20 +00:00
Aleksey Kuznetsov
3c907cc019 cli: Add --sort option to the bookmark list command
This allows different sort orders for the bookmarks list, not only
alphabetically (by name).

Relates to #3831
2025-04-02 14:46:20 +00:00
Aleksey Kuznetsov
20dd35c1d5 commit: Make Commit::store_commit return &Arc<backend::Commit>
The underlying type is already under Arc
2025-04-02 14:46:20 +00:00
Nick Pupko
2ba756cc1d docs: replace outdated method in the Signature type
The `username()` is deprecated, but `email().local()` technically does the same now
2025-04-02 14:20:01 +00:00
Martin von Zweigbergk
9763143d65 fix: always rewrite descendants of fixed commits 2025-04-02 14:04:08 +00:00
Martin von Zweigbergk
70cd3ea67f fix: show bug when descendant is already fixed
If a commit needs fixing but its descendant does not, we currently
skip rewriting the descendant, which means it will instead be rebased
when the transactions finished. That usually results in
conflicts. This adds a test case for that case.
2025-04-02 14:04:08 +00:00
Siva Mahadevan
24cd079307 templates: create new git_format_patch_email_headers template
With this template, a 'git format-patch' compatible
email message can be generated using something like:

jj show --git --template git_format_patch_email_headers <rev>
2025-04-02 13:16:47 +00:00
dependabot[bot]
e67eba3abf cargo: bump the cargo-dependencies group across 1 directory with 2 updates
Bumps the cargo-dependencies group with 2 updates in the / directory: [clap](https://github.com/clap-rs/clap) and [rustix](https://github.com/bytecodealliance/rustix).


Updates `clap` from 4.5.34 to 4.5.35
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.34...clap_complete-v4.5.35)

Updates `rustix` from 1.0.3 to 1.0.5
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.3...v1.0.5)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: rustix
  dependency-version: 1.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 18:48:41 +00:00
dependabot[bot]
00eae152b1 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.40 to 2.49.43
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](daa3c1f1f9...575f713d02)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 17:46:43 +00:00
Ilya Grigoriev
ecde53c5bb cli completion: add completion for jj git push --named
Follow-up to https://github.com/jj-vcs/jj/pull/5698
2025-04-01 16:56:33 +00:00
Remo Senekowitsch
fb1a27ff09 cli: require --deleted to push deleted bookmarks 2025-04-01 15:58:50 +00:00
Yuya Nishihara
5db62a6b96 tests: port test_revset_output.rs to TestWorkDir API 2025-04-01 15:00:17 +00:00
Yuya Nishihara
f1a52d8504 tests: port test_revert_command.rs to TestWorkDir API 2025-04-01 15:00:17 +00:00
Yuya Nishihara
4d4de68c91 tests: port test_restore_command.rs to TestWorkDir API 2025-04-01 15:00:17 +00:00
Yuya Nishihara
3ba2ea7ace tests: port test_resolve_command.rs to TestWorkDir API 2025-04-01 15:00:17 +00:00
Yuya Nishihara
169a8b3f35 tests: set up fake editor early in test_{resolve,restore}_command.rs
This helps avoid borrowing issue in the next patch.
2025-04-01 15:00:17 +00:00
Yuya Nishihara
4bcebe678c cli: pad empty description intro/instruction lines with "JJ:"
Since 7618b52b "cli: consider 'JJ:' lines as comments also when not followed by
space", lines starting with "JJ:" (without space) are also ignored. We can
simply add "JJ:" prefix to empty intro/instruction lines.

Closes #5484
2025-04-01 02:02:02 +00:00
Yuya Nishihara
fd05b6f4cb cli: make description template insert blank line if old description was empty
This helps detect whether the last line is "JJ:" instruction or not. It seems
also nice that I don't have to insert newline to reflow the edited paragraph.
2025-04-01 02:02:02 +00:00
Ilya Grigoriev
bb902b69b5 Revert "git_backend: stop writing tree ids to proto"
This reverts commit b8ca9ae and adds a comment.

In commit 4d42604 (a year ago), we started writing the trees involved in
conflicted commits to the Git commit object in addition to the proto
storage. We validated that the two record matched when reading commits
until as recently as f7b14be (about a week ago). Just after that, in
commit b8ca9ae, we stopped writing the tree ids to the proto storage.
That means that any version of jj between 4d42604 and f7b14be would
error fail when reading a conflicted commit created by new versions.
While we don't guarantee forward compatibility, it's easy to do so here,
so let's be friendly to older versions by rolling back b8ca9ae so we
continue to write the conflicted tree ids to both places for a while
more.

(Thanks to Martin for the detailed explanation!)

Fixes https://github.com/jj-vcs/jj/issues/6185
2025-03-31 23:19:21 +00:00
Martin von Zweigbergk
4930ee7601 faq: in entry about reordering commits, mention rebase -A/-B 2025-03-31 21:46:51 +00:00
dependabot[bot]
23a3bd3dce cargo: bump once_cell in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [once_cell](https://github.com/matklad/once_cell).


Updates `once_cell` from 1.21.2 to 1.21.3
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.2...v1.21.3)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 18:21:12 +00:00
dependabot[bot]
ccc5dc4a2a github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.49.37 to 2.49.40
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](63f2419bb6...daa3c1f1f9)

Updates `astral-sh/setup-uv` from 5.4.0 to 5.4.1
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](22695119d7...0c5e2b8115)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 18:20:54 +00:00
Robin Stocker
0456701e18 cli: Print @ and @- in status and print_updated_working_copy_stats
This helps newcomers learn/remember what @ and @- stand for.
2025-03-31 11:57:04 +00:00
Robin Stocker
2d9c72ffb1 cli/tests: Prevent git.subprocess from reading outside git config
A subset of cli tests could fail if the system /etc/gitconfig had
configuration interfering with the tests. The cause seems to be running
of `jj` commands that would in turn use a `git` subprocess.

Fix this by setting `GIT_CONFIG_SYSTEM` and `GIT_CONFIG_GLOBAL`, like
in `hermetic_git`.

Fixes #6159
2025-03-31 10:46:57 +00:00
Yuya Nishihara
60372ead43 tests: port test_repo_change_report.rs to TestWorkDir API 2025-03-31 10:35:36 +00:00
Yuya Nishihara
d15aa8c1c1 tests: port test_rebase_command.rs to TestWorkDir API 2025-03-31 10:35:36 +00:00
Yuya Nishihara
f3ed4cfed6 tests: port test_parallelize_command.rs to TestWorkDir API 2025-03-31 10:35:36 +00:00
Yuya Nishihara
da8ff3083c tests: port test_operations.rs to TestWorkDir API 2025-03-31 10:35:36 +00:00
Yuya Nishihara
022147e4f4 diff: use BString more consistently in rendering functions 2025-03-31 10:34:14 +00:00
Yuya Nishihara
a3a59abe74 diff: pass left/right contents and line number pairs by array
When rendering diff of conflicts, it might make sense to show diff of negative
(or base) terms with "removed"/"added" labels swapped. For example, diffs
between A and B-C+D can be rendered as

  {left, right} = {A (-), B (+)}, {A (+), C (-)}, {A (-), D (+)}

by padding -A+A to the left side. To achieve that, I'm thinking of adding
labels: [&str; 2] parameter. This patch will help keep function arguments more
consistent there.

FWIW, I also tried rendering in {A (-), B (+)}, {C (-), D (+)} forms. This
seemed not intuitive because, when diffing, we compare two distinct states. In
the example above, the diff {C, D} should be considered an internal state at the
right side, not the states pair to be diffed.
2025-03-31 10:34:14 +00:00
Ilya Grigoriev
9db0739320 cli git push: new --named NAME=REVISION argument to create and immediately push bookmark
Fixes #5472
2025-03-31 03:51:28 +00:00
Yuya Nishihara
3f5f872204 view: rename workspace "id" to "name"
This matches the current implementation.
2025-03-31 03:39:29 +00:00
Yuya Nishihara
e66c545438 view: replace WorkspaceId by string-like newtypes
I think this makes more sense because WorkspaceId is currently a human-readable
name. In error/status messages, workspace names are now printed in revset
syntax.

New WorkspaceId types do not implement Default. It would be weird if string-like
type had non-empty Default::default(). The DEFAULT constant is provided instead.
2025-03-31 03:39:29 +00:00
Yuya Nishihara
a08d53bef8 cli: do not panic on non-utf-8 workspace name to add 2025-03-31 03:39:29 +00:00
Yuya Nishihara
f87db58617 view: rename RemoteRefState::Tracking to Tracked
In jj's model, a local bookmark "tracks" remote bookmarks. It's wrong to call
a remote bookmark state as "tracking".
2025-03-31 01:41:31 +00:00
Yuya Nishihara
15e5bf7188 changelog: restore "Fixed bugs" heading removed at 68ef93ebd6e0 2025-03-30 02:17:00 +00:00
Yuya Nishihara
e4982a9c33 git: report HEAD export failure as warning
It's safe to commit transaction even if HEAD couldn't be updated due to
concurrent changes.

Closes #6098
2025-03-30 01:46:00 +00:00
Yuya Nishihara
cd1ee943e6 git: split GitResetHeadError, add UpdateHeadRef variant
This helps handle "HEAD" export failure differently. At this point, the set of
error variants look distinct enough to split the error types.
2025-03-30 01:46:00 +00:00
Yuya Nishihara
4beaa6c509 cli: don't map GitExportError to internal error
It seemed inconsistent that only GitExportError was translated to an internal
error. There are various reasons that triggers git::export_refs()/reset_head()
failure, and some of them are environmental error.
2025-03-30 01:46:00 +00:00
Yuya Nishihara
c33be3839d git: remove redundant error context messages, rename variants
Wrapped errors are usually displayed after "Failed to import refs ...", etc., so
the context is obvious. I also removed "Internal"/"Error" for consistency.
2025-03-30 01:46:00 +00:00
Martin von Zweigbergk
cb8d8d0eef paid_contributors: torquestomp has left Google
They are technically employed for another week or so, but they're on
vacation. Removing now before I forget.
2025-03-29 17:33:47 +00:00
Yuya Nishihara
69f278a581 tests: port test_next_prev_commands.rs to TestWorkDir API 2025-03-29 14:42:08 +00:00
Yuya Nishihara
1df956bfb5 tests: port test_new_command.rs to TestWorkDir API 2025-03-29 14:42:08 +00:00
Yuya Nishihara
511d0bd499 tests: port test_log_command.rs to TestWorkDir API 2025-03-29 14:42:08 +00:00
Yuya Nishihara
7f1bc15469 tests: port test_interdiff_command.rs to TestWorkDir API 2025-03-29 14:42:08 +00:00
Yuya Nishihara
76283dc4b6 tests: port test_immutable_commits.rs to TestWorkDir API 2025-03-29 14:42:08 +00:00
Jakob Hellermann
68ef93ebd6 cli: add revsets.log-graph-prioritize setting
The option will be used to prioritize branches of the `jj log` graph to
be displayed on the left.
This can make them more readable in some situations.
An example would be
```
[revsets]
log-graph-prioritize = "coalesce(description("megamerge\n"), trunk())"
```
2025-03-29 13:50:55 +00:00
Vincent Ging Ho Yim
25dbce663e docs/revsets: fix fork_point() example
In the diagram, E has parent B, and D has parents B and C, so the fork point of E and D
(the most downstream common ancestor of these commits) should be B rather than A.
2025-03-29 13:08:01 +00:00
Vincent Ging Ho Yim
80d81b5d13 docs/revsets: add missing node glyphs in diagrams 2025-03-29 13:08:01 +00:00
Vincent Ging Ho Yim
0cd352e16a mkdocs: rename docs page name for 'Sapling comparison' for consistency with 'Git comparison' 2025-03-29 05:02:38 +00:00
Vincent Ging Ho Yim
73e87bcf65 mkdocs: use sentence case consistently for docs page names 2025-03-29 05:02:38 +00:00
Vincent Ging Ho Yim
53e8218dca mkdocs: remove quotes surrounding docs page names in YAML fields
This avoids having to escape double and single quotes used within page names. One page has double quotes in its name in the current state, and another will have a single quote in its name in the next commit.
2025-03-29 05:02:38 +00:00
Vincent Ging Ho Yim
2c76054136 mkdocs: use single quotes consistently in YAML values
I don't have a preference for single vs double quotes, but currently `mkdocs.yml` mostly uses single quotes for YAML values.
2025-03-29 05:02:38 +00:00
Vincent Ging Ho Yim
2eb1d5b3e4 mkdocs: standardise indentation 2025-03-29 05:02:38 +00:00
dependabot[bot]
6327cc1da2 cargo: bump once_cell in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [once_cell](https://github.com/matklad/once_cell).


Updates `once_cell` from 1.21.1 to 1.21.2
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.1...v1.21.2)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-28 19:16:12 +00:00
Martin von Zweigbergk
5a90264cff github: add a CODEOWNERS file with only the maintainers in
As discussed several times before, we want to restrict permission to
approve PRs to the maintainers only. This patch adds a GitHub
CODEOWNERS file for that purpose. Once this has been merged, I'm going
to update the rulesets to make PRs requires approval from a
maintainer.
2025-03-28 16:52:50 +00:00
Yuya Nishihara
96cfae1ff7 cli: abandon: show warning if deleted bookmarks had tracking remotes
This should mitigate the issue that user might push deleted bookmarks without
noticing by "jj abandon && jj git push --all".

https://github.com/jj-vcs/jj/issues/3505#issuecomment-2646024815
2025-03-28 14:34:51 +00:00
Yuya Nishihara
7124d1b90a cli: move has_tracked_remote_bookmarks() to cli_util 2025-03-28 14:34:51 +00:00
Yuya Nishihara
fb1c5da3db cli: git-push: remove support for existing long --change bookmark names
It's deprecated since 1aad25042029 "Shorten the git push branch when possible
using the short change ID hash" (2023-01-12). I don't think we need the
fallback. I also removed the check for ambiguous prefixes as I believe it
wouldn't practically matter. If needed, we can add a check for existing bookmark
pointing to different commit. We can also make it templated with default
"'push-' ++ change_id.shortest(12)".
2025-03-28 14:34:11 +00:00
Yuya Nishihara
d6be4a598e git: use GitRefName newtypes in push functions 2025-03-28 01:29:30 +00:00
Yuya Nishihara
fb8e45d6f9 view: introduce GitRefName newtypes
We don't reuse the RefName type here because fully-qualified Git ref name
shouldn't be considered a local ref name in jj.
2025-03-28 01:29:30 +00:00
Remo Senekowitsch
f295817fe3 cli: completion: fix renamed paths
Previously, the completions suggested the literal string:
`{f_not_yet_renamed => f_renamed}`. Instead, the old and new file names
should be completed separately.
2025-03-27 22:23:29 +00:00
Remo Senekowitsch
4c8ddd47d0 cli: completion: fix test for renamed paths
Previously, the "renamed" file wasn't deleted at its old location, so it
wasn't renamed at all. Correcting this reveals a bug in the completions
of renamed paths. The completions suggest the literal string:
`{f_not_yet_renamed => f_renamed}`. Instead, the old and new file names
should be completed separately.
2025-03-27 22:23:29 +00:00
Ilya Grigoriev
1dedd9ce10 vimdiff merge tool: automatically navigate to first conflict
This also sets the search pattern to `<<<<<`, allowing the user to press `n` to
jump to the next conflict
2025-03-27 22:12:08 +00:00
Ilya Grigoriev
9d8474768d vimdiff merge tool: use the default conflict markers
With the default config (if global `conflict-marker-style` is not
customized), this reverts the behavior to that before 7f57866332.

`vimdiff` config already shows three panes with the three snapshots for
each conflict, so it's helpful to show jj's diff view in the editing
pane.

In other words, previously the conflict was always shown in the
"snapshot" format in the main pane:

```
<<<<<<< Conflict 1 of 1
+++++++ Contents of side #1
fn has_tracked_remote_bookmarks(view: &View, bookmark: &RefName) -> bool {
------- Contents of base
fn has_tracked_remote_bookmarks(view: &View, bookmark: &str) -> bool {
+++++++ Contents of side #2
pub fn has_tracked_remote_bookmarks(view: &View, bookmark: &str) -> bool {
>>>>>>> Conflict 1 of 1 ends
```

and now it is shown in whatever format the user set as the default. If
the user didn't pick a default, the "diff" format is used in the main
pane:

```
<<<<<<< Conflict 1 of 1
%%%%%%% Changes from base to side #1
-fn has_tracked_remote_bookmarks(view: &View, bookmark: &str) -> bool {
+fn has_tracked_remote_bookmarks(view: &View, bookmark: &RefName) -> bool {
+++++++ Contents of side #2
pub fn has_tracked_remote_bookmarks(view: &View, bookmark: &str) -> bool {
>>>>>>> Conflict 1 of 1 ends
```

See also the screenshot in https://github.com/jj-vcs/jj/pull/6147 to see
the main pane in the context of the three other panes that always
display the snapshots.
2025-03-27 22:12:08 +00:00
dependabot[bot]
a7e7571d6d cargo: bump clap from 4.5.32 to 4.5.34 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.32 to 4.5.34
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.32...clap_complete-v4.5.34)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-27 20:52:23 +00:00
dependabot[bot]
d1a3148b11 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.35 to 2.49.37
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](5651179950...63f2419bb6)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-27 20:52:10 +00:00
Yuya Nishihara
bcf3d5869b revset: add optional local variables to RevsetParseContext 2025-03-27 13:23:58 +00:00
Yuya Nishihara
483e7941b4 revset: add support for local variables expansion
I think this can be used in order to substitute remote name in the default push
revset expression?

  push(remote) = remote_bookmarks(remote=remote)..@
2025-03-27 13:23:58 +00:00
Yuya Nishihara
ffad6fe96f revset: extract internal context struct from RevsetParseContext
This allows callers to mutate RevsetParseContext if needed.

RevsetParseContext was changed to an opaque struct at 4e0abf06317f "revset: make
RevsetParseContext opaque." I think the intent there was to hide implementation
details from revset extension functions. This is now achieved by opaque
LoweringContext.

Another reason of this change is that aliases_map is no longer needed when
transforming AST to UserRevsetExpression.
2025-03-26 13:03:41 +00:00
Yuya Nishihara
a50f3881f0 cli: move revset_parse_context() and template_aliases_map() to env object
There aren't many callers of these functions.
2025-03-26 13:03:41 +00:00
Yuya Nishihara
3656a62bf5 git: use RemoteName newtype in remote management functions 2025-03-26 13:03:37 +00:00
Yuya Nishihara
aa0327667f git: extract default_fetch_refspec(remote) helper 2025-03-26 13:03:37 +00:00
Yuya Nishihara
d44251a2a5 git: use RemoteName newtype in push functions 2025-03-26 13:03:37 +00:00
Yuya Nishihara
4ff2827d00 git: use RemoteName/RefName newtypes in fetch functions 2025-03-26 13:03:37 +00:00
Yuya Nishihara
b9f51ff992 ref_name: add RefName::to_remote_symbol(remote) for convenience 2025-03-26 13:03:37 +00:00
Yuya Nishihara
02722eae54 view: port bookmark/tag name types to RefName/RemoteName
I tried to minimize this patch, but it seemed rather complicated than porting
most callers all at once. Remote management functions in git.rs are unchanged.
They'll be ported separately.

With this change, many non-template bookmark/remote name outputs should be
rendered in revset syntax.
2025-03-26 11:07:06 +00:00
Yuya Nishihara
05e10920e6 view: wrap (remote, name) pair in RemoteRefSymbol by flatten_remote_bookmarks()
Since RemoteRefSymbol is now hosted by ref_name module, it seems good that
op_store depends on it.
2025-03-26 11:07:06 +00:00
Yuya Nishihara
db4382d395 str_util: add filter_btree_map_*() that can accept Ref/RemoteName newtypes
It's a bit weird that Deref is required, but this helps deduce the query type.
Maybe we can add typed StringPattern wrapper, but I'm not sure if that's a good
idea.

Alternatively, RefNameBuf could implement Borrow<str>, but that means the map
could be looked up by weakly-typed string keys.
2025-03-26 11:07:06 +00:00
Yuya Nishihara
d227479450 git: use "if" to reject bad branch/remote names in parse_git_ref()
This will help port branch/remote names to RefName/RemoteName types.
2025-03-26 11:07:06 +00:00
Yuya Nishihara
cc8a80c548 ref_name: move RemoteRefSymbol types from refs module 2025-03-26 11:07:06 +00:00
Yuya Nishihara
0efc9423d5 ref_name: add RefName and RemoteName newtypes
These types are akin to ObjectId types, but for refs. Perhaps, WorkspaceId can
be ported to impl_name_type!().

These types don't implement Display because it would be source of subtle bugs
if a string-like type could be formatted differently. For example, we would
have to be careful to not format!("refs/heads/{name}") if the name implemented
a default Display in revset syntax.
2025-03-26 11:07:06 +00:00
Yuya Nishihara
c9cd59ada7 content_hash: forward String::hash() to str
This is basically the same as Vec<T> and [T].
2025-03-26 11:07:06 +00:00
Baltasar Dinis
8ae80523e4 git: forward rejection reason on push
We've been finding that a lot of bug reports on `jj git push` come from
sub-standard error reporting on the reasons the failure happens.

It can come from a number of places:
 - hook failure
 - remote branch protection
 - git config

This commit forwards the reason as explained by the ouptut of git push
to help users figure out what is happening.
2025-03-26 04:46:38 +00:00
Baltasar Dinis
89bb1c4e71 git: refactor push ref error reporting
We need to report more complicated errors on push.
Firstly, we can have a mix of unexpected ref locations and remote
rejections. We should report both at the same time.

Second, git gives us a reason for why a push failed.
For this to work, it's relevant to refactor the current error reporting
path to allow us to inject this information.
2025-03-26 04:46:38 +00:00
Austin Seipp
056d2acaff nix: regularly scheduled flake update
We all know the drill.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-03-25 19:40:48 +00:00
dependabot[bot]
d94aa2062e github: bump the github-dependencies group with 3 updates
Bumps the github-dependencies group with 3 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action), [actions/setup-python](https://github.com/actions/setup-python) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.49.32 to 2.49.35
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](351cce3d3a...5651179950)

Updates `actions/setup-python` from 5.4.0 to 5.5.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](42375524e2...8d9ed9ac5c)

Updates `github/codeql-action` from 3.28.12 to 3.28.13
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](5f8171a638...1b549b9259)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 17:36:45 +00:00
dependabot[bot]
a9df5f70b3 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [pest](https://github.com/pest-parser/pest), [pest_derive](https://github.com/pest-parser/pest) and [whoami](https://github.com/ardaku/whoami).


Updates `pest` from 2.7.15 to 2.8.0
- [Release notes](https://github.com/pest-parser/pest/releases)
- [Commits](https://github.com/pest-parser/pest/compare/v2.7.15...v2.8.0)

Updates `pest_derive` from 2.7.15 to 2.8.0
- [Release notes](https://github.com/pest-parser/pest/releases)
- [Commits](https://github.com/pest-parser/pest/compare/v2.7.15...v2.8.0)

Updates `whoami` from 1.5.2 to 1.6.0
- [Release notes](https://github.com/ardaku/whoami/releases)
- [Changelog](https://github.com/ardaku/whoami/blob/v1.6.0/CHANGELOG.md)
- [Commits](https://github.com/ardaku/whoami/compare/v1.5.2...v1.6.0)

---
updated-dependencies:
- dependency-name: pest
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: pest_derive
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: whoami
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 16:51:25 +00:00
Daniel Luz
05c77a853f formatter: add support for reversing colors 2025-03-25 15:54:22 +00:00
Evan Mesterhazy
83ce245995 cli split: Check the evolog in two tests
This is to ensure two things:

1. That the evolog isn't missing the history from the target commit.
2. That the evolog doesn't include extra "temporary" commits due to
   the way `jj split` is implemented.

The impetus for this is the discussion in https://github.com/jj-vcs/jj/pull/5926.
The changes added here detect the extra temporary commit added to the evolog
when `split --parallel` is run using the implementation in #5926.
2025-03-25 14:21:44 +00:00
Martin von Zweigbergk
108aae1d9b git: set --intent-to-add on new files
In colocated repos, we set up the Git index to make `git diff` closely
match `jj diff`. However, `git diff` will not include new files. We're
long talked about using the `git add --intent-to-add` feature to make
the match closer. This patch implements that. It does so both after
updating the working copy and after snapshotting. After updating the
working copy, the new file in the working-copy commit (compared to the
parent(s)) are marked as intent-to-add. After snapshotting, newly
snapshotted files are marked as intent-to-add, and deleted ones that
were previously marked as intent-to-add are removed from the index.

Closes #6122
2025-03-25 13:40:13 +00:00
Martin von Zweigbergk
5d7ab9be6b git: also test how we set the index flags
I'm about to set the intent-to-add flag for newly added files. It's
also somewhat useful to see the index flags for conflict stages (the
different stages).
2025-03-25 13:40:13 +00:00
Scott Taylor
301f14e856 git_backend: don't allow single tree in jj:trees header
It is important for this case to be an error, because otherwise it would
be possible to construct a non-conflicted commit which appears to have a
different tree when viewed using `jj` than when viewed using Git. This
could potentially be used to hide malicious code in commits in such a
way that on GitHub, the code would appear normal, but it would become
malicious when cloned using `jj`.

Prior to f7b14beacc678a9b351ae65224df43a96aa26392, if a commit had a
`jj:trees` header with only a single tree, it would result in a panic of
"root tree should have been initialized to a legacy id". This commit
restores the error behavior by adding an explicit check for this case.
2025-03-25 12:42:39 +00:00
Scott Taylor
c96d7856a7 git_backend: add test for jj:trees with one tree
This demonstrates an issue which will be fixed by the next commit.
2025-03-25 12:42:39 +00:00
Yuya Nishihara
f2f2e26bb3 cargo-deny: remove chrono-english advisory from ignore list
It's addressed by e3924482 "time_util: replace use of `chrono-english` by
`interim`."
2025-03-25 12:36:28 +00:00
Benjamin Tan
60ce0b87d8 cli: git: add git root command
This was discussed briefly in [1], and makes it easier to execute
scripts which require a reference to the Git directory.

[1]: https://github.com/jj-vcs/jj/discussions/5767#discussioncomment-12480764
2025-03-25 08:58:43 +00:00
Martin von Zweigbergk
e392448288 time_util: replace use of chrono-english by interim
https://rustsec.org/advisories/RUSTSEC-2024-0395 recommends switching
from `chrono-english` to `interim` because the former is unmaintained.
2025-03-24 23:59:21 +00:00
Baltasar Dinis
f4d89fb6df cli/tests: add a filter to remove trailing whitespace in test_git_push_rejected_by_remove
The git remote sideband adds a dummy suffix of 8 spaces to attempt to clear
any leftover data. This is done to help with cases where the line is
rewritten.

However, a common option in a lot of editors removes trailing whitespace.
This means that anyone with that option that opens this file would make the
following snapshot fail. Using the insta filter here normalizes the
output.
2025-03-24 21:49:20 +00:00
Evan Mesterhazy
6ba5a76f51 cli split: Rename "siblings" test to "parallel"
The name of the flag was changed from --siblings to --parallel a while back,
but the tests weren't renamed.

#cleanup
2025-03-24 14:42:40 +00:00
Evan Mesterhazy
bb78c06d76 cli split: Add a simple split with descendants test
We have this test for split --parallel, but we don't actually have a
non-parallel split test where the target commit has descendants.
2025-03-24 14:42:06 +00:00
Evan Mesterhazy
b627924828 cli split: Explain how the diff script works in test_split_interactive_with_paths
It wasn't immediately obvious to me what was happening here, but once I
understood it it seemed pretty simple. Perhaps it's worth a comment to explain
it to the next reader.
2025-03-24 13:58:21 +00:00
Yuya Nishihara
5ee1dbbd22 git: wrap list of failed exports in struct
I have no immediate plan, but I think we can make "jj git export" show exported
refs. The FailedRefExport type is replaced with a plain tuple since we have a
named type wrapping (symbol, reason) pairs now.
2025-03-24 01:18:24 +00:00
Yuya Nishihara
c75dbfdabb git: rename "branch" in export code path 2025-03-24 01:18:24 +00:00
Martin von Zweigbergk
b8ca9ae806 git_backend: stop writing tree ids to proto
We have been writing conflicted tree ids to the `jj:trees` commit
header since 4d426049 (just over a year ago) and we have been able to
read them from there since the same commit. This patch updates the
write path so we no longer redundantly write the trees to the proto
storage.
2025-03-23 16:12:22 +00:00
Martin von Zweigbergk
f7b14beacc git_backend: if jj:trees header exists, ignore tree ids from proto
We write the `jj:trees` commit header since 4d426049. If the
information is written to that header, we should not require it to
also be in the proto storage.
2025-03-23 16:12:22 +00:00
Martin von Zweigbergk
e7a36bbd8d git_backend: remove some unused code for legacy conflicts
We have called `import_head_commits_with_tree_conflicts()` only with
`uses_tree_conflict_format=true` outside of tests since aa0fbd9f.
2025-03-23 16:12:22 +00:00
Yuya Nishihara
7404338362 git: replace remaining caller of old parse_git_ref() 2025-03-23 11:02:49 +00:00
Yuya Nishihara
f6800787ed git: inline parsing of remote default branch
This should be simpler than using parse_git_ref_inner(), and we'll drop git2
support anyway.
2025-03-23 11:02:49 +00:00
Yuya Nishihara
8ed9c36377 git: in import_refs(), use sorted vec as map of bookmarks/tags
For consistency with export_refs().

changed_git_refs doesn't have to be sorted, but the sorting cost wouldn't matter
in practice.
2025-03-23 01:32:29 +00:00
Yuya Nishihara
b6419ca790 git: in import_refs(), split known remote bookmarks/tags maps
It's simpler, and more consistent with export_refs().
2025-03-23 01:32:29 +00:00
Yuya Nishihara
ddaa909977 git: extract inner loop that compares known refs and actual refs to import
I'll split known_remote_refs to bookmarks and tags, and the input Git refs
iterators are also filtered by ref types.
2025-03-23 01:32:29 +00:00
Yuya Nishihara
7d59d0564e git: use translated remote symbols in export_refs()
I also made to_git_ref_name() stricter as it seemed odd that empty tag name and
remote name were allowed.
2025-03-23 01:32:29 +00:00
Yuya Nishihara
c3a32c4265 git: in export_refs(), use sorted Vec as map of bookmarks
I'm going to change the key type from RefName to RemoteSymbolBuf, but the std
BTreeMap/HashMap doesn't support lookup by un-Borrow-able ref types. We can use
hashbrown::HashMap, but there aren't popular alternative for ordered maps.
Since we don't need random insertion and lookup, we can simply use Vec.
2025-03-23 01:32:29 +00:00
Yuya Nishihara
5ae63a2355 tests: port test_gitignores.rs to TestWorkDir API 2025-03-23 01:31:28 +00:00
Yuya Nishihara
0d8aeee3e5 tests: port test_git_remotes.rs to TestWorkDir API 2025-03-23 01:31:28 +00:00
Yuya Nishihara
d5226aad0a tests: port test_git_push.rs to TestWorkDir API 2025-03-23 01:31:28 +00:00
Yuya Nishihara
b80e5e07b8 tests: port test_git_private_commits.rs to TestWorkDir API 2025-03-23 01:31:28 +00:00
Yuya Nishihara
1466bce13c tests: move instantiation out of set_up() in test_git_{private_commits,push}.rs
set_up() doesn't have to create TestEnvironment by itself, and the local
TestWorkDir will borrow &test_env.
2025-03-23 01:31:28 +00:00
Yuya Nishihara
7583b9108e tests: port test_git_init.rs to TestWorkDir API 2025-03-23 01:31:28 +00:00
Yuya Nishihara
c2f2461b5f cli: truncate list of newly conflicted commits
If there are hundreds of new conflict commits, the user wouldn't want to see all
of them.
2025-03-23 01:31:22 +00:00
Yuya Nishihara
00c2d15f29 cli: do not print commit summary for previously-conflicted commits
I usually don't read these messages carefully, and the red "(conflict)" label
looked scary. Suppose we don't have to take further action on resolved commits,
I don't think we need to print commit summary for each resolved commit.
2025-03-23 01:31:22 +00:00
Yuya Nishihara
e52ddda98f conflicts: pack MaterializedTreeValue::File fields, use read_all() helper
This pattern is common.
2025-03-23 01:31:15 +00:00
Yuya Nishihara
a1eff5057a conflicts: extract MaterializedFileValue type from absorb
The interface is slightly adjusted because it wouldn't make much sense that
generic read_all() method returned BString.
2025-03-23 01:31:15 +00:00
Yuya Nishihara
9a615b5b62 config: relax bare string rule to accept middle apostrophes, etc.
Closes #5748
2025-03-22 09:00:44 +00:00
dependabot[bot]
78239ee133 github: bump astral-sh/setup-uv in the github-dependencies group
Bumps the github-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 5.3.1 to 5.4.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](f94ec6bedd...22695119d7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-22 06:27:03 +00:00
Caleb White
caf172f651 templates: create builtin_draft_commit_description template
This moves the default template to `builtin_draft_commit_description` and
points `draft_commit_description` to it. This makes it easier to override
the template while still being able to refer to the default.
2025-03-22 02:06:10 +00:00
Martin von Zweigbergk
1a98a73ae4 fix: avoid an unnecessary clone 2025-03-21 19:56:26 +00:00
Martin von Zweigbergk
be0ad55309 doc: update some references to "the native backend" 2025-03-21 16:27:51 +00:00
Yuya Nishihara
ca616fade2 tests: port test_git_import_export.rs to TestWorkDir API 2025-03-21 01:19:20 +00:00
Yuya Nishihara
569a405cea tests: port test_git_fetch.rs to TestWorkDir API 2025-03-21 01:19:20 +00:00
Yuya Nishihara
64f8661f0f tests: port test_git_colocated.rs to TestWorkDir API 2025-03-21 01:19:20 +00:00
Yuya Nishihara
a36bf18a57 tests: port test_git_clone.rs to TestWorkDir API
In test_git_clone.rs, it's common to execute "jj git clone" or do file operation
at the root directory. This patch adds root_dir: &TestWorkDir references for
that reason. We might want to somehow merge root_dir and test_env later.
2025-03-21 01:19:20 +00:00
Austin Seipp
2c074702e9 github: bump binaries timeout limit
Like the CI runners, I've occasionally seen spikes where binary builds
on 'main' were failing. Bump a little bit to avoid that.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-03-20 23:04:20 +00:00
Austin Seipp
fc8de6006b github: bump timeout to 20 minutes
We're well under the 15 minute limit at this point at the p90 case, but it
seems like the p99 build tends to run into ~10minute spikes due to underlying
runner anomalies (oversaturation?)

Let's go ahead and use a timeout of 20 minutes to give it a little more slack.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-03-20 23:04:20 +00:00
Austin Seipp
4b28aaa8c6 Back out "github: only run dependabot workflow on dependabot/** branches"
This might be the cause of dependabot PRs not having automerge. Let's
just revert it and see what happens when the next PRs roll in.

This backs out commit 47cd10669de28ecc36f0d7dbbb9964945124b730.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-03-20 19:10:45 +00:00
dependabot[bot]
d83cc1d97e cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [clap_complete](https://github.com/clap-rs/clap) and [tempfile](https://github.com/Stebalien/tempfile).


Updates `clap_complete` from 4.5.46 to 4.5.47
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.46...clap_complete-v4.5.47)

Updates `tempfile` from 3.19.0 to 3.19.1
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.19.0...v3.19.1)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-20 17:48:20 +00:00
dependabot[bot]
71e225ad0c github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `actions/upload-artifact` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](4cec3d8aa0...ea165f8d65)

Updates `taiki-e/install-action` from 2.49.30 to 2.49.32
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](37bdc826ea...351cce3d3a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-20 17:20:15 +00:00
Caleb White
680c41c30f signing: add gpgsm backend
The adds support for PKCS#12 certificates through the `gpgsm` backend.

Closes #5856
2025-03-20 17:01:39 +00:00
Raphael Borun Das Gupta
d1242635c6 docs: fix spelling of «NixOS»
The name «NixOS» doesn't have a space in it.
2025-03-19 22:46:34 +00:00
Fedor Sheremetyev
364ece09e0 signing: Don't show console on Windows
When used inside GUI application, enabling GPG or SSH signing causes console window to appear temporarily.

std::process::Command needs special flags to prevent that. More details: https://stackoverflow.com/a/60958956
2025-03-19 18:40:16 +00:00
Fedor Sheremetyev
a028e4a03a git: Don't show console on Windows
When used inside GUI application, git.subprocess=true option causes console window to appear temporarily.

std::process::Command needs special flags to prevent that. More details: https://stackoverflow.com/a/60958956
2025-03-19 18:40:16 +00:00
Martin von Zweigbergk
bdbb7afbbb protos: finish local_store->simple_store migration from f8ab8a0e 2025-03-19 17:32:02 +00:00
dependabot[bot]
abd6b22e92 github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.28.11 to 3.28.12
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](6bb031afdd...5f8171a638)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-19 16:26:37 +00:00
Yuya Nishihara
9f867b6fac tests: port test_fix_command.rs to TestWorkDir API 2025-03-19 14:52:52 +00:00
Yuya Nishihara
602799afae tests: inline init_with_fake_formatter() to callers 2025-03-19 14:52:52 +00:00
Yuya Nishihara
5647113cbd tests: extract helper that configures fake "fix" formatter 2025-03-19 14:52:52 +00:00
Yuya Nishihara
1769b14995 tests: port test_file_track_untrack_commands.rs to TestWorkDir API 2025-03-19 14:52:52 +00:00
Yuya Nishihara
6f31ba9042 cli: git-push: use RemoteRefSymbol to format and pass name@remote around
This ensures that remote symbols are printed with quoting as needed. Local
bookmark names aren't quoted, which will be fixed separately by introducing
RefName(str) newtype.
2025-03-19 14:28:56 +00:00
Yuya Nishihara
8ae605703e cli: git-push: rename bookmark name/remote variables, bind reference
I'm going to replace (name, remote) pair with RemoteRefSymbol. The underlying
name types will also be changed to RefName/RemoteName newtypes.
2025-03-19 14:28:56 +00:00
Emily
8052b62f2e github: use native builder for x86 macOS
This shaves over 3½ minutes off the tests, taking this from the
slowest job to faster than Windows again. The macOS 13 builders
presumably won’t be around forever, and the newer free builders are
all Apple Silicon, but this is an easy substantial improvement for now.
2025-03-19 14:07:02 +00:00
Yuya Nishihara
f37f927bc2 cli: abandon: remove --summary flag, just print elided list
AFAICT, this option was needed when we're going to abandon hundreds of commits.
However, I typically notice that I had to use --summary to suppress the output
after the fact. Since the list is now truncated up to 10 commits, I don't think
the --summary flag is useful anymore. This patch also removes the special case
for 1 item, which existed primarily for overriding --summary.
2025-03-19 05:32:26 +00:00
Yuya Nishihara
976f3585e1 cli: truncate list of abandoned/created/updated commits
The choice of the upper limit is arbitrary. We use 5 in "multiple revisions"
error, but I feel 5 would be too small for "jj absorb", where the stack of
mutable commits may be ~10, but wouldn't likely be ~100s.
2025-03-19 05:32:26 +00:00
Yuya Nishihara
f07203cb3b cli: extract helper that prints list of indented commit summary 2025-03-19 05:32:26 +00:00
Yuya Nishihara
3b083f914c tests: port test_file_show_command.rs to TestWorkDir API 2025-03-19 01:47:00 +00:00
Yuya Nishihara
9e04e60594 tests: port test_file_chmod_command.rs to TestWorkDir API 2025-03-19 01:47:00 +00:00
Yuya Nishihara
ae5e717d4e tests: port test_file_annotate_command.rs to TestWorkDir API 2025-03-19 01:47:00 +00:00
Yuya Nishihara
bf53953c46 tests: port test_evolog_command.rs to TestWorkDir API 2025-03-19 01:47:00 +00:00
Yuya Nishihara
4f192d0162 tests: port test_edit_command.rs to TestWorkDir API 2025-03-19 01:47:00 +00:00
Yuya Nishihara
082602f2da tests: port test_duplicate_command.rs to TestWorkDir API 2025-03-19 01:47:00 +00:00
Emily
fbb14a1b2f github: use the D: drive on Windows 2025-03-19 01:23:58 +00:00
dependabot[bot]
be432750bd github: bump the github-dependencies group with 2 updates
Updates the requirements on [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) and [taiki-e/install-action](https://github.com/taiki-e/install-action) to permit the latest version.

Updates `dtolnay/rust-toolchain` to 888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](888c2e1ea6)

Updates `taiki-e/install-action` from 2.49.24 to 2.49.29
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](0f58b6a196...62730e3d4f)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-type: direct:production
  dependency-group: github-dependencies
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 20:50:16 +00:00
dependabot[bot]
f6237f07fd cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [async-trait](https://github.com/dtolnay/async-trait), [git2](https://github.com/rust-lang/git2-rs) and [rustix](https://github.com/bytecodealliance/rustix).


Updates `async-trait` from 0.1.87 to 0.1.88
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.87...0.1.88)

Updates `git2` from 0.20.0 to 0.20.1
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.20.0...git2-0.20.1)

Updates `rustix` from 1.0.2 to 1.0.3
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.2...v1.0.3)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: git2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 16:27:53 +00:00
Yuya Nishihara
322c9951cf ui: do not pass empty choice input to parse function
Since prompt_choice_with() is now public, it should handle the default value
more consistently. parse() shouldn't be a function that can potentially feed a
default value.

We no longer print "unrecognized response" on empty input. I think this is
correct because an empty input is noop.
2025-03-18 15:43:43 +00:00
Yuya Nishihara
c2c2196b14 cli: split name/email not configured message to warning and hint
We usually print a short error message plus detailed hints.
2025-03-18 14:35:54 +00:00
Yuya Nishihara
b9e88be191 cli: extract common formatting function for updated working-copy stats
It's not important to print the parent commits on update-stale, but there should
be no reason to deviate the output formatting.
2025-03-18 14:35:54 +00:00
Yuya Nishihara
ccf927e1d8 cli: pass CheckoutStats to "print" function by reference 2025-03-18 14:35:54 +00:00
Yuya Nishihara
1a4d8dbbf4 ui: reimplement prompt_yes_no() by using prompt_choice_with()
It was odd that uppercase "Y"/"N" wasn't accepted.
2025-03-18 11:17:34 +00:00
Yuya Nishihara
083292d198 ui: extract prompt_choice_with(..callback)
I think "jj next/prev" prompt can also support selection by change ID prefix.
Maybe we can also add ui.prompt_choice_range(prompt, range, default) if that's
common?
2025-03-18 11:17:34 +00:00
Vincent Ging Ho Yim
7e1433a333 docs: fix missed rename of LocalBackend to SimpleBackend
This is a follow-up to f8ab8a0e.
2025-03-18 06:52:00 +00:00
Vincent Ging Ho Yim
a523503dbc docs: update types diagram to reflect rename of LocalBackend to SimpleBackend
This is a follow-up to f8ab8a0e.
2025-03-18 06:52:00 +00:00
Ilya Grigoriev
2a4b2dedf0 simple backend: document the lack of fetching and pushing
This is barely worth doing, but since I did it once...

I feel that some people might treat the simple backend as an easter egg, and
this will help them know what to expect.
2025-03-18 06:38:18 +00:00
Ilya Grigoriev
659325f9c3 cli git fetch: stop tracing the arguments of the outer function
The important arguments are traced in the inner function.

Previously:

```
2025-03-18T05:01:44.714692Z DEBUG run_command:cmd_git_fetch{args=GitFetchArgs { branch: [Exact("main"), Exact("master"), Glob(GlobPattern("gh-readonly-queue*")), Glob(GlobPattern("ig/*"))], remotes: [Exact("upstream")], all_remotes: false }}:fetch{remote_name="upstream" branch_names=[Exact("main"), Exact("master"), Glob(GlobPattern("gh-readonly-queue*")), Glob(GlobPattern("ig/*"))] depth=None}: jj_lib::git_subprocess: spawning a git subprocess cmd=LC_ALL="C" "git" "--git-dir" "/Users/ilyagr/dev/jj/.git" "branch" "--remotes" "--delete" "--" "upstream/master"
```

Now:

```
2025-03-18T05:06:29.573444Z DEBUG run_command:cmd_git_fetch:fetch{remote_name="upstream" branch_names=[Exact("main"), Exact("master"), Glob(GlobPattern("gh-readonly-queue*")), Glob(GlobPattern("ig/*"))] depth=None}: jj_lib::git_subprocess: spawning a git subprocess cmd=LC_ALL="C" "git" "--git-dir" "/Users/ilyagr/dev/jj/.git" "branch" "--remotes" "--delete" "--" "upstream/master"
```
2025-03-18 06:02:05 +00:00
Ilya Grigoriev
1247aaf7f0 lib str_utils: make debug output for Glob StringPatterns more concise
For example, we'll have `Glob(GlobPattern("ig/*"))` instead of the
former

```
Glob(Pattern { original: "ig/*", tokens: [ Char( 'i', ), Char( 'g', ), Char( '/', ), AnySequence, ], is_recursive: false,
})
```
2025-03-18 06:02:05 +00:00
Yuya Nishihara
be4cb847fa repo: unblock loading of simple backend
This removes warning about unused SimpleBackend on non-dev build. Since
Workspace::init_simple() isn't feature-gated, it doesn't make sense to disable
the backend loading.
2025-03-18 05:37:12 +00:00
Baltasar Dinis
837eee90be git: add more targetted debug logging for subprocess code
In recent bugs, it's been really hard to triage the behaviour from the
existing debug logs.

In particular, there have been situations where the `git` command is not
enough to triage, because the bug stems from a particular
environment/config issue. Moreover, these bugs are either transient, and
as such hard to replicate, or they only manifest when spawning `git`
from `jj` (and as such re-running the `git` command does not yield
useful information).

In those cases, seeing what the subprocessing code is seeing becomes
very much useful. This commit adds some debug logging to help with this
problem, by logging some parts of the `git(1)` output and the config.
2025-03-18 03:27:05 +00:00
Emily
d9c3ffa022 github: revert the timeout to 15 minutes 2025-03-18 02:22:47 +00:00
Emily
3ad42b8f15 github: use faster linkers in CI
This shaves off something like 20 to 40 seconds for Linux. It’s
seemingly within the margin of error for Windows, so not sure if
we’ll want to keep it there.
2025-03-18 02:22:47 +00:00
Emily
5d48339165 github: only pass --target to Cargo when necessary 2025-03-18 02:22:47 +00:00
Emily
acd8ecf9f2 github: move CARGO_* CI variables into Cargo configuration
`incremental` is unnecessary since the `rust-toolchain` action
already sets the environment variable, but probably harmless for
future‐proofing.
2025-03-18 02:22:47 +00:00
Emily
608ce2de55 cargo: move config{=> -ci}.toml
This configuration is for the CI builders, so by moving it to another
file we can add more opinionated things without risking breaking
builds for anyone else.
2025-03-18 02:22:47 +00:00
Ilya Grigoriev
341ddc9148 ci: make sure tests fail in CI if gpg or taplo binaries are not found
Fixes #5696
2025-03-18 02:18:08 +00:00
Yuya Nishihara
b000c94a7a tests: add work_dir.dir(path), make create_dir*() return helper for sub dir
I found this pattern is common.
2025-03-18 01:25:08 +00:00
Yuya Nishihara
c7811bb956 tests: move strip_last_line() to command_output module
The primary caller of this function is CommandOutputString. We can reexport it
if needed.
2025-03-18 01:25:08 +00:00
Yuya Nishihara
7ab62ab865 tests: extract CommandOutput types to sub module
These types don't depend on TestEnvironment.
2025-03-18 01:25:08 +00:00
Yuya Nishihara
c63d8d945e tests: do not mark SSH private key as executable
It doesn't matter, but the intent here should be "chmod go-rwx".
2025-03-18 01:24:47 +00:00
Johannes Altmanninger
4c01ed0746 docs: fish shell enables completions by default
To avoid redundant computation, users who run `COMPLETE=fish jj | source`
in their ~/.config/fish/config.fish should eventually remove that, or move it
to ~/.config/fish/completions/jj.fish which overrides the default completions.

Ref: https://github.com/fish-shell/fish-shell/pull/11046
2025-03-17 18:37:48 +00:00
Philip Metzger
f8ab8a0e72 lib: rename the LocalBackend to SimpleBackend
This makes it clear to source code readers, that it isn't the _native backend_ the project
talks about in the Roadmap.
2025-03-17 17:17:11 +00:00
Emily
76bff8fa34 github: set LIBGIT2_NO_VENDOR on builds as well
This was causing unnecessary rebuilds in the test step due to the
changed environment variable.
2025-03-17 17:10:11 +00:00
Emily
758e35eef1 github: don’t install Gpg4win on Windows
The stock version seems to be sufficient these days.
2025-03-17 17:10:11 +00:00
Emily
eb39c4175a github: don’t reinstall OpenSSH on Windows
The stock version seems to be sufficient these days.
2025-03-17 17:10:11 +00:00
Benjamin Tan
2bf5d6aa8f cli: deprecate backout command 2025-03-17 15:03:29 +00:00
Benjamin Tan
8a816b2622 cli: add revert command
This adds a revert command which is similar to backout, but adds the
`--destination`, `--insert-after`, and `--insert-before` optoins to
customize the location of the new reverted commits.

`jj backout` will subsequently be deprecated.

Closes #5688.
2025-03-17 15:03:29 +00:00
Yuya Nishihara
ec6c5235d5 annotate: report root commit id if no origin found within range
Suppose we add "jj file annotate" option to specify the search domain or depth,
the root commit within the range can be displayed as the boundary commit. "git
blame" for example displays boundary commits with ^ prefix.

This follows up 85e0a8b0687e "annotate: add option to not search all ancestors
of starting commit."
2025-03-17 10:57:33 +00:00
Yuya Nishihara
789249541e cargo: remove "anyhow" which was used only in config tests
It's not important to remove anyhow dependency, but there was no reason to use
anyhow.
2025-03-17 01:39:55 +00:00
Yuya Nishihara
dd0e4f8f66 tests: define config TestCase types early
I feel it's easier to follow if types are defined first.
2025-03-17 01:39:55 +00:00
Yuya Nishihara
93aa697ca1 tests: remove redundant .to_path_buf() from test_config_path() 2025-03-17 01:39:55 +00:00
Yuya Nishihara
0e32223d9a tests: inline config TestCase::run() into test function, use assert!()s
We generally use panicking functions in tests.
2025-03-17 01:39:55 +00:00
Yuya Nishihara
27c777b3e5 tests: reorganize config TestCase as parameterized test_case
The TestCase type will be a pure value type.
2025-03-17 01:39:55 +00:00
Yuya Nishihara
cdc2ddf822 tests: remove generic parameters from config TestCase
We don't care about the memory layout here, and it'll become verbose to spell
them out in the following patches.
2025-03-17 01:39:55 +00:00
Jonathan Gilchrist
153c972822 diff-editor: Always use Section::FileMode when creating or deleting files
The code in this area gets significantly simpler, since there's no
longer any ambiguity to resolve. The scm-record changes expect that any
mode changes are represented by file mode sections, and adds UI hooks to
simplify working with them (e.g. automatically de-selecting a deletion
mode change if any lines in the file are de-selected, since the file
still needs to exist to hold those lines).

If the user selects a file mode change, the new mode comes back as part
of .get_selected_contents(), so we can use this directly to figure out
whether the file was removed or just changed.
2025-03-16 18:18:46 +00:00
David Rieber
7e1901fefa jj fix: Refactor jj fix CLI, move some logic to lib to make it usable in other tools (e.g. in servers).
* The lib part should not deal with tools, or tool config, or running stuff in subprocesses. That stays in the CLI.
* Adds a fix_files function. This is the entry point.
* Adds a FileFixer trait with a single method to process a set of files. This is a high-level plugin point.
* Adds a ParallelFileFixer which implements the FileFixer trait and invokes a function to fix one file a time, in parallel.
* The CLI uses ParallelFileFixer.

The FileFixer trait allows environments (other than jj CLI) to plug in their own implementation; for example, a server could implement a FileFixer that sends RPCs to a "formatting service".
2025-03-16 17:59:22 +00:00
Emily
8bb6b90478 github: add variant without git2 to CI
Just using the fastest platform should be fine for this. Hopefully it
shouldn’t slow down CI too much since it’s an independent build
job and only temporary, though a potential alternative would be to
just check the build instead. (It wouldn’t catch build regressions
in the test code, though.)
2025-03-16 06:07:28 +00:00
Emily
cce229d914 config: warn on unsupported git.subprocess = false 2025-03-16 06:07:28 +00:00
Emily
a56b78bdb6 git: make git2 support optional
This helps us prepare for removing the functionality down the line and
makes things easier for people building or packaging their own Jujutsu.
2025-03-16 06:07:28 +00:00
Emily
8f51e749d0 git: inline open_git_repo
The fetch and push code are the only remaining users of this, so
let’s not encourage adding any more.
2025-03-16 06:07:28 +00:00
Ilya Grigoriev
d1c2a1c578 config docs: touch up platform-specific config location docs 2025-03-16 04:14:15 +00:00
Baltasar Dinis
142ba74427 cli: change debug logging behaviour to whitelist jj crates
The current behaviour means (transitively) dependent crates' debug logs end up
in our logs when `--debug` is active.

The biggest offender here is `globset`, imported from the `ignore
crate`. These logs are extremely noisy and mostly irrelevant from our usecase.

This commit changes this behaviour to whitelist `jj` related debug logs,
while allowing more debugging to come from env vars
2025-03-16 04:12:01 +00:00
Caleb White
a4ef8b3e4d sign: gpg: automatically use user email as signing key
If a signing key is not configured, the user's email will be
used as the signing key. This aligns with `git`'s behavior
and allows the users to not specify the key in their configs
given that they have a key associated with their email.
2025-03-16 02:19:51 +00:00
Ilya Grigoriev
acaedc3382 cleanup: enable unused_trait_names clippy lint and run clippy --fix 2025-03-16 00:35:56 +00:00
Caleb White
07d392ccf2 config: only load toml files in a directory
This helps avoid loading errors should the JJ_CONFIG
environment variable be set to a directory that
contains other file types.
2025-03-15 23:36:39 +00:00
dependabot[bot]
6db87dd8f4 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.19 to 2.49.22
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](87b5304d4e...4a7eafa27f)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 19:24:16 +00:00
Caleb White
669bfaf09b cli: config: support multiple user configuration files
Multiple user configs are now supported and are loaded in the following precedence order:
  - `$HOME/.jjconfig.toml`
  - `$XDG_CONFIG_HOME/jj/config.toml`
  - `$XDG_CONFIG_HOME/jj/conf.d/*.toml`

Which removes the need to set `JJ_CONFIG` for a multi-file approach.
Later files override earlier files and the `JJ_CONFIG` environment
variable can be used to override the default paths.

The `JJ_CONFIG` environment variable can now contain multiple paths separated
by a colon (or semicolon on Windows).
2025-03-15 18:43:42 +00:00
Baltasar Dinis
1c5585f912 git: add error for inadequate git version
When using old git versions, some of the options we use
(namely, --no-write-fetch-head #5933), we want to report that the error
comes from the outdated git, instead of cryptically failing.

Closes #5933
2025-03-15 14:32:24 +00:00
Yuya Nishihara
67a2f41b80 ui: make prompt_choice() return index of choices
It's easier to process index than re-parsing stringified choice.
2025-03-15 06:29:01 +00:00
Yuya Nishihara
efabaa48b2 cli: print list of next/prev candidates to stderr
Follows up 0c7357ec5f55 "ui: write prompt messages to stderr."

I also removed &mut since writing to Ui doesn't require mutable reference.
2025-03-15 06:29:01 +00:00
Martin von Zweigbergk
162e455390 tree: delete unused code for merging legacy trees
This code has been unused since 8e6e04b92.

I also deleted the associated tests. I considered porting them to
`MergedTree` but it looks like we have good coverage of these cases in
the existing `MergedTree` tests combined with the low-level `Merge`
tests.
2025-03-15 05:18:43 +00:00
Emily
09d6d48718 github: fix ci workflow concurrency setting in the queue 2025-03-15 03:25:25 +00:00
Yuya Nishihara
aeb80d2ce7 tests: port test_diffedit_command.rs to TestWorkDir API 2025-03-15 02:41:22 +00:00
Yuya Nishihara
31620a11ae tests: port test_diff_command.rs to TestWorkDir API 2025-03-15 02:41:22 +00:00
Yuya Nishihara
fc84c639f7 tests: configure fake editor early in test_diff*_command.rs
This works around borrowing issue of test_env.
2025-03-15 02:41:22 +00:00
Yuya Nishihara
717f02a97f tests: port test_describe_command.rs to TestWorkDir API 2025-03-15 02:41:22 +00:00
Yuya Nishihara
5b13043167 tests: configure fake editor early in test_describe_command.rs
This works around borrowing issue of test_env.
2025-03-15 02:41:22 +00:00
Yuya Nishihara
f8082a052d templates: do not apply "overridden" label twice to detailed config list 2025-03-15 01:51:47 +00:00
Yuya Nishihara
d9c58446e8 templates: rename inner "config list" helper to format_*()
This is our convention, and helper functions can be hidden from the builtin
aliases list.
2025-03-15 01:51:47 +00:00
Yuya Nishihara
d79b93707c config: minor cleanup around new path/source template handling
Cow<str> provides .into_owned(). I don't care much about .to_string() vs
.to_owned(), but it was originally .to_owned(), so reverted the change.
2025-03-15 01:51:47 +00:00
Yuya Nishihara
ee89fd34a3 tests: port test_config_list_origin() to TestWorkDir API 2025-03-15 01:51:47 +00:00
Martin von Zweigbergk
9b76f05d65 cargo: update to pollster 0.4
I just happened to see that there's a new(ish) version out. It removed
all unsafe code, apparently.
2025-03-14 20:32:18 +00:00
dependabot[bot]
72513fd377 cargo: bump the cargo-dependencies group across 1 directory with 3 updates
Bumps the cargo-dependencies group with 3 updates in the / directory: [once_cell](https://github.com/matklad/once_cell), [tempfile](https://github.com/Stebalien/tempfile) and [tokio](https://github.com/tokio-rs/tokio).


Updates `once_cell` from 1.21.0 to 1.21.1
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.0...v1.21.1)

Updates `tempfile` from 3.18.0 to 3.19.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.18.0...v3.19.0)

Updates `tokio` from 1.44.0 to 1.44.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.0...tokio-1.44.1)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-14 18:46:32 +00:00
Yuya Nishihara
b24c4eb758 cli: use RemoteRefSymbol to format trackable bookmarks 2025-03-14 15:40:58 +00:00
Yuya Nishihara
caa0e75936 cli: move print_trackable_remote_bookmarks() back to git/init.rs
We no longer have external callers of git_init() functions.
2025-03-14 15:40:58 +00:00
Yuya Nishihara
896424db76 templater: rename RefName type to CommitRef
I'm thinking of adding RefName(str) and RemoteName(str) newtypes, and the
templater type name would conflict with that. Since the templater RefName type
is basically a (name, target) pair, I think it should be called a "Ref", and I
added "Commit" prefix for disambiguation.

This isn't a breaking change since template type names only appear in docs and
error messages.
2025-03-14 14:58:29 +00:00
Jakob Hellermann
1f751e28cc templates: expose format_short_signature_oneline template alias hook
For `builtin_log_compact` you can specify how you want signatures to be
displayed via `format_short_signature`, but the same wasn't possible for
`builtin_log_compact_oneline` which always chose `email().local()`.
2025-03-14 13:50:51 +00:00
Baltasar Dinis
4e5260dad2 git: detect remote refusal of a push
When we push a ref to a git remote, we use --force-with-lease
Our understanding was that this could fail iff the expected location of
the ref on the remote was not that of the local tracking ref

However, if the ref is from a protected branch (e.g., main) it will be
rejected by the remote for a different reason.

This commit solves this, by detecting this difference.
2025-03-14 08:09:37 +00:00
Baltasar Dinis
bb75612d00 git: small fixes to git-push parsing
Mainly, the reason is not separated by a tab, but rather a space
Additionally, the test output was made more realistic
2025-03-14 08:09:37 +00:00
Baltasar Dinis
6c95f6192f git: refactor git push output 2025-03-14 08:09:37 +00:00
Caleb White
67e17d5474 cli: config list: show origin of config values
Adds a `templates.config.list` config option to control whether the
detailed list is shown or not.

The `builtin_config_list_detailed` template adds the config origin to
the end of the line for each config value in the list. Options coming
from files will show the file path.
2025-03-13 23:59:45 +00:00
Emily
47cd10669d github: only run dependabot workflow on dependabot/** branches
I think this will reduce some noise from skipped checks in the UI.
2025-03-13 22:28:05 +00:00
Emily
81aad08bc3 Back out "github: don’t use auto‐merges for dependabot"
It turns out that merge queues bundle auto‐merge for free, so we
can just do this the old way.

This backs out commit eba4257ab9a0268460108c234dc0d046efcd6c33.
2025-03-13 22:28:05 +00:00
Emily
117c262086 github: set fail-fast for merge queue jobs
Ideally we’d just cancel workflows entirely when they’re ejected
from the merge queue but apparently that’s kinda hard for some
inexplicable reason. This should be a cheap and harmless win. I
guess it gives you slightly less information if something fails
because of changes in the queue but you can just rebase your PR to
get everything running.
2025-03-13 18:36:24 +00:00
Emily
cd1125467d github: adjust ci concurrency settings
I don’t think `github.event.merge_queue.head_ref` actually does
anything useful, but I’m not sure if `github.ref` works for merge
groups and it seems better to have a fallback than not. I’d really
like to cancel PR workflows when a PR enters the merge queue, and
cancel queue workflows when a PR is ejected from the merge queue,
but apparently this simple task is high‐level wizardry when you’re
using GitHub Actions, so I’m postponing that for later.
2025-03-13 18:36:24 +00:00
Emily
968fcc0b0c github: remove push trigger from ci
This was running all the checks twice for pushes to pull requests,
unnecessarily limiting our concurrency and clogging up the status
report. The only benefit is if someone is pushing to a branch that
they don’t have a PR for and waiting for the checks to run. I
suspect nobody is doing this with regularity, but if it turns out
the functionality is important, we could just ask people to add this
back to the `.github/workflows/ci.yml` on the branches they want
GitHub to test, or add logic to try and cancel `push` runs that match
`pull_request` ones.
2025-03-13 18:36:24 +00:00
Yuya Nishihara
81f8ae42d6 tests: port test_debug_command.rs to TestWorkDir API 2025-03-13 16:13:12 +00:00
Yuya Nishihara
8520557aaa tests: port test_copy_detection.rs to TestWorkDir API 2025-03-13 16:13:12 +00:00
Yuya Nishihara
e35da6988c tests: port test_config_command.rs to TestWorkDir API
Some setup bits are reordered manually to work around mutable borrows.
2025-03-13 16:13:12 +00:00
Yuya Nishihara
dd00489235 tests: port test_concurrent_operations.rs to TestWorkDir API 2025-03-13 16:13:12 +00:00
Yuya Nishihara
334c9e184e tests: port test_completion.rs to TestWorkDir API 2025-03-13 16:13:12 +00:00
Yuya Nishihara
1ba5af082f config: externalize ConfigPath::Unavailable variant
We're going to add support for multiple user config paths, and it would be
weird if Unavailable could be included in a Vec.
2025-03-13 15:38:40 +00:00
Caleb White
7e944f0d11 cli: config {edit,set,unset}: prompt when multiple files exist
This allows the user to select a particular file when using multiple
configs. In the event that a prompt cannot be displayed, the first
file will be automatically selected.
2025-03-13 03:51:52 +00:00
Yuya Nishihara
4ff6150df7 tests: port test_commit_template.rs to TestWorkDir API
std::fs::rename() and symlink() aren't added to TestWorkDir. There are few
instances of rename(). work_dir.symlink() would be a bit unclear whether the
link content path is normalized, which usually shouldn't.
2025-03-13 03:45:45 +00:00
Yuya Nishihara
55eeac9ddd tests: port test_commit_command.rs to TestWorkDir API 2025-03-13 03:45:45 +00:00
Yuya Nishihara
86beeea50c tests: configure fake editor early in test_commit_command.rs
This works around borrowing issue of test_env.
2025-03-13 03:45:45 +00:00
Yuya Nishihara
c07db5a4d8 tests: port test_builtin_aliases.rs to TestWorkDir API
The local work_dir is created by caller since it borrows &test_env.
2025-03-13 03:45:45 +00:00
dependabot[bot]
8edfcf3e88 cargo: bump quote from 1.0.39 to 1.0.40 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [quote](https://github.com/dtolnay/quote).


Updates `quote` from 1.0.39 to 1.0.40
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.39...1.0.40)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-13 02:19:07 +00:00
Emily
228385f7f4 github: attempt to fix required-checks 2025-03-12 20:23:45 +00:00
Emily
725139dd38 github: add required-checks job to ci
This reports the status of the checks that are currently set as
required in the repository configuration, but only in the merge
queue. The advantages are twofold:

1. We can adjust required checks with our normal CI process rather
   than having to bug Martin for it. (This doesn’t really place any
   more trust in anyone than we do currently, because a malicious PR
   could always just replace the jobs with ones that unconditionally
   succeed anyway.)

2. We can make the job only ever fail in the merge queue. Currently,
   we can only submit a PR into the merge queue after all its checks
   pass. Those checks then immediately get run again in the merge
   queue. If you do one final fix‐up to an approved PR, it takes
   half an hour after that to merge instead of fifteen minutes. We
   make this less painful by using auto‐merges, but it’s silly
   to block on the PR checks when the actual guarantees are provided
   by the merge queue.

   Unfortunately GitHub demands the same jobs be required for putting
   something into the merge queue and taking it out. We can work
   around this by only requiring the `required-checks` job and having
   it report its status depending on the context.

Tasks for Martin:

1. Go to <https://github.com/jj-vcs/jj/settings> and disable “Allow
   auto-merge”. This would now only block on PR approval / discussion
   resolution, so it’s probably more confusing than helpful; I’ve
   hit the button and then come back an hour later to discover that
   I forgot to resolve discussions.

   Go to <https://github.com/jj-vcs/jj/settings/rules/3400426> and
   replace all of the required GitHub Actions checks under “Require
   status checks to pass” with the single `required-checks` check.

3. While you’re at it, go to
   <https://github.com/jj-vcs/jj/settings/actions> and ensure that
   “Workflow permissions” is set to “Read repository contents
   and packages permissions”. We already handle this correctly in
   all our workflows but the default is to allow write permissions
   for legacy reasons.
2025-03-12 18:15:50 +00:00
Emily
eba4257ab9 github: don’t use auto‐merges for dependabot
Since we no longer want auto‐merge in general.
2025-03-12 17:41:55 +00:00
Emily
c8477b78f4 github: simplify dependabot concurrency settings 2025-03-12 17:41:55 +00:00
Emily
6674ae7e96 github: don’t run dependabot workflow in the merge queue
This is totally pointless I think, since the point of the workflow
is to queue things in the first place.
2025-03-12 17:41:55 +00:00
Joachim Desroches
67ffbfa14f cli: annotate: do not panic on reaching initial commit.
As described in #5909, in the case where jj was initialized in a
shallowly cloned repository which was then unshallow'd, jj does not
import the fetched commits that were outside the shallow boundary.

However, it does import the ones after the boundary, which after
unshallowing do not contain the changes made before the boundary.

Therefore, when running annotate in such a case, jj would panic because
it does not find the commit from which a line originates. This sets the
empty commit as carrying the blame for that line.
2025-03-12 13:45:57 +00:00
Yuya Nishihara
dcf4771a07 cli: emit warning for deprecated diff.format, move default to config/misc.toml
The old name "diff.format" has been deprecated since 0.7.0, but there was no
deprecation warning.
2025-03-12 13:42:41 +00:00
Benjamin Tan
d928ef6f96 cli_util: compute_commit_location: return CommitId instead of Commit
Not all callers of `compute_commit_location` require the full `Commit`
struct.
2025-03-12 10:12:05 +00:00
Benjamin Tan
3f78de300b cli_util: WorkspaceCommandHelper::resolve_some_revsets_default_single: return CommitId instead of Commit 2025-03-12 10:12:05 +00:00
Yuya Nishihara
902ef9fce3 diff: fix inconsistent handling of "short" format arguments
All "short" formats should be able to be combined with -p/--patch. It was also
weird that "short" diff stats followed "long" color-words/git diffs.

Fixes #5986
2025-03-12 04:32:01 +00:00
Martin von Zweigbergk
a01d0bf773 cli: resolve: leave executable bit unchanged when using external tool
`jj resolve` will currently try to resolve the executable bit and will
clear it if it can't be resolved. I don't think the executable bit
should be affected by the external merge tool. We could preset the
flags on the files we pass to the merge tool and then expect the merge
tool to update the flags, but we don't do that. I'm not sure that's a
good idea (few merge tools probably expect that), but since we don't
do it, I think it's better to leave the executable bits
unchanged. This patch makes it so by calling
`Merge::with_new_file_ids()` on the original conflict whether or not
the content-level conflict was resolved.

I noticed this was while working on the copy-tracking proposal in PR
#4988, which involves adding copy information in the `TreeValue`. If
we do implement that, then we should preserve copy information here
too (in addition to the executable flag). That will automatically work
after this patch.
2025-03-12 04:15:17 +00:00
Martin von Zweigbergk
9fe2075650 cli: chmod: update TreeValues in place
If we add copy info to `TreeValue::File`, we will want to preserve
that when the user runs `jj chmod`. It's easier to preserve it by
updating the `Merge<Option<TreeValue>>` in place, so that's what this
patch does.
2025-03-12 04:15:17 +00:00
Ilya Grigoriev
2f97d4a3d8 cli clone and init: remove unhelpful parts of clap error
We could replace clap's "SuggestedSubcommand" instead of removing it,
but then the user might not notice that this is a custom suggestion and
not pay enough attention to it.

This uses the just-released https://github.com/clap-rs/clap/pull/5941.
2025-03-12 04:02:52 +00:00
Ilya Grigoriev
2bb8775c44 cli clone and init: color test for the error message
There are alternative ways to solving the problem addressed by the next
commit, but they don't seem to preserve color well. So, let's have a
test to make sure future refactorings can tell whether this issue is
still relevant.
2025-03-12 04:02:52 +00:00
Ilya Grigoriev
c38e316a91 cli op diff: show changes to the working copy positions
I find this useful for debugging what happened sometimes,
e.g. after some concurrent operations mess.
2025-03-12 03:41:27 +00:00
Ilya Grigoriev
270924ddaa lib: new diff_named_commit_ids helper 2025-03-12 03:41:27 +00:00
Ilya Grigoriev
ca46ce1d3a view: store working copies in BTreeMap
This is analogous to how bookmarks are stored and simplifies the
following commit.
2025-03-12 03:41:27 +00:00
Ilya Grigoriev
7d7a2fa390 cli string patterns: explain case-insensitive string prefixes
Before reading the docs, my instinct was to try `iglob:` instead of the
correct `glob-i:`.
2025-03-12 03:09:03 +00:00
Yuya Nishihara
e054a291b1 git: use translated remote symbols in import_refs()
This removes the special case for RefName::LocalBranch(_), which can be
processed as a remote bookmark.

"jj git import" now prints local bookmarks and tags with @git suffix. I think
this is more correct since these refs are imported from the backing Git
repository.
2025-03-12 02:20:00 +00:00
Yuya Nishihara
540ae1b697 git: pass remote (kind, symbol) parameters to git_ref_filter()
I'll reimplement import/export internals to be based off the translated remote
symbols.
2025-03-12 02:20:00 +00:00
Yuya Nishihara
78361eb4f8 cli: deduplicate RefKind types
They're identical except for naming.
2025-03-12 02:20:00 +00:00
Yuya Nishihara
907b20162f git: extract function that parses git ref to remote symbol
The old parse_git_ref() function and RefName type will be removed.
2025-03-12 02:20:00 +00:00
Yuya Nishihara
d8ae6dc8c9 tests: port test_bookmark_command.rs to TestWorkDir API 2025-03-12 02:18:56 +00:00
Yuya Nishihara
8fc6dea310 tests: port test_backout_command.rs to TestWorkDir API 2025-03-12 02:18:56 +00:00
Yuya Nishihara
b87a50783d tests: port test_alias.rs to TestWorkDir API 2025-03-12 02:18:56 +00:00
Yuya Nishihara
a68620a803 tests: port test_advance_bookmarks.rs to TestWorkDir API 2025-03-12 02:18:56 +00:00
Yuya Nishihara
76e9342bbb tests: port test_acls.rs to TestWorkDir API 2025-03-12 02:18:56 +00:00
Yuya Nishihara
d5d4c7fe79 tests: port test_absorb_command.rs to TestWorkDir API, add read_file() helper
read_file() returns BString as it implements Display.
2025-03-12 02:18:56 +00:00
Lucas Garron
4e22902249 Replace the PNG logo with a vector SVG version.
This version is significantly smaller and crisp at any resolution.

Update docs/index.md
2025-03-11 22:44:13 +00:00
dependabot[bot]
e72a85599f cargo: bump the cargo-dependencies group with 5 updates
Bumps the cargo-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.31` | `4.5.32` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.7.1` | `2.8.0` |
| [libc](https://github.com/rust-lang/libc) | `0.2.170` | `0.2.171` |
| [once_cell](https://github.com/matklad/once_cell) | `1.20.3` | `1.21.0` |
| [rustix](https://github.com/bytecodealliance/rustix) | `1.0.1` | `1.0.2` |


Updates `clap` from 4.5.31 to 4.5.32
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.31...clap_complete-v4.5.32)

Updates `indexmap` from 2.7.1 to 2.8.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.7.1...2.8.0)

Updates `libc` from 0.2.170 to 0.2.171
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.171/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.170...0.2.171)

Updates `once_cell` from 1.20.3 to 1.21.0
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.3...v1.21.0)

Updates `rustix` from 1.0.1 to 1.0.2
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 18:52:35 +00:00
Martin von Zweigbergk
fe846d69d4 faq: in entry about default jj log revset, explain rationale
The FAQ entry explaining why `jj log` doesn't show all commits
explained that the behavior is configurable but it didn't explain what
the rationale for not showing all commits is. Users coming from Git
are used to seeing all commits and probably read this FAQ entry to
find an answer. We don't want them to just update their config without
understanding why we have the default we have.
2025-03-11 17:55:32 +00:00
Yuya Nishihara
d37a5b1b74 fileset, revset: settle on optionally-quoted pattern syntax
Suppose revsets and filesets are primarily used in command shell, it would be
annoying if quoting is required in addition to the shell quoting. We might also
want to relax the revset parser to allow bare * in glob string.

Closes #2101
2025-03-11 08:35:27 +00:00
Martin von Zweigbergk
dc7216d73a cli: diff: support multiple revisions to -r 2025-03-11 06:27:51 +00:00
Emily
f862d89143 git: port Git state clean‐up from git2 2025-03-11 03:09:13 +00:00
Emily
185a09b9ca git: add test for Git state clean‐up 2025-03-11 03:09:13 +00:00
Ilya Grigoriev
09426ac222 cli fileset errors: insert link to docs 2025-03-11 03:00:58 +00:00
Ilya Grigoriev
bc06e66313 fileset docs: edits to quoting and file pattern instructions
- Swapped `file:"path"` and `cwd-file:"path"` for consistency with the first line

- Created a labeled section for quoting rules

- Elaborated on the quoting rules in the beginning of file pattern section.
2025-03-11 03:00:58 +00:00
Emily
fe9e71b90e git: remove .gitmodules parsing code
This is redundant with `gix`’s native API and not used by anything
but a hidden debugging command.
2025-03-11 02:37:13 +00:00
Yuya Nishihara
1a8d2f5195 tests: use TestWorkDir in test_abandon_command.rs 2025-03-11 01:23:13 +00:00
Yuya Nishihara
e802c4b8fe tests: move current_operation_id() to TestWorkDir 2025-03-11 01:23:13 +00:00
Yuya Nishihara
61a449554b tests: pass &TestWorkDir to create_commit*() helper
Callers will be ported to TestWorkDir API separately.
2025-03-11 01:23:13 +00:00
Jo Liss
4e3f43c261 docs: explain that jj root is synonymous with jj workspace root 2025-03-10 21:44:06 +00:00
Emily
b2d63189b9 git: use try_collect 2025-03-10 21:22:42 +00:00
dependabot[bot]
9a15ce5fbd github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action).


Updates `taiki-e/install-action` from 2.49.16 to 2.49.19
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](1426bdb9e2...87b5304d4e)

Updates `EmbarkStudios/cargo-deny-action` from 2.0.10 to 2.0.11
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](4de59db63a...34899fc7ba)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 17:34:36 +00:00
dependabot[bot]
517d647091 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [rustix](https://github.com/bytecodealliance/rustix), [serde](https://github.com/serde-rs/serde) and [syn](https://github.com/dtolnay/syn).


Updates `rustix` from 0.38.44 to 1.0.1
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.44...v1.0.1)

Updates `serde` from 1.0.218 to 1.0.219
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.218...v1.0.219)

Updates `syn` from 2.0.99 to 2.0.100
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.99...2.0.100)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: cargo-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 17:33:43 +00:00
demize
c3cc3de818 git: clean up remove_remote_git_config_sections 2025-03-10 15:08:38 +00:00
demize
cf696ce1b6 git: update gitoxide repository options
This fixes tests in --release and provides safer
defaults.
2025-03-10 15:08:38 +00:00
Emily
eeb34146ca tests: remove libgit2 test performance hack
I think we should only initialize the library for the fetch/push
tests now, so it should be okay to drop this.
2025-03-10 15:08:08 +00:00
Emily
ad35f865ed git_backend: remove obsolete libgit2 workaround 2025-03-10 15:07:46 +00:00
Caleb White
072af8448f cli: bookmark move: allow short aliases for --to/--from
If `--to` is going to become a required argument, it should
have a short alias as it will be used quite frequently.

Given that `--to` has a short alias it only makes sense to
allow `-f` for `--from` as this is consistent with other
commands and nothing makes this particular command special.
2025-03-10 13:02:08 +00:00
Yuya Nishihara
16028245d6 tests: add workspace test helper, use it in test_global_opts.rs
It's super common to pair test_env with repo_path.

Some of the tests still use absolute paths because it seemed rather confusing
to mix paths relative to the repo_root and to the env_root.
2025-03-10 07:50:18 +00:00
Martin von Zweigbergk
5f8654ef68 revset: remove unused lifetime parameter from RevsetIteratorExt
It became unused in 75605e36.
2025-03-10 07:49:59 +00:00
Yuya Nishihara
c0066083ad git: make import of refs/remotes/git/* non-error, warn failed refs instead
I'm going to reimplement git_ref_filter to process translated remote bookmark
names, and "git" remote will mean the local Git-tracking remote there. The
reserved remote name is checked prior to filtering because refs in that remote
cannot be represented as remote symbols.

I originally implemented the error handling the other way because we didn't
have a machinery to report partial import failure. Now we have stats, it's
easy to report skipped ref names.
2025-03-10 02:04:33 +00:00
Yuya Nishihara
cc1b716d39 git: plumbing to report unimportable refs, warn non-utf-8 names
Tests will be added by the next patch. It's not important to report non-utf-8
refs, but this patch implements it as doing that was easy.
2025-03-10 02:04:33 +00:00
Emily
fd7f1f558e cli: remove git_util::get_git_repo
No longer used by anything.
2025-03-09 21:17:10 +00:00
Emily
2e0bdd3396 cli: use gix in maybe_set_repository_level_trunk_alias 2025-03-09 21:17:10 +00:00
Emily
8297938feb git: port remote management to gix 2025-03-09 21:17:10 +00:00
Emily
09f6cafa2b git: add remote management tests 2025-03-09 21:17:10 +00:00
Emily
c25aa6e417 git: abstract git2 in remote management API
This is more consistent with other similar APIs and minimizes churn
in the test code as we move these to `gix`.
2025-03-09 21:17:10 +00:00
Yuya Nishihara
3318d172ff cli: bookmark: scan deleted local bookmarks in separate loop
We no longer have to do multiple things in one loop.
2025-03-09 09:33:07 +00:00
Yuya Nishihara
791e04954e cli: bookmark: collect all list items to temporary vec
I think this will help implement sorting options. Untracked remote bookmarks
should be sorted independently, whereas tracked remote bookmarks should always
be listed after the associated local bookmarks.
2025-03-09 09:33:07 +00:00
Yuya Nishihara
cdcaee62f1 templater: add public getter methods to RefName type
Some of these will be called from "jj bookmark list". The template RefName type
is useful as an abstract local/remote ref object. Maybe it should be renamed to
RefEntry or CommitRef.
2025-03-09 09:33:07 +00:00
Martin von Zweigbergk
0055cd4e0b cli tests: move create_commit() tests helpers to common/ 2025-03-09 00:44:25 +00:00
Martin von Zweigbergk
169c131902 test_git_fetch: move clarification of description to template
I'm about to move the `create_commit()` helper to a common
place. However, this version of `create_commit()` is different from
the others in that it put a prefix of "descr_for_" in the
description. This patch removes that and instead updates the template
so it's still clear what's a description and what's a bookmark name.
2025-03-09 00:44:25 +00:00
Caleb White
813d2f1e6d config: update diff-editor and merge-editor schema
The docs show that the diff-editor, and merge-editor settings
can be a string or an array of strings. This updates the config
schema to reflect that.
2025-03-09 00:16:04 +00:00
Martin von Zweigbergk
95aac580b0 rewrite: fix a Clippy lint 2025-03-08 20:45:39 +00:00
Emily
596738a303 cargo: bump git2 to 0.20.0 2025-03-08 16:50:58 +00:00
Benjamin Tan
c248d97679 cli: debug copy-detection: fix help text
It looks like the help text was copied from another command and not
updated.
2025-03-08 15:28:55 +00:00
Anton Älgmyr
a209f522ab docs: Update documentation wrt JJ_CONFIG and --when/--scope
This is hidden gem of a feature, especially in combination.

`JJ_CONFIG` being a directory allows loading multiple TOML config files.
`--when` can be used on the top level, which can lend itself to a much
cleaner config than `[[--scope]]` tables.
2025-03-08 11:19:39 +00:00
Ilya Grigoriev
7b38f82b3f cli op log: short -d alias for --op-diff
`--op-diff` is often more useful than `-p`/`--patch`.
2025-03-08 06:49:34 +00:00
Yuya Nishihara
697cd504ff cli: pass clap::Error to map_clap_cli_error() 2025-03-08 04:33:09 +00:00
Yuya Nishihara
9bf2e9b20d cli: move tracing configuration out of parse_args()
This helps simplify the error type. It's also weird that the parsing function
sets up tracing subscription.
2025-03-08 04:33:09 +00:00
dependabot[bot]
3df26f2d70 github: bump the github-dependencies group across 1 directory with 2 updates
Bumps the github-dependencies group with 2 updates in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.49.15 to 2.49.16
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](955a6ff141...1426bdb9e2)

Updates `github/codeql-action` from 3.28.10 to 3.28.11
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b56ba49b26...6bb031afdd)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-08 02:12:22 +00:00
dependabot[bot]
1f38488b07 cargo: bump the cargo-dependencies group across 1 directory with 2 updates
Bumps the cargo-dependencies group with 2 updates in the / directory: [tempfile](https://github.com/Stebalien/tempfile) and [tokio](https://github.com/tokio-rs/tokio).


Updates `tempfile` from 3.17.1 to 3.18.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.17.1...v3.18.0)

Updates `tokio` from 1.43.0 to 1.44.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.44.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-08 01:53:32 +00:00
Yuya Nishihara
a27d925565 cleanup: switch to use<'_> capturing syntax
This patch fixes `'a: 'b, 'a` lifetime constraints, adds use<>, use<'_>, etc.
where the default would differ between editions. Redundant '_ is also removed.

https://doc.rust-lang.org/stable/edition-guide/rust-2024/rpit-lifetime-capture.html#edition-specific-rules-when-no-use-bound-is-present
2025-03-08 01:16:56 +00:00
Yuya Nishihara
b255dbed0a cleanup: remove unneeded lifetime capturing from snapshot_progress()
ui.progress_output() doesn't borrow anything from &Ui, and there would be no
reason to extend the borrow. Write ops on Ui don't require mutable instance.
2025-03-08 01:16:56 +00:00
Yuya Nishihara
df3c15a030 revset: remove support for multiple files() arguments
Since there are no revset functions that require at least N arguments, the
error message test is moved to test_templater.rs.
2025-03-08 00:54:28 +00:00
Yuya Nishihara
d9615800ec revset: replace use of deprecated file() alias in tests 2025-03-08 00:54:28 +00:00
Yuya Nishihara
bf54158fa2 revset: remove deprecated singular aliases 2025-03-08 00:54:28 +00:00
Yuya Nishihara
54868877d6 revset, templater: remove deprecated "branches" aliases 2025-03-08 00:54:28 +00:00
Evan Mesterhazy
0edf23eb16 lib rewrite: Add a test for CommitWithSelection
This is in preparation for adding a new function that inverts the selection.
2025-03-08 00:11:29 +00:00
Evan Mesterhazy
29e2ccf4ae cargo-deny: Ignore paste crate deprecation
The paste crate hasn't had a maintainer since Oct. 2024. We don't use it
directly, but it's used by ratatui, and there's no direct replacement for it
AFAIK.

Advisory: https://rustsec.org/advisories/RUSTSEC-2024-0436

This is currently blocking PRs because ci fails:
```
error[unmaintained]: paste - no longer maintained
    ┌─ /github/workspace/Cargo.lock:262:1
    │
262 │ paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index
    │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ unmaintained advisory detected
    │
    ├ ID: RUSTSEC-2024-0436
    ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2024-0436
    ├ The creator of the crate `paste` has stated in the [`README.md`](https://github.com/dtolnay/paste/blob/master/README.md) 
      that this project is not longer maintained as well as archived the repository
    ├ Announcement: https://github.com/dtolnay/paste
    ├ Solution: No safe upgrade is available!
    ├ paste v1.0.15
      └── ratatui v0.29.0
          └── scm-record v0.5.0
              └── jj-cli v0.27.0
                  └── (dev) jj-cli v0.27.0 (*)

advisories FAILED
```
2025-03-07 19:21:36 +00:00
George Christou
ad39c97d04 cli: remove untrack subcommand 2025-03-07 11:00:08 +00:00
Yuya Nishihara
dfb10cab9b git: reject reserved remote name early in fetch() function
I'm going to make git::import_refs() not fail because of a real remote named
"git", but fetching from such remote should be an error.
2025-03-07 03:03:45 +00:00
Yuya Nishihara
b2fd1a002e git: extract helper to check unsupported remote name 2025-03-07 03:03:45 +00:00
Yuya Nishihara
99bc2d2f7d git: consolidate when to check unsupported remote name on push
It's odd that "foo/bar" is tested earlier in push_branches() whereas "git" is
rejected by push_updates(). Suppose push_updates() is a low-level function to
push arbitrary refs without updating jj's repo view, it's probably okay to allow
unsupported remote name here.
2025-03-07 03:03:45 +00:00
George Christou
5eb3c5b658 cli: remove unsquash subcommand 2025-03-07 01:46:19 +00:00
Brandon Hall
e71650a27a Update community_tools.md
Added a link and blurb about the took VS Code extension Jujutsu Kaizen
2025-03-07 01:13:25 +00:00
Martin von Zweigbergk
9aeb13488c docs: correct description of tracking of ignored files 2025-03-06 22:29:05 +00:00
Ilya Grigoriev
5eae2d92a0 tests: run insta --force-update-snapshots
This is a replacement for #5558.

Thanks to @yuja 's https://github.com/mitsuhiko/insta/pull/722, this is
now easy to generate.
2025-03-06 21:35:08 +00:00
dependabot[bot]
09f7613f46 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [either](https://github.com/rayon-rs/either), [indoc](https://github.com/dtolnay/indoc) and [textwrap](https://github.com/mgeisler/textwrap).


Updates `either` from 1.14.0 to 1.15.0
- [Commits](https://github.com/rayon-rs/either/compare/1.14.0...1.15.0)

Updates `indoc` from 2.0.5 to 2.0.6
- [Release notes](https://github.com/dtolnay/indoc/releases)
- [Commits](https://github.com/dtolnay/indoc/compare/2.0.5...2.0.6)

Updates `textwrap` from 0.16.1 to 0.16.2
- [Release notes](https://github.com/mgeisler/textwrap/releases)
- [Changelog](https://github.com/mgeisler/textwrap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mgeisler/textwrap/compare/0.16.1...0.16.2)

---
updated-dependencies:
- dependency-name: either
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: indoc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: textwrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-06 21:26:56 +00:00
Evan Mesterhazy
3ab5f1d197 cli split: Refactor and move tree selection to a function
This change moves the code for prompting the user to select changes for the
first commit to a helper function. There are no functional changes.

Resulting from the discussion in https://github.com/jj-vcs/jj/pull/5828
we are planning to add several new flags (-B, -A, and -d) to jj split.

Since this may change how the user selects changes (i.e. they may select
changes to remove from the target commit, rather than what stays in the target
commit), I want to move the selection logic to a function to make the main
split function more readable.
2025-03-06 14:38:29 +00:00
Evan Mesterhazy
707680be22 rewrite: Rename CommitToSquash to CommitWithSelection
Removing "Squash" from the name will allow this type to be used in split as
well without it being confusing.
2025-03-06 14:38:29 +00:00
Evan Mesterhazy
0b583ff3a7 cli split: Add a function to resolve the raw cli arguments
This is part of a refactor of cmd_split in preparation for adding new -A, -B,
and -d arguments to control where the changes split from the target commit end
up.

The goal is to simplify the flow in the main cmd_split function and manage
complexity introduced by the new options. In this case, the raw arguments will
be parsed once at the beginning of the command in resolve_args, after which the
raw SplitArgs are no longer used.

The function is pretty simple now, but will evolve once the new arguments are
added.
2025-03-06 14:38:29 +00:00
Yuya Nishihara
c6ed2afbfa tests: wrap formatter output so insta won't trim trailing whitespace
Not all tests should be strict about whitespace, but formatting tests usually
should.
2025-03-06 13:47:12 +00:00
George Christou
693be6cc02 config: remove support for git.push-branch-prefix 2025-03-06 11:47:01 +00:00
Jonathan Gilchrist
742bc8af1c config: Allow hiding the 'how to resolve conflicts' hint
This only deals with a single hint for now to resolve any discussions
around naming and approach.
2025-03-06 10:49:54 +00:00
George Christou
b7f7d923bd config: rename core.watchman.register_snapshot_trigger 2025-03-06 08:42:35 +00:00
Ilya Grigoriev
9af7e5b7ed cargo: use MSRV-aware resolver 2025-03-06 07:24:28 +00:00
Ilya Grigoriev
c36bfafb6a clippy: replace allow directives with expect or delete them when possible
Also resolves one TODO made possible by the new MSRV

Most of this was done by enabling the lint forbidding `allow` directives
using `cargo cranky`, running `cargo cranky --workspace
--all-featuers --fix`, and fixing up the result.
2025-03-06 07:24:28 +00:00
Ilya Grigoriev
1ede79c483 MSRV: Update to 1.84 and run clippy --fix, cargo fmt
The CI seems to correctly use rustc 1.84.1 (and not 1.84.0) with this.

For reference, we last updated the MSRV to 1.76 in 5b517b5. According to
https://releases.rs/docs/1.76.0/, this was when it barely became stable.

`flake.nix` seems to be using a nightly toolchain now, so it seems there
is no need to update the version there.

The more precise clippy command used was:

cargo clippy --workspace --all-targets --fix
2025-03-06 07:24:28 +00:00
dependabot[bot]
4d4ec1afc6 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.13 to 2.49.15
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](970d55e3ce...955a6ff141)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-06 02:01:54 +00:00
Ilya Grigoriev
6ce7a77da5 release: 0.27.0 2025-03-05 23:48:50 +00:00
Ilya Grigoriev
2bcf0384e2 FAQ: detailed example for the "accidentally changed files in the wrong commit" question
It was requested on Discord once.

The result is almost a tutorial; we could consider actually moving it into the
tutorial and providing a link in the FAQ. That could be done separately.

The `jj` commands outputs are faked in a few, hopefully inconspicuous, ways.
Hopefully, this is not distracting.
2025-03-05 23:46:32 +00:00
Yuya Nishihara
b0e9bdffc0 settings: load signing parameters early, propagate config error
Typo in signing.behavior shouldn't be ignored.

The idea is the same as [user] and [operation] tables. It's easier if all
parameters needed to create a commit is parsed by UserSettings constructor.
Another option is to make repo.start_transaction() fail on config error.
2025-03-05 10:02:42 +00:00
Ilya Grigoriev
1449c2d040 debug init-local: remove the ui.allow-init-native option
It is not needed to gate `jj debug init-local` on an option now that the
command is no longer called `jj init`.

This also includes a separate fixup to #5845 in demos/
2025-03-05 05:52:02 +00:00
Ilya Grigoriev
5590e9473e jj debug init-local: fixup, docs
Fixups to #5845
2025-03-05 05:52:02 +00:00
Baltasar Dinis
4c5800ac4a cli/tests: port test_git_clone to gitoxide 2025-03-05 05:39:19 +00:00
Baltasar Dinis
09d92e8278 cli/tests: port test_git_fetch to gitoxide 2025-03-05 05:39:19 +00:00
Baltasar Dinis
876262a41b cli/tests: have clone helper take in a remote name
This is relevant for the git_fetch tests
2025-03-05 05:39:19 +00:00
Baltasar Dinis
a19787d2d4 cli/tests: port test_git_push to gitoxide 2025-03-05 05:39:19 +00:00
Baltasar Dinis
c2a92fce37 cli/tests: port test_git_remotes to gitoxide 2025-03-05 05:39:19 +00:00
Yuya Nishihara
4de95ac2f5 cli: reimplement "jj revert" stub as error hint
We might rename "jj backout" to "jj revert", but until then, user can add alias
as they want.

Closes #5335
Closes #5701
2025-03-05 05:22:51 +00:00
Yuya Nishihara
7711fc14e6 cli: sort debug commands lexicographically 2025-03-05 05:22:45 +00:00
Baltasar Dinis
61f3732ad8 docs: update the docs to best describe the authentication compatibility 2025-03-05 04:33:57 +00:00
Ilya Grigoriev
d446a85c4a cli: make init and clone print a friendly error but be overridable with aliases
The main goal here is so that a completely new user that types in `jj
init` or `jj clone` gets a pointer to the correct command needed to try
out `jj`.
2025-03-05 02:53:48 +00:00
Ilya Grigoriev
22bc271a7b cli: move jj init to jj debug init-local
We could also incorporate parts of #5827 later.
2025-03-05 02:53:48 +00:00
Ilya Grigoriev
8bd7d3daea test_global_opts: use the Git backend
Follows up on #5875
2025-03-05 02:53:48 +00:00
dependabot[bot]
f1bb889473 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action).


Updates `taiki-e/install-action` from 2.49.10 to 2.49.13
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](3c8fc6eaa5...970d55e3ce)

Updates `EmbarkStudios/cargo-deny-action` from 2.0.6 to 2.0.10
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](0484eedcba...4de59db63a)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 01:35:59 +00:00
Martin von Zweigbergk
88157c32b9 tests: avoid using the local backend
Now that we depend on the `git` executable being available for `jj git
fetch/push` tests, we might as well use it for `jj util gc` tests
too.

I also switched to using the Git backend in
`cli/tests/test_file_track_untrack_commands.rs`, which seemed to be
using the local backend for no good reason.
2025-03-04 22:12:17 +00:00
Jonathan Tan
507e4b033d lib/testutils/Cargo.toml: remove unused gix feature
The gix feature "blocking-network-client" was configured in cd6141693
(cli/tests: add gitoxide helpers, 2025-02-03) most likely because
we needed to clone using gix in tests, but 071e724c1 (cli/tests:
move test_git_colocated_fetch_deleted_or_moved_bookmark to gitoxide,
2025-02-25) changed the test to clone by subprocessing out to system git
(not by using gitoxide, as a cursory read of the commit description may
indicate), meaning that we no longer need that feature.

Therefore, remove that feature.
2025-03-04 20:39:20 +00:00
Ilya Grigoriev
02e2f06e7a mkdocs: Suppress a warning that paid_contibutors.md is not in nav
The warning this suppresses is shown by `mkdocs serve`:

```
INFO    -  The following pages exist in the docs directory, but are not included in the "nav" configuration:
             - paid_contributors.md
```

We can alternatively add that doc to nav.
2025-03-04 20:00:12 +00:00
Scott Taylor
a95281f9ca docs: fix syntax highlighting for conflicts.md
I think the `diff` syntax highlighting makes these examples harder to
read since it's inconsistent and wouldn't appear in real conflicts.
2025-03-03 22:42:14 +00:00
dependabot[bot]
cf3b45878e cargo: bump the cargo-dependencies group with 9 updates
Bumps the cargo-dependencies group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.96` | `1.0.97` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.86` | `0.1.87` |
| [insta](https://github.com/mitsuhiko/insta) | `1.42.1` | `1.42.2` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.93` | `1.0.94` |
| [quote](https://github.com/dtolnay/quote) | `1.0.38` | `1.0.39` |
| [ref-cast](https://github.com/dtolnay/ref-cast) | `1.0.23` | `1.0.24` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.139` | `1.0.140` |
| [syn](https://github.com/dtolnay/syn) | `2.0.98` | `2.0.99` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.11` | `2.0.12` |


Updates `anyhow` from 1.0.96 to 1.0.97
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.96...1.0.97)

Updates `async-trait` from 0.1.86 to 0.1.87
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.86...0.1.87)

Updates `insta` from 1.42.1 to 1.42.2
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.42.1...1.42.2)

Updates `proc-macro2` from 1.0.93 to 1.0.94
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.93...1.0.94)

Updates `quote` from 1.0.38 to 1.0.39
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.38...1.0.39)

Updates `ref-cast` from 1.0.23 to 1.0.24
- [Release notes](https://github.com/dtolnay/ref-cast/releases)
- [Commits](https://github.com/dtolnay/ref-cast/compare/1.0.23...1.0.24)

Updates `serde_json` from 1.0.139 to 1.0.140
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.139...v1.0.140)

Updates `syn` from 2.0.98 to 2.0.99
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.98...2.0.99)

Updates `thiserror` from 2.0.11 to 2.0.12
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.11...2.0.12)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: insta
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: ref-cast
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 18:19:47 +00:00
dependabot[bot]
1cd16874aa github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.49.8 to 2.49.10
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](dccf3df6e0...3c8fc6eaa5)

Updates `astral-sh/setup-uv` from 5.3.0 to 5.3.1
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](1edb52594c...f94ec6bedd)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 18:18:56 +00:00
Benjamin Tan
ea8e4ac771 cli_util: extract shared compute_commit_location function
This will be used to compute the location to place commits from the
`--destination`, `--insert-after`, and `--insert-before` options. This
is used across the `new`, `duplicate`, and `rebase` commands, and can be
used for further commands as well.
2025-03-03 14:56:32 +00:00
Benjamin Tan
48b850b3a5 cli: new: --insert-after: remove special handling of commits which are ancestors
The previous implementation of `new --insert-after` excluded children
that were ancestors of the new commit. This was done to avoid creating
cycles in the commit graph. However, this does not align with the
behavior of the `--insert-after` in the other commands, which explicitly
error if inserting a new commit after the given `--insert-after` commits
would create a cycle.

For consistency, and given that the desired behavior can be achieved
using by using `--insert-after` along with `--insert-before`, this
behavior is removed.
2025-03-03 14:56:32 +00:00
Yuya Nishihara
7e8dba8d94 op_store: validate operation/view id length to detect corruption earlier
After system crash, file contents are often truncated or filled with zeros. If
a file was truncated to empty, it can be decoded successfully and we'll get
cryptic "is a directory" error because of an empty view ID. We should instead
report data corruption with the ID of the corrupted file.

#4423
2025-03-03 01:18:34 +00:00
Yuya Nishihara
2eb6a0198b op_store: remove redundant DecodeError type
It should be equivalent to OpStoreError::ReadObject. Perhaps, we couldn't use
ReadObject before because the source error type wasn't dyn Error.
2025-03-03 01:18:34 +00:00
Yuya Nishihara
6261d576da git: use gix::ObjectId::from_bytes_or_panic() consistently 2025-03-02 02:19:00 +00:00
Burak Varlı
7b52ff51f0 cli git fetch: support string pattern syntax in remote option
Signed-off-by: Burak Varlı <unexge@gmail.com>
2025-03-01 12:11:19 +00:00
Baltasar Dinis
038da961c0 cli/tests: move test_git_colocated_unborn_bookmarks to gitoxide 2025-03-01 09:54:31 +00:00
Baltasar Dinis
071e724c1c cli/tests: move test_git_colocated_fetch_deleted_or_moved_bookmark to gitoxide 2025-03-01 09:54:31 +00:00
Baltasar Dinis
05479df69e cli/tests: move most of the test_git_colocated tests to gitoxide 2025-03-01 09:54:31 +00:00
pylbrecht
7b229e6b97 sign: format link in help
Following the existing conventions for formatting links.
2025-03-01 07:31:55 +00:00
pylbrecht
0e498c7dcb sign: link to docs in jj sign and jj unsign 2025-03-01 07:31:55 +00:00
Evan Mesterhazy
2af5b60d32 cli split: Disable the new bookmark behavior for jj split
The consensus about this change, if one ever existed, seems to have dissolved
into two cohorts, one which is happy with the change, and one which thinks we
should move both the bookmark and change id to the child commit instead of
leaving them on the parent.

We may decide to add flags to allow users to choose between the two behaviors,
but there are also other concerns such as where @ should go (parent or child).
Until we agree on a path forward it seems reasonable to delay the breaking
change by disabling it via the config option we added. I don't think it's
necessary to fully revert the code and new tests since we aren't announcing the
option.


#3419
2025-02-28 23:23:06 +00:00
dependabot[bot]
8a6bfc9ee9 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.7 to 2.49.8
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ada1a57be8...dccf3df6e0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-28 17:59:06 +00:00
Evan Mesterhazy
8046ecefba cli split: Rename the "commit" variable to "target_commit"
This has bothered me for a while... It's useful to describe the target of the
split command as the "target commit", and it's a little confusing to have a
variable named "commit" in the code when we are dealing with several different
commits in the command.

I think this improves readability by being more precise.
2025-02-28 12:15:31 +00:00
Yuya Nishihara
63f874376c tests: make git::IndexManager::new() preserve the staged changes
It's odd that IndexManager::new(repo) creates a new empty index ignoring the
current state. The callers appear not to depend on this behavior.
2025-02-28 11:19:46 +00:00
Yuya Nishihara
2586ec5b75 tests: remove unused field from git::IndexManager 2025-02-28 11:19:46 +00:00
pylbrecht
e5ca254084 sign: add --key argument to jj sign
Following up on #4747 (see
https://github.com/jj-vcs/jj/pull/4747#discussion_r1968720554).
2025-02-28 07:52:57 +00:00
pylbrecht
7bf4148169 commit_builder: split set_sign_key()
Clearing a sign key would require calling `set_sign_key(None)`, which
makes the API slightly awkward.

Instead, we introduce `clear_sign_key()` and make `set_sign_key()` only
accept a `String`. This makes clearing the sign key explicit.

Co-Authored-By: Yuya Nishihara <yuya@tcha.org>
2025-02-28 07:52:57 +00:00
Yuya Nishihara
b15769448b cli: provide better hint on bookmark name parse error
It should be better to say parsing failed, rather than starting with detailed
pest error message.
2025-02-28 07:27:40 +00:00
Yuya Nishihara
72b24d18e3 revset: box RevsetParseErrorKind to keep error type small
RevsetParseErrorKind has variants containing 3 String fields, which ends up 80
bytes.
2025-02-28 07:27:40 +00:00
Yuya Nishihara
0b56863aed revset: export low-level parse_program() function
This is useful to check if an input text can be parsed as a revset expression.
2025-02-28 07:27:40 +00:00
Yuya Nishihara
539f242e6c cli: print error sources if clap cannot parse arguments
Bookmark name error will be nested in order to provide a better error hint.
2025-02-28 07:27:40 +00:00
pylbrecht
c90f0b96a4 sign: add default revisions for jj sign
When signing commits with `jj sign`, one might want to use a workflow
like:

```bash
jj fix && jj sign .. && jj git push
```

Making the default value for `-r`/`--revisions` configurable, will allow
such a workflow.

Co-Authored-By: Yuya Nishihara <yuya@tcha.org>
2025-02-28 07:14:59 +00:00
Aleksey Kuznetsov
cfcd034879 docs: Add pointers to usage of jj help -k <keyword>
Relates to #5306
2025-02-28 06:52:52 +00:00
Yuya Nishihara
6c597d962d git: on reset_head(), check if the actual HEAD points to known location
This will probably mitigate problems of concurrent updates. If two concurrent
processes tried to import + reset HEAD, one of them should fail.

Closes #3754
2025-02-28 05:45:29 +00:00
Baltasar Dinis
e9e127c233 lib/tests: move remaining git test to gitoxide 2025-02-28 03:58:46 +00:00
dependabot[bot]
e22469d6c9 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.5 to 2.49.7
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](019e221005...ada1a57be8)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-27 18:39:56 +00:00
Ilya Grigoriev
f399c57466 docs workflow: try to fix it a third time
I think I likely found the issue. zizmor seems OK
with persisting credentials, see
https://github.com/jj-vcs/jj/actions/runs/13559693565/job/37900455060?pr=5820

Both of these workflows run only on commits in `main`,
so this doesn't seem like a huge security hole, but
we could consider other, better solutions in the
future.

Follow up to 78177ff. See #5819 for a failed attempt.
cc @thoughtpolice @neongreen @martinvonz
2025-02-27 06:32:04 +00:00
Ilya Grigoriev
78177ff69e docs workflow: try to fix it again
It's been broken since 514a009. Since 0e2d079,
the error was:

```
error: failed to push branch gh-pages to origin:
"fatal: could not read Username for 'https://github.com': No such device or address"
```

This is hard to test outside of prod, but follows a guess after
reading some Github docs and `gh auth login` docs from
<https://cli.github.com/manual/gh_auth_login>. I could not
find docs specifically about using the `git` CLI, but I'm guessing
that if `gh auth login` makes `git push` work on user machines by
setting `GH_TOKEN`, doing the same might help.

🤞

Cc @thoughtpolice, @martinvonz
2025-02-27 05:07:12 +00:00
dependabot[bot]
e30a617428 cargo: bump chrono from 0.4.39 to 0.4.40 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [chrono](https://github.com/chronotope/chrono).


Updates `chrono` from 0.4.39 to 0.4.40
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.39...v0.4.40)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 16:11:17 +00:00
dependabot[bot]
83e943de7b github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.4 to 2.49.5
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](6c595e9f7b...019e221005)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 16:10:45 +00:00
Yuya Nishihara
ae8e6e8e8e view: port remote_bookmark accessors to RemoteRefSymbol 2025-02-26 03:17:45 +00:00
Yuya Nishihara
4b06274785 view: make remote bookmark iterators yield RemoteRefSymbol instead of tuple
This also changes formatting of remote symbol to use revset compatible form.
2025-02-26 03:17:45 +00:00
Yuya Nishihara
6c71f3cbf2 git: use RemoteRefSymbolBuf to represent RefName::RemoteBranch variant
This also changes the formatting to use revset compatible form.
2025-02-26 03:17:45 +00:00
Yuya Nishihara
c63a7fa4d2 cli: bookmark: replace RemoteBookmarkName with RemoteRefSymbol
Now remote bookmark names are printed in revset compatible form.
2025-02-26 03:17:45 +00:00
Yuya Nishihara
cdd62a68f1 cli: bookmark: borrow remote bookmark names from cloned repo
This will help port find_remote_bookmarks() to RemoteRefSymbol<'_>.
2025-02-26 03:17:45 +00:00
Yuya Nishihara
4729a01082 cli: git: use RemoteRefSymbol type to specify trunk() alias 2025-02-26 03:17:45 +00:00
Yuya Nishihara
821f6b0f8b revset: externalize RemoteSymbol variant by using RemoteRefSymbol types
A struct with named fields should be more readable than unnamed arguments pair.
2025-02-26 03:17:45 +00:00
Yuya Nishihara
201603f48f refs: add RemoteRefSymbol { name, remote } types
I'm going to fix some CLI outputs to print remote symbols in revset syntax, and
it's easier if the view API took/returned type that represents a ref name.

.as_ref() and .to_owned() are implemented as non-trait methods because
RemoteRefSymbol can't be a true reference type. I think it's similar to
Option::as_ref(). I couldn't find any std examples of non-trait .to_owned(),
though.
2025-02-26 03:17:45 +00:00
dependabot[bot]
f642ca8dda github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.49.1 to 2.49.4
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ae97ff9daf...6c595e9f7b)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 01:44:51 +00:00
Martin von Zweigbergk
0e2d079ef8 github: fix invalid permission settings for docs workflow
Similar to #5792. We haven't had any updates to the GitHub pages since
#5076, AFAICT.
2025-02-25 16:41:26 +00:00
Yuya Nishihara
2fd842ebf7 tests: normalize formatting in insta macro that could leave trailing whitespace
rustfmt occasionally complains about that.
2025-02-25 15:40:14 +00:00
Yuya Nishihara
7fa8420908 tests: resolve directory path to run_jj_in() relative to env_root
Suppose we'll add test_env.init_workspace(path) or something, this will probably
make sense.
2025-02-25 15:40:14 +00:00
Yuya Nishihara
3634ff298c tests: simplify jj_cmd() interface, rename to new_jj_cmd() 2025-02-25 15:40:14 +00:00
Yuya Nishihara
28803eef94 tests: rewrite remaining callers of jj_cmd() to use run_jj_in/with() 2025-02-25 15:40:14 +00:00
pylbrecht
f7ceac3bf4 unsign: implement jj unsign command
The output of `jj unsign` is based on that of `jj abandon`.

We output warnings when unsigning commits, which are not authored by the
user. This is encouraging to use `jj undo`, in case one unintentionally
drops signatures of others.

---

Co-authored-by: julienvincent <m@julienvincent.io>
Co-authored-by: necauqua <him@necauq.ua>
2025-02-25 13:36:44 +00:00
Anton Bulakh
76f79961fb sign: implement the jj sign command
We always sign commits. This means commits, which are already signed,
will be resigned. While this is cumbersome for people using hardware
devices for signatures, we cannot reliably check if a commit is already
signed at the moment (see https://github.com/jj-vcs/jj/issues/5786).

We output warnings when signing commits, which are not authored by the
user. This is encouraging to use `jj undo`, in case one unintentionally
signs commits of others.

The output of `jj sign` is based on that of `jj abandon`.

---

Co-authored-by: julienvincent <m@julienvincent.io>
Co-authored-by: necauqua <him@necauq.ua>
2025-02-25 13:36:44 +00:00
dependabot[bot]
cdc9c69a8f github: bump the github-dependencies group with 5 updates
Bumps the github-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.0` | `4.6.1` |
| [taiki-e/install-action](https://github.com/taiki-e/install-action) | `2.48.20` | `2.49.1` |
| [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) | `2.0.5` | `2.0.6` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.28.9` | `3.28.10` |
| [ossf/scorecard-action](https://github.com/ossf/scorecard-action) | `2.4.0` | `2.4.1` |


Updates `actions/upload-artifact` from 4.6.0 to 4.6.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65c4c4a1dd...4cec3d8aa0)

Updates `taiki-e/install-action` from 2.48.20 to 2.49.1
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](2dbeb927f5...ae97ff9daf)

Updates `EmbarkStudios/cargo-deny-action` from 2.0.5 to 2.0.6
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](13fd9ef18c...0484eedcba)

Updates `github/codeql-action` from 3.28.9 to 3.28.10
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9e8d0789d4...b56ba49b26)

Updates `ossf/scorecard-action` from 2.4.0 to 2.4.1
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](62b2cac7ed...f49aabe0b5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-25 01:26:19 +00:00
Baltasar Dinis
653cc6c1c9 lib: move git_backend tests to gitoxide
This forces some code duplication to avoid depending on the testutils.
2025-02-24 21:02:29 +00:00
dependabot[bot]
712e659e49 cargo: bump the cargo-dependencies group with 4 updates
Bumps the cargo-dependencies group with 4 updates: [clap](https://github.com/clap-rs/clap), [clap_complete](https://github.com/clap-rs/clap), [either](https://github.com/rayon-rs/either) and [libc](https://github.com/rust-lang/libc).


Updates `clap` from 4.5.30 to 4.5.31
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.30...v4.5.31)

Updates `clap_complete` from 4.5.45 to 4.5.46
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.45...clap_complete-v4.5.46)

Updates `either` from 1.13.0 to 1.14.0
- [Commits](https://github.com/rayon-rs/either/compare/1.13.0...1.14.0)

Updates `libc` from 0.2.169 to 0.2.170
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.170/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.169...0.2.170)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: either
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 18:45:08 +00:00
Martin von Zweigbergk
a3664f77ed github: fix invalid permission settings for scorecards workflow
In https://github.com/jj-vcs/jj/actions/runs/13490171888, we got this error:

```
Invalid workflow file: .github/workflows/scorecards.yml#L48
The workflow is not valid. .github/workflows/scorecards.yml (Line: 48, Col: 9): Unexpected value 'permissions'
```

It looks like permissions can be set per job but not per step, so I
moved the permission setting to the job level.
2025-02-24 16:33:04 +00:00
Yuya Nishihara
ad6985fd9c tests: migrate unusual patterns of jj_cmd_ok() to run_jj_in()
These callers are manually ported to run_jj_in().
2025-02-24 15:39:11 +00:00
Yuya Nishihara
804d175fd9 tests: migrate non-snapshot users of jj_cmd_ok() to run_jj_in().success()
These callers were mostly substituted mechanically.
2025-02-24 15:39:11 +00:00
Yuya Nishihara
3193513049 tests: migrate snapshot users of jj_cmd_ok() to run_jj_in()
These callers were mostly substituted mechanically, then fixed up formatting.
2025-02-24 15:39:11 +00:00
Yuya Nishihara
255532b6b8 tests: reorder comments and snapshots, split insta::allow_duplicates! { .. }
This helps replace jj_cmd_ok() by pattern matching.
2025-02-24 15:39:11 +00:00
Yuya Nishihara
37bfc64cf2 working_copy: do not update watchman clock if untracked files exist
This means that watchman is partially disabled until the user cleans up
untracked files. This isn't nice, but should be better than hiding untracked
files forever.

Fixes #5728
Fixes #5602
2025-02-24 08:22:21 +00:00
Yuya Nishihara
b7e2932dba tests: compare CommandOutput where makes sense 2025-02-24 00:57:24 +00:00
Yuya Nishihara
c9926eae4c tests: implement Debug and Eq for CommandOutput
This helps assert that two command invocations generate exactly the same
results.
2025-02-24 00:57:24 +00:00
Yuya Nishihara
a1270f0149 tests: remove stale comment about CommandOutput wrapper 2025-02-24 00:57:24 +00:00
Yuya Nishihara
c83abc9dec tests: migrate non-snapshot users of jj_cmd_success() to run_jj_in().success() 2025-02-23 13:34:17 +00:00
Yuya Nishihara
cdcc777730 tests: migrate snapshot users of jj_cmd_success() to run_jj_in()
These callers were mostly substituted mechanically, then fixed up minor
formatting and name errors.
2025-02-23 13:34:17 +00:00
Yuya Nishihara
317993894b tests: migrate unusual users of jj_cmd_success() to run_jj_in/with()
These callers are manually ported to run_jj_in(). assert_eq!() is changed to
insta::assert_snapshot!() where possible.
2025-02-23 13:34:17 +00:00
Yuya Nishihara
a326b6e792 tests: migrate jj_cmd_success() output wrappers to run_jj_in() 2025-02-23 13:34:17 +00:00
Yuya Nishihara
2543329c78 tests: remove unused jj_cmd_panic() 2025-02-23 03:06:59 +00:00
Yuya Nishihara
79a0867050 tests: migrate callers of jj_cmd_internal_error() to run_jj_in() 2025-02-23 03:06:59 +00:00
Yuya Nishihara
b79b29c288 tests: migrate callers of jj_cmd_cli_error() to run_jj_in() 2025-02-23 03:06:59 +00:00
Yuya Nishihara
14ad894e47 tests: rewrite some alias tests to use normalize_stderr_with() 2025-02-23 03:06:59 +00:00
Yuya Nishihara
d19e58f591 tests: migrate callers of jj_cmd_failure() to run_jj_in() 2025-02-23 03:06:59 +00:00
Yuya Nishihara
696cfee55f tests: stop using assert_eq!() in cli tests where possible
Normalize the output instead.
2025-02-23 03:06:59 +00:00
Yuya Nishihara
7c2f723935 tests: unify parsing output helpers in revset and templater tests
run_jj_in() can handle both success and failure cases.
2025-02-23 03:06:59 +00:00
Bryce Berger
708e1c58cd cli: improve hint message when suggesting --ignore-immutable
There have been a number of users confused about why
their commits are immutable, or what to do about it, ex.
[https://github.com/jj-vcs/jj/discussions/5659].

Separately, I feel that the cli is too quick to suggest
`--ignore-immutable`, without context of the consequences. A new user
could see that the command is failing, see a helpful hint to make it not
fail, apply it and move on. This has wildly different consequences, from
`jj squash --into someone_elses_branch@origin` rewriting a single commit,
to `jj edit 'root()+'` rewriting your entire history.

This commit changes the immutable hint by doing the following:

* Adds a short description of what immutable commits are used for, and a
  link to the relevant docs, to the hint message.
* Shows the number of immutable commits that would be rewritten if
  the operation had succeeded.
* Removes the suggestion to use `--ignore-immutable`.
2025-02-23 02:24:43 +00:00
Bryce Berger
a17ed203ab fix: invoke tools from the workspace root
Previously, tools invoked by `jj fix` did not have their
`.current_dir()` set, and would just run from whatever directory the
user was in.

Now, the tools will always be invoked from the same directory that
`jj root` gives.

As a motivating example, consider a configuration path that's always at
the workspace root:

```toml
[fix.tools.something]
# previous:
command = ["cmd", "--config", "$root/tool.toml"]
#                              ^^^^^^ not possible
# now:
command = ["cmd", "--config", "./tool.toml"]
#                              ^^ now, just use a relative path
```
2025-02-23 02:21:57 +00:00
Yuya Nishihara
5fbd4f26f6 tests: accept duplicated snapshots in test_resolve_symbol_change_id()
I didn't notice this function had #[test_case(..)].
2025-02-23 02:13:57 +00:00
Bryce Berger
35936463ed cli: warn when command differs after --config, including when subcommand not found
Aliases and `ui.default-command` are loaded before `--config` and
`--config-file` arguments are parsed (#5282).

There is supposed to be a warning when the result of these arguments
would change the parsed args: #5286.

However, that warning was not being printed if the arguments failed to
parse due to an unrecognized subcommand. Example:

```bash
# correct:
$ jj --config ui.default-command=nonsense
Warning: Command aliases cannot be loaded from -R/--repository path or --config/--config-file arguments.

# previous, confusing:
$ jj --config aliases.some-alias=nonsense some-alias
error: unrecognized subcommand 'some-alias'

# now:
$ jj --config aliases.some-alias=nonsense some-alias
Warning: Command aliases cannot be loaded from -R/--repository path or --config/--config-file arguments.
error: unrecognized subcommand 'some-alias'
```
2025-02-22 20:19:42 +00:00
dependabot[bot]
a031defcff github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.48.19 to 2.48.20
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](55451daf94...2dbeb927f5)

Updates `astral-sh/setup-uv` from 5.2.2 to 5.3.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](4db96194c3...1edb52594c)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-22 01:48:48 +00:00
Yuya Nishihara
679a5c567a tests: migrate some jj_cmd_ok() output wrappers to run_jj_in()
Some of the wrapper functions are deduplicated as we can now handle success
and failure cases in the same way.
2025-02-22 01:23:04 +00:00
Yuya Nishihara
f1cd3a4e53 tests: migrate some jj_cmd_success() output wrappers to run_jj_in() 2025-02-22 01:23:04 +00:00
Yuya Nishihara
18ea718113 tests: migrate some helper functions that uses jj_cmd_*() to run_jj_in()
Snapshotted operation IDs are changed because this patch reorders "jj new"
command arguments.
2025-02-22 01:23:04 +00:00
George Christou
e1abaca21c templater: add trim methods to String type 2025-02-21 21:23:11 +00:00
Baltasar Dinis
837250277e lib/tests: port lib tests to gitoxide
As per #5548, this moves the majority of the tests in lib/test over to
gitoxide
2025-02-21 18:41:30 +00:00
Antoine Martin
631b9ada3d track: only print snapshot stats and hints once
Move the `track` specific logic to track.rs, let print_snapshot_stats
handle just the default case.

Previously print_snapshot_stats would get called twice and could warn
twice about a file (if that file was both in the auto-track fileset as
well as in the fileset passed to `jj file track`), so now we merge the
snapshot stats and display appropriate hints for each file.
2025-02-21 17:35:04 +00:00
Antoine Martin
0d02e855cd cli_utils: extract warning printing code from print_snapshot_stats
This will be re-used in the `track` command.
2025-02-21 17:35:04 +00:00
Antoine Martin
984c7aae20 cli_utils: make snapshot stats printing optional
Snapshot stats were automatically printed when calling
`WorkspaceCommandHelper::snapshot_working_copy`. This has been moved up
the call chain to allow more choice in when to actually print stats.

These functions used to trigger printing of the snapshot stats via
(direct or indirect) calls to `snapshot_working_copy`:

- CommandHelper::workspace_helper_with_stats
- CommandHelper::recover_stale_working_copy
- WorkspaceCommandHelper::maybe_snapshot_impl

This means we can now chose not to print the snapshot stats if we know
we will perform another snapshot operation during the same command, to
reduce the number of warnings and hints shown to the user.

Calling `workspace_helper`, or `maybe_snapshot` instead of
`workspace_helper_with_stats` and `maybe_snapshot_impl` still prints
stats automatically.
2025-02-21 17:35:04 +00:00
Yuya Nishihara
1d3558ff96 tests: rewrite change ID resolution test to not rely on git::import_refs()
It should be simpler to assign change IDs explicitly. Maybe we didn't have
.set_change_id() before?
2025-02-21 06:30:38 +00:00
George Christou
f77e64391d cli: add support for Sublime Merge 2025-02-21 05:44:53 +00:00
George Christou
19cf1368f1 cli: restore: Fix --to placeholder 2025-02-21 05:40:43 +00:00
dependabot[bot]
ee71b89f35 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action).


Updates `taiki-e/install-action` from 2.48.14 to 2.48.19
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](13b3c51a32...55451daf94)

Updates `EmbarkStudios/cargo-deny-action` from 2.0.4 to 2.0.5
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](e2f4ede4a4...13fd9ef18c)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 02:36:29 +00:00
dependabot[bot]
edbdd82ae7 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [anyhow](https://github.com/dtolnay/anyhow), [serde](https://github.com/serde-rs/serde) and [serde_json](https://github.com/serde-rs/json).


Updates `anyhow` from 1.0.95 to 1.0.96
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.95...1.0.96)

Updates `serde` from 1.0.217 to 1.0.218
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.217...v1.0.218)

Updates `serde_json` from 1.0.138 to 1.0.139
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.138...v1.0.139)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 02:09:41 +00:00
Yuya Nishihara
e23290d95d tests: migrate users of strip_last_line() to run_jj_in() function 2025-02-21 01:49:41 +00:00
Yuya Nishihara
2581cd82be tests: migrate users of normalize_with() to run_jj_in() function 2025-02-21 01:49:41 +00:00
Yuya Nishihara
cf7f1dfc88 tests: migrate users of normalize_exit_status() to run_jj_in() function 2025-02-21 01:49:41 +00:00
Yuya Nishihara
53664ce2a4 tests: migrate jj_cmd_stdin*() to run_jj*() functions 2025-02-21 01:49:41 +00:00
Yuya Nishihara
d9b762b8ec lib: internalize testutils::new_temp_dir() for unit tests
This fixes real dependency cycle that would expose slightly different versions
of jj_lib types to unit tests, one from super::* and another from testutils::*.

The testutils dependency can be removed by splitting lib/tests to a separate
crate.
2025-02-20 13:01:30 +00:00
Yuya Nishihara
6308897a23 cli: mention revsets doc on syntax error
Maybe we can show more specific hints on certain cases, but this should be good
as a fallback. The message is copied from the FilesetParseError hint.
2025-02-20 08:05:20 +00:00
Yuya Nishihara
b1eca3710c cli: parse bookmark name to be created/updated as revset symbol
This should help prevent misuse of "jj bookmark set" with e.g. remote bookmark
name. A remote-looking bookmark can be created by quoting the name if needed.

This patch doesn't change the parsing of name patterns. For patterns, I think
it's better to add support for compound expressions (e.g. `glob:foo* & ~bar`)
rather than adding revset::parse_string_pattern(text) -> StringPattern.

Closes #5705
2025-02-20 08:05:20 +00:00
Yuya Nishihara
59ca16705e revset: add parsing function that rejects expression other than symbol name 2025-02-20 08:05:20 +00:00
Baltasar Dinis
683ee9287e tests: move gitoxide test helpers to testutils
These helpers are going to be needed to port the git2 code in the lib
tests to gitoxide. Since the cli tests already depend on testutils, this
helps with avoiding duplicating the code
2025-02-20 06:13:20 +00:00
Baltasar Dinis
c8ee6b7088 cli/tests: move bookmark_command test to gitoxide
An in-flight PR snuck in another git2 usage, moved to gitoxide
2025-02-20 05:33:22 +00:00
Yuya Nishihara
de462a6268 tests: add helper to snapshot command output and exit status all at once
It's easier to review insta snapshot diffs than scanning panic log containing
lengthy output. I think this will also help printf debugging.

test_global_opts.rs is migrated to new API as an example.
2025-02-20 05:22:32 +00:00
Yuya Nishihara
740d8a2b1b tests: add #[must_use] to helper functions that return value to be tested 2025-02-20 05:22:32 +00:00
George Christou
a5e079f543 ui: add color to help text 2025-02-20 01:22:02 +00:00
Stephan Hügel
fececd9174 cli: add mergiraf to the default merge tools config
Added as per Martin's suggestion: https://discord.com/channels/968932220549103686/969291218347524238/1341910959601614893
2025-02-20 01:14:39 +00:00
Evan Mesterhazy
f364cd0c55 changelog: Move jj split working copy change to "breaking changes"
This was originally a deprecation until we made it the default behavior in
https://github.com/jj-vcs/jj/pull/5697. Since the default behavior has changed,
we should list this a breaking change.
2025-02-19 18:04:47 +00:00
Baltasar Dinis
6d26580a97 git: remove --bare flag from git commands
This causes some issues (e.g., #5700) and is not actively needed.
2025-02-19 15:48:08 +00:00
Baltasar Dinis
f233de5f69 cli: remove error hint for invalid remote names
Existing hint suggests using `jj git remote rename`, but that's not
applicable to `jj git clone`.

This commit removes that hint.
2025-02-19 07:14:00 +00:00
Yuya Nishihara
48b02226f5 cli: add "Hint: " prefix to conflict hints 2025-02-19 06:52:09 +00:00
Yuya Nishihara
a2a9a9935c cli: render "conflicts" message as warning
I don't have strong feeling, but this might be better because conflicts are
unusual?
2025-02-19 06:52:09 +00:00
Yuya Nishihara
1fa4e5a340 cli: status: do not label message lines as "conflict"
The "conflict" label is supposed to highlight "(conflict)" markers in commit
template. This patch just removes the label as we don't colorize the same
message in update_working_copy().
2025-02-19 06:52:09 +00:00
Yuya Nishihara
617e17fbe6 cli: use commit.has_conflict() extensively
Maybe it's slightly cheaper if there are no conflicts.
2025-02-19 06:52:09 +00:00
Yuya Nishihara
067e2ebaaa cli: status: reorder conflict message to match checkout stats
It seems also nice that the list of conflict files is displayed closer to the
resolution hints.
2025-02-19 06:52:09 +00:00
Baltasar Dinis
31afda99d4 git: pass --no-write-fetch-head to git
This makes the fetch invisible to other people.
2025-02-19 05:19:07 +00:00
dependabot[bot]
00bc69b10e cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [clap](https://github.com/clap-rs/clap) and [clap_complete](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.29 to 4.5.30
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.29...clap_complete-v4.5.30)

Updates `clap_complete` from 4.5.44 to 4.5.45
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.44...clap_complete-v4.5.45)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 03:04:41 +00:00
dependabot[bot]
d6af0a0dfe github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.48.13 to 2.48.14
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ad0904967b...13b3c51a32)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 02:48:59 +00:00
Yuya Nishihara
630036eeb0 tests: add [EOF] marker to command output when displaying
It's important to test that command output is (usually) terminated with newline,
but insta::assert_snapshot!() is lax about that.
2025-02-19 02:31:59 +00:00
Yuya Nishihara
a54165230a tests: add CommandOutputString wrapper
I'm going to add "[EOF]" marker to test that command output is terminated by
newline char. This patch ensures that callers who expect a raw output string
would never be affected by any normalization passes.

Some common normalization functions are extracted as CommandOutputString
methods.
2025-02-19 02:31:59 +00:00
Yuya Nishihara
0eddf5f3d6 tests: extract helper function that substitutes $TEST_ENV
I'm going to add wrapper type for normalized output, and this change makes
porting a bit easier.
2025-02-19 02:31:59 +00:00
Yuya Nishihara
dd59cef1a2 cli: show executable name (with suffix) in help requested by "jj help"
On Windows, "jj help" and "jj --help" outputs were slightly different because
the binary name may have ".exe" suffix.
2025-02-19 02:31:59 +00:00
Ilya Grigoriev
55f130e085 streampager: stop uselessly reading the config file (fix a TODO)
https://github.com/facebook/sapling/pull/1011 is in, yay! 🎉

Follows up on https://github.com/jj-vcs/jj/pull/5415
and https://github.com/jj-vcs/jj/pull/5711
2025-02-18 19:01:30 +00:00
dependabot[bot]
eefbcfb87f cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [dirs](https://github.com/soc/dirs-rs), [smallvec](https://github.com/servo/rust-smallvec) and [tempfile](https://github.com/Stebalien/tempfile).


Updates `dirs` from 5.0.1 to 6.0.0
- [Commits](https://github.com/soc/dirs-rs/commits)

Updates `smallvec` from 1.13.2 to 1.14.0
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.13.2...v1.14.0)

Updates `tempfile` from 3.16.0 to 3.17.1
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.16.0...v3.17.1)

---
updated-dependencies:
- dependency-name: dirs
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: cargo-dependencies
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-18 11:24:22 +00:00
Baltasar Dinis
64ea8bee47 git: reject remotes with forward slashes
Although this behaviour is accepted by git, it's a degenerate case.
Especially because we implicitely rely on being able to parse out the
remote from the refname (i.e., `refs/remotes/<remote>/<branch>`).

Branches can have forward slashes, but if remotes can also have them,
parsing the refname becomes ambiguous, and a pain. Especially because it
would be totally legal to have a branch "c" on remote "a/b" and a branch
"b" on remote "a".

Fixes #5731
2025-02-18 07:10:01 +00:00
Baltasar Dinis
d6e7047142 cli: add hint to rename on remote management error 2025-02-18 07:10:01 +00:00
dependabot[bot]
55bb43c950 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.48.11 to 2.48.13
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](297054b274...ad0904967b)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-18 00:36:31 +00:00
Andrew Gilbert
5dac0ef4d1 docs templates: add link to templates toml file 2025-02-17 21:02:23 +00:00
Antoine Martin
149702ef7d working_copy: adjust max-size hint to fit all configurations
In the case where the user has set `snapshot.auto-track` to something
other than `all()`, running `jj st` with a higher file size set just for
that command will not actually fix the user's problem, `jj file track`
needs to be called instead.
2025-02-17 20:52:18 +00:00
Martin von Zweigbergk
a6ca04e6cb cli_util: call Workspace::check_out() also for no-op updates
Some working-copy implementations, like the Google CitC one, may want
to update the VFS's state even if the tree didn't change. In
particular, when updating between two commits with the same tree but
different mainline base, we want to update the base commit. Otherwise
a future snapshot, which we do relative to the mainline commit, may
notice an incorrect set of changed file.
2025-02-17 18:59:56 +00:00
Martin von Zweigbergk
52c1a55bbf local_working_copy: short-circuit update when tree is unchanged
When updating to the same tree, we can skip the actual update and skip
writing the tree-state file.
2025-02-17 18:59:56 +00:00
Martin von Zweigbergk
a89bcf23ea cli_util: make update_working_copy() return all-zero stats instead of None
When updating the working copy results in no changes, we currently
return `None` from `cli_util::update_working_copy()`. However, a no-op
update is still an update, so I think it makes more sense to always
return a `CheckoutStats`. We already skip printing stats if they're
zero.
2025-02-17 18:59:56 +00:00
Scott Taylor
00f87a2846 cli: untrack remote bookmarks on bookmark forget
Forgetting remote bookmarks can lead to surprising behavior since it
causes the repo state to become out-of-sync with the remote until the
next `jj git fetch`. Untracking the bookmarks should be a simpler and
more intuitive default behavior. The old behavior is still available
with the `--include-remotes` flag.

I also changed the displayed number of forgotten branches. Previously
when forgetting "bookmark", "bookmark@remote", and "bookmark@git" it
would display `Forgot 1 bookmarks`, but I think this would be confusing
with the new flag since the user might think that `--include-remotes`
didn't work. Now it shows separate `Forgot N local bookmarks` and
`Forgot M remote bookmarks` messages when applicable.
2025-02-17 17:02:58 +00:00
Yuya Nishihara
4ed0e1330a op_store: propagate initialization error 2025-02-17 12:58:57 +00:00
Yuya Nishihara
caaa2ab201 op_heads_store: propagate initialization error 2025-02-17 12:58:57 +00:00
Yuya Nishihara
33d61180a5 repo: plumbing to propagate error from backend initializer/loader functions 2025-02-17 12:58:57 +00:00
Yuya Nishihara
3cdaa5b1d8 cli: bookmark: remove hint about upsert behavior
It was added at 3c80e3453db6 "cli: branch: make "set" do upsert as before" in
order to remind behavior change.
2025-02-17 12:44:03 +00:00
Alain Leufroy
fa3254b4a7 templates: add hook points for users to customize the default operation id
Same as `format_short_change_id`.

We can't use `format_short_id` because `operation.id()` does not have `shortest()` method.
2025-02-17 09:08:51 +00:00
Martin von Zweigbergk
3bfbec1c83 undo: fix check for double-undo
The condition we were checking (which I suggested during review) is
very broken. It checks if the new view is equal to view before the
current operation, but that's supposed to always be true (except for
operation that update remote bookmarks). The reason the warning didn't
trigger all the time was that we did the comparison after calling
`merge_view()` but before calling `rebase_descendants()` (via
`tx.finish()`). The former only records commits as abandoned without
actually hiding them. However, because of [the hack][hack] in
`merge_view()` to make it work reasonably well in huge repos, every
undo operation at Google would print the warning.

This patch fixes the bug by checking if the to-be-undone operation is
an undo by comparing its view to its grandparent's view.

 [hack]: ec6f8278fd/lib/src/repo.rs (L1693-L1708)
2025-02-16 16:52:51 +00:00
Martin von Zweigbergk
3470a48345 undo: demonstrate how double-undo warning isn't shown correctly
Will be fixed in the next patch.
2025-02-16 16:52:51 +00:00
maan2003
ec6f8278fd signing: allow specifying sign behavior 2025-02-16 05:56:52 +00:00
Evan Mesterhazy
a4b064ea8d cli split: Move bookmarks to the first commit instead of the second
After even more discussion on Discord, we decided to use the new bookmark
behavior immediately and only print a warning during `jj split` if the user has
opted out of the new behavior using the `split.legacy-bookmark-behavior` config
setting.

The reasoning is that if the behavior change breaks someone's workflow or is
very disruptive, they are likely to check the changelog and learn about the
config option. For users that are not adversely impacted, printing a warning
that can only be silenced by changing their config is also disruptive.

#3419
2025-02-16 04:02:24 +00:00
Yuya Nishihara
5def466ac1 index, op_store: write temporary files in destination directory
This should be safer, and should be okay since we ignore invalid file names when
scanning the store directories.

Fixes #5712
2025-02-16 01:48:10 +00:00
Yuya Nishihara
be77d03bca op_store: extract functions that return views/operations dir paths 2025-02-16 01:48:10 +00:00
Yuya Nishihara
89fb97be88 op_store: refactor SimpleOpStore::init() to use self.path for initialization
I'm going to add helper functions that return these directory paths.
2025-02-16 01:48:10 +00:00
Yuya Nishihara
4ec22dde38 templater: port annotation line content to template
I originally thought we would have to add BString template type first, but we
can use opaque Template type instead.
2025-02-15 11:07:17 +00:00
Yuya Nishihara
6f51696600 templater: use property.map() to apply infallible transformation
Also renamed data to line as the type name was changed to AnnotationLine.
2025-02-15 11:07:17 +00:00
dependabot[bot]
e220fe39ef github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.48.10 to 2.48.11
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](ae9d9ea210...297054b274)

Updates `astral-sh/setup-uv` from 4.2.0 to 5.2.2
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v4.2...4db96194c378173c656ce18a155ffc14a9fc4355)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-15 08:59:56 +00:00
Bryce Berger
3bc111e60e annotate: make AnnotationLine template type
Allows:
* self.commit()
* self.line_number()
* self.first_line_in_hunk()

Certain pagers (like `delta`), when used for `git blame`, only show the
commit information for the first line in a hunk. This would be a nice
addition to `jj file annotate`.

`jj file annotate` already uses a template to control the rendering of
commit information --- `templates.annotate_commit_summary`. Instead of
a custom CLI flag, the tools necessary to do this should be available in
the template language.

If `1 % 2` or `1.is_even()` was available in the template language, this
would also allow alternating colors (using `raw_escape_sequence`).

Example:

```toml
[templates]
# only show commit info for the first line of each hunk
annotate_commit_summary = '''
if(first_line_in_hunk,
  show_commit_info(commit),
  pad_end(20, " "),
)
'''
```
2025-02-15 02:34:38 +00:00
Martin von Zweigbergk
6ec337e715 cargo: upgrade to latest streampager
This removes some dependencies on very old versions of the `dirs`
crate and a few others. Thanks to @quark-zju for getting a new
`sapling-streampager` released.
2025-02-15 02:24:02 +00:00
Martin von Zweigbergk
77fb50c8cd completion: mark git remote arguments as URL instead of path
Fixes #5704.
2025-02-15 01:59:16 +00:00
Bryce Berger
e4ad7b6c0f fix: add simple debugging logs
There's currently no way to tell what `jj fix` is doing. This adds some
traces such that `jj fix --debug` will at least tell you what's being
run and what it's exit code is, similar to other subprocessing code.
2025-02-15 01:13:17 +00:00
Philip Metzger
4a47a4fc1d commit: Add commit.is_hidden(repo)
Also simplify the `hidden()` revset with it.
2025-02-14 16:24:01 +00:00
Martin von Zweigbergk
5e896b22fe repo: make commit visible when pointing bookmark to it
This was discussed in the Discord a while ago, and this is the logical and consistent
conclusion. Implementing it as such makes it consistent with both `jj edit` and `jj new`
which make hidden commits such as predecessors visible.

This actually was Martins work, I just added the tests.

Co-Authored-by: martinvonz <martinvonz@google.com>
2025-02-14 16:24:01 +00:00
Austin Seipp
514a009675 github: add zizmor check to CI matrix
This should help us catch GHA security issues earlier.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
a3a658783b github: remove template injection vectors in release workflow
In theory this could be exploited by a contributor who pushes a tag with a
specifically crafted name in order to exfiltrate a `GITHUB_TOKEN` that has
permissions on the jj repository. This token would be scoped narrowly ideally,
but...

Today, Martin does releases manually, so this workflow currently does not e.g.
have the ability to upload a compromised package to crates.io. However in the
future we'd like to have the release done automatically via workflow as well,
which would make this potential injection vector catastrophic if the injection
was possible in a step with a crates.io API token available.

Found by `zizmor`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
7c1c1a7d4c github: narrow perms in release workflow
Found by `zizmor`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
c4b350b85d github: narrow perms in ci workflow
Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
3ca7f9b18b github: narrow perms in binaries workflow
Found by `zizmor`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
0ee37948de github: narrow perms in the docs workflow
Found by `zizmor`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
04a25d1ef4 github: narrow perms in dependabot workflow
The dependabot workflow already specifies the exact permissions it needs within
the workflow steps, so there's no need to enable any default permissions.

Found by `zizmor`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Austin Seipp
23f9d00dc6 github: narrow perms in scorecards workflow
This stops issuing overly broad permissions to the entire workflow and instead
scopes them to the necessary steps in the job.

Found by `zizmor`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-14 09:38:49 +00:00
Benjamin Tan
e021036740 cli: backout: add templates.backout_description configuration
Closes #5676.
2025-02-14 07:56:40 +00:00
Josh Steadmon
23691a636c config-schema: validate default configs in tests with taplo
Set up tests using `taplo check --schema` to validate the default configs.
Install taplo in GitHub CI and in the Nix flake.

Fixes #5670.
2025-02-14 04:17:37 +00:00
Evan Mesterhazy
37992412b6 cli split: Recommend jj config set --user instead of --repo in deprecation warning
It only occurred to me after I rebuilt jj and actually had to run the command
that we should probably recommend a change to the user config instead of the
repo config.

#3419
2025-02-14 01:41:41 +00:00
Yuya Nishihara
381bb8a869 tests: create parent directory of .git link file by caller
It seems a bit safer to set up the working directory by caller who knows the
test environment directory exists.
2025-02-14 01:14:39 +00:00
Yuya Nishihara
d5493e6b10 tests: inline create_gitlink() option to caller
Since we don't set up Git worktree properly, it's odd that init_git_repo() has
a option to create a workspace at the specified path.
2025-02-14 01:14:39 +00:00
Yuya Nishihara
0d2becbac3 templater: add stringify(x) function to call string methods on template
It's also useful to remove color labels from the inner template.
2025-02-14 00:54:03 +00:00
Yuya Nishihara
78f08d9e6c docs: use single-quoted template string in JSON example
It's easier to parse than \".
2025-02-14 00:54:03 +00:00
Martin von Zweigbergk
5e694ea571 docs: record contributors whose employer pays for contributions
This adds a record of all contributing employees of companies who pay
for contributions. The purpose is to help make possible conflicts of
interest easier to spot. As far as we know, only Google currently pays
right now.
2025-02-13 21:52:02 +00:00
Evan Mesterhazy
d5d21643d5 cli split: Add a config option controlling how bookmarks move during splits
Currently, `jj split` moves bookmarks from the target revision to the second
revision created by the split. Since the first revision inherits the change id
of the target revision, moving the bookmarks to the first revision is less
surprising (i.e. the bookmarks stay with the change id). This no-implicit-move
behavior also aligns with how `jj abandon` drops bookmarks instead of moving
them to the parent revision.

Two releases from now, `jj split` will no longer move bookmarks to the second
revision created by the split. Instead, local bookmarks associated with the
target revision will move to the first revision created by the split (which
inherits the target revision's change id). You can opt out of this change by
setting `split.legacy-bookmark-behavior = true`, but this will likely be
removed in a future release. You can also try the new behavior now by setting
`split.legacy-bookmark-behavior = false`.

Users who have not opted into the new behavior via the config setting will see
a warning when they run `jj split` informing them about the change. The default
behavior be changed in the future.

The `jj split` tests for bookmarks are updated to run in all three configurations:

- Config setting enabled
- Config setting disabled
- Config setting unset


#3419
2025-02-13 21:45:56 +00:00
dependabot[bot]
ae59bd88cd github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.48.9 to 2.48.10
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](995f97569c...ae9d9ea210)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 18:56:58 +00:00
Ilya Grigoriev
d79c7a0dd5 mkdocs: update mkdocs-material and mkdocs-include-markdown-plugin
It's been about five months, and it seems best to occasionally try
updating. Let's see if this causes any problems.

There was also a minor breaking change to
`mkdocs-include-markdown-plugin`, I added an option to keep
the old behavior (which seems nice in this case).

To do these kinds of updates, `uv tree --outdated` is useful.
2025-02-13 17:30:21 +00:00
Samuel Tardieu
148e4235fd git: box gix::Repository large instances
`gix::Repository` instances are larger than 1KB on 64-bit machines. This
makes Clippy warn about an unbalance between variants of `GitFetchImpl`:
the `Git2` variant requires 8 bytes, while the `Subprocess` variant
requires 1128 bytes.

Boxing the `gix::Repository` makes `GitFetchImpl` instances smaller, as
well as the other structs embedding them such as `GitFetch`.
2025-02-13 08:10:02 +00:00
Matthew Davidson
204cffe386 Remove broken Sublime LSP TOML reference in config.md
Removes nonexistent Sublime TOML LSP server link. Since I wasn't able to find an alternative, I removed the Sublime bit entirely.
2025-02-13 04:44:39 +00:00
Evan Mesterhazy
b90e5af51c json schema: Fix bad indentation for jj fix settings
The fix settings weren't indented consistent with the rest of the settings,
causing them to appear in the redacted snapshot in the test_util_config_schema
test.
2025-02-13 02:37:19 +00:00
dependabot[bot]
61cab24c2f cargo: bump clap from 4.5.28 to 4.5.29 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.28 to 4.5.29
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.28...clap_complete-v4.5.29)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-12 22:51:41 +00:00
hkalbasi
272955f4fe templater: add .escape_json method to string 2025-02-12 22:17:30 +00:00
Ilya Grigoriev
e83b328f71 ui.rs: style, fixup to 8aa2916 2025-02-12 21:25:37 +00:00
Josh Steadmon
69050f31f4 config-schema: expand options for ui.editor, ui.diff.tool, and ui.pager
In addition to a single string, the `ui.editor` and `ui.diff.tool` options also
allow arrays of strings. `ui.pager` allows arrays of strings as well as a nested
table describing both the command and the environment.

Update config-schema.json to allow all of these types of values.

Fixes #5617
2025-02-12 20:43:17 +00:00
Evan Mesterhazy
b2e2c79088 cli split: Move bookmark testing to a separate test
We are planning to add a config option that controls how bookmarks and change
ids move during `jj split` based on feedback in https://github.com/jj-vcs/jj/pull/5618.
I think the tests will be more readable after the config option is added if we
move the bookmark testing to its own test.

#3419
2025-02-12 19:45:37 +00:00
Baltasar Dinis
9ca6d179b2 cli/tests: port test_gitignore to gitoxide 2025-02-12 17:06:59 +00:00
Baltasar Dinis
6746942a06 cli/tests: port test_git_init to gitoxide
cargo-insta was re-run.
This is due to centralizing git interaction code to use a particular
different signature, which changes the commit hashes
2025-02-12 17:06:59 +00:00
Baltasar Dinis
18cd4d3010 cli/tests: add more gitoxide helpers
These are helpers for:
 - gitlinks
 - removing config values
 - symbolic references
 - status
 - index management
2025-02-12 17:06:59 +00:00
Yuya Nishihara
a1fe2dc95e git: trim trailing space in sideband messages received from git subprocess
Since git CLI appends "        " to sideband messages to overwrite the previous
line, we need to undo that. Our GitSidebandProgressMessageWriter expects
original sideband messages.
2025-02-12 01:25:53 +00:00
Ilya Grigoriev
a3647a6796 json schema: update some defaults to their modern values 2025-02-11 23:25:00 +00:00
Ilya Grigoriev
3f9e55ffee json schema: set schema version to draft-4, clarify taplo support
"$comment" is not officially supported by draft-4, but it is in newer
drafts, and JSON schema readers are supposed to skip fields that
are not known to them.
2025-02-11 23:25:00 +00:00
Ilya Grigoriev
dae1664ad3 json schema: fix color names failing validation
This makes the schema a bit less smart, but there's a good chance
`taplo` could not use "propertyNames" anyway because that comes
from a newer version of the schema than `taplo` officially supports
(see the child of this commit).

Fixes #5607
2025-02-11 23:25:00 +00:00
David Rieber
dd73b5ab7d bookmarks: First step to make target revision a required argument to bookmark create/move/set.
With this change a warning is shown if the user does not explicitly specify the target revision, but the behavior is unchanged (it still defaults to the working copy).
In the future the warning will be turned into an error. In other words, it will be required to specify target revision.

The bulk of the changes here are to prepare tests for the upcoming change, to make the transition easier.

For additional details please see:
* https://github.com/jj-vcs/jj/issues/5374
* https://github.com/jj-vcs/jj/discussions/5363
2025-02-11 22:21:19 +00:00
dependabot[bot]
b03350eaa1 cargo: bump toml_edit in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [toml_edit](https://github.com/toml-rs/toml).


Updates `toml_edit` from 0.22.23 to 0.22.24
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.23...v0.22.24)

---
updated-dependencies:
- dependency-name: toml_edit
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 20:16:46 +00:00
dependabot[bot]
658d26b290 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.48.1 to 2.48.9
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](510b3ecd79...995f97569c)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 20:16:06 +00:00
Martin von Zweigbergk
f291ac44db cli: add difftastic as diff tool
Difftastic is pretty popular tool. Let's add it as a diff tool so it's
easier to enable.
2025-02-11 18:00:01 +00:00
Martin von Zweigbergk
7b1820adba cli: sort tools in merge_tools.toml 2025-02-11 18:00:01 +00:00
Baltasar Dinis
39fe9aad3a cli/tests: remove allow(unused) from git helpers 2025-02-11 13:46:25 +00:00
Baltasar Dinis
5b174c9109 cli/tests: port test_git_import_export to gitoxide 2025-02-11 13:46:25 +00:00
Baltasar Dinis
4f6a2fcfc0 cli/tests: port test_commit_template to gitoxide 2025-02-11 13:46:25 +00:00
Baltasar Dinis
5076d0e6b1 cli/tests: port test_bookmark to gitoxide
cargo-insta was re-run.
This is due to centralizing git interaction code to use a particular
different signature, which changes the commit hashes
2025-02-11 13:46:25 +00:00
Baltasar Dinis
a8eb76f9c4 cli/tests: port test_operations to gitoxide
cargo-insta was re-run.
This is due to centralizing git interaction code to use a particular
different signature, which changes the commit hashes
2025-02-11 13:46:25 +00:00
Baltasar Dinis
d56d910db8 cli/tests: port test_undo to gitoxide 2025-02-11 13:46:25 +00:00
Baltasar Dinis
806e76613b cli/tests: port test_git_submodule to gitoxide 2025-02-11 13:46:25 +00:00
Baltasar Dinis
b80e73b7cf cli/tests: port test_tag_command to gitoxide 2025-02-11 13:46:25 +00:00
Baltasar Dinis
cd6141693b cli/tests: add gitoxide helpers 2025-02-11 13:46:25 +00:00
Baltasar Dinis
769dbd8f5f cli/tests: move test environment to its own file
The `mod.rs` was becoming crowded and this makes way for unrelated
additions to the test utilities module
2025-02-11 13:46:25 +00:00
Austin Seipp
61c3be6057 github: bump ci timeout to 20 minutes
The CI system is variously hitting the limits of the existing 15 minute
build mark; in particular, Windows seems to be consistently hitting ~14m
so the chance of a runner timing out gets high if the underlying load is
also high.

While I'm working on this, let's just (regrettably) increase the slack
on the timeout so that everyone else isn't disturbed.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
404778c8ca github: rename 'build' workflow to 'ci'
'ci' is much more high-level and accurately explains what the jobs in
this test should be for. It's much more applicable than 'test' (testing
what? on what platforms?) or 'build' (does it only build? or test? or
run linters?)

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
8fe23d80ad github: split windows-specific setups into private composite action
Moves all the Windows junk out of the way, where it can be encapsulated.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
768ec3b93d github: s/build-binaries/binaries/
Just a rename; now that `build` and `build-nix` are unified, this makes
it more clear this workflow is conceptually separate. NFC.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
19bbecbe5e github: inline nix builds into main build workflow
There's no real reason to keep these separate and it makes the build UX
easier to navigate, too; as well as see what the actual "global" slowest
builds across all workflows easier to see.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
48d91c33a0 github: reorganize and rename default build workflows
This makes the methods and the madness a little more consistent with
each other, so it's easier to read and categorize build reports in the
GHA UX.

No functional change, but requires updating rulesets upstream.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
eea80e4c7a github: build x86_64-macos binaries on macos-14 runners
Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Austin Seipp
50160b4853 github: make build workflow more similar to release flow
Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-11 05:50:39 +00:00
Emily
77f54a267e git: enable subprocessing by default
Given the previously‐stated intention of making this default
for the 0.27 release, prepare for that decision ahead of time by
enabling subprocessing by default on trunk. This will help surface
any regressions and workflow incompatibilities and therefore give
us more information to decide whether to keep or revert this commit,
without inconveniencing any users who haven’t already opted in to
the bleeding edge.

Please feel free to revert without hesitation if any major issues
arise; this is not intended as a strong commitment to enable this
option for the next stable release if it turns out to not be ready. In
that case, it’s better that we learn that early on in the cycle,
rather than having to revert at the last minute or, worse, cutting
a stable release that we later find contains a serious regression.
2025-02-10 22:10:22 +00:00
dependabot[bot]
c187142090 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.48.1 to 2.48.6
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](510b3ecd79...5bc300ae62)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 17:40:16 +00:00
Austin Seipp
02500e5248 meta: add initial GOVERNANCE.md
This is the result of a lot of back and forth, the weekly efforts of the
governance working group, consisting of:

- Martin von Zweigbergk (martinvonz)
- Waleed Khan (arxanas)
- Emily Shaffer (nasamuffin)
- Austin Seipp (thoughtpolice; yours truly)

Many thanks as well to emeritus member Khionu Sybiern, who helped kickstart this
whole process.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-10 17:39:34 +00:00
Martin von Zweigbergk
d135e04f7d docs: undo another mistaken "branch->bookmark" change 2025-02-10 17:24:32 +00:00
Martin von Zweigbergk
1e25101cea
cli: status: replace "is clean" by "has no changes"
"The working copy has no changes" seems much easier to understand for
new users. Thanks to @Wilfred for the suggestion.
2025-02-10 08:01:50 -08:00
Martin von Zweigbergk
65dc6750e0 docs: move Git command table to a separate page
The table can be easy to miss in its current location.
2025-02-10 15:29:32 +00:00
Martin von Zweigbergk
ac55f52292 docs: swap "git" and "jj" columns in command equivalence table
The table is probably mostly used by users switching from Git, so it
makes sense to have the `git` command on the left side.
2025-02-10 15:29:32 +00:00
Evan Mesterhazy
915fe8f11e cli split: Test how the working copy commit changes across multiple workspaces
When a commit is split, the second commit produced by the split becomes the
working copy commit for all workspaces whose working copy commit was the target
of the split.

This commit adds two tests for this behavior:

1. Split a commit which is the working copy commit for two workspaces.
2. Split a commit which is the working copy commit for only one of the two
   workspaces.

These tests check that the working copy commits for the workspaces are updated
correctly. Both parallel and non-parallel splits are tested.
2025-02-10 14:05:45 +00:00
Jacob Hayes
6aefb58ed8
sign: Support ~/ path expansion for allowed-signers
Fixes #5625
2025-02-10 16:50:39 +07:00
Evan Mesterhazy
aee05b9ea0 repo: Use thiserror #[from] with EditCommitError::RewriteRootCommit
See the discussion in #5623. This prevents future accidents if member variables
are added to RewriteRootCommit which need to be propagated to EditCommitError
during conversion.
2025-02-10 04:23:04 +00:00
Martin von Zweigbergk
ea10adfbd5 cli: delete unused variants of BuiltinToolError
Some variants became unused in bce8cb9.
2025-02-10 02:56:01 +00:00
Evan Mesterhazy
4422e8cea6 cli: Fix single-character string constant used as pattern clippy warning
For further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern

Apparently this got moved to "pedantic" in 1.80, but I ran clippy 1.79 and saw
it. Seems harmless to fix though.

#cleanup
2025-02-09 13:23:41 -05:00
Martin von Zweigbergk
0cc1a17e78 faq: fix quoting of git.private-commits example
The example became incorrect when `jj config set` stopped promoting
values with apostrophes to strings in 3f115cb. Use a TOML literal
string to fix it.
2025-02-09 17:51:14 +00:00
Benjamin Tan
07c63ed182 cli: remove ui.allow-filesets configuration option
The "fileset" language has been enabled by default since v0.20.
2025-02-08 19:34:05 +00:00
Martin von Zweigbergk
9cab4d9a0b cli: describe how to set a config value with apostrophes
Apostrophes are not uncommon in e.g. `user.name`, so let's help the
user by providing examples.
2025-02-08 18:46:37 +00:00
Evan Mesterhazy
6d6f2c6dec repo: Change name for unused closure argument
The previous name violated the "snake case" convention for variable names. I
suspect the intention was to use `RewriteRootCommit` as a type declaration, not
the argument name. Since the argument isn't used, we can use an underscore as
the name and leave the type declaration.

#cleanup
2025-02-08 15:25:44 +00:00
Ilya Grigoriev
cc5b34809c docs: label prerelease docs with different title and header color
This makes the pre-release header bar into a steely blue-grey color and
changes its label, while the header for the stable docs (and past
release docs) remains indigo (blue). The goal is to make it easier to
tell prerelease docs and regular docs apart with a glance. I also
considered using a favicon with a different background, but perhaps
that's not as useful as changing the header color.

See https://github.com/jj-vcs/jj/pull/5614 for screenshots and
https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#primary-color
for some more possibilities. "Deep purple" or "brown" are the closest
runners up to "blue grey" in my mind.

This can be tested with

```
MKDOCS_PRIMARY_COLOR="blue grey" MKDOCS_SITE_NAME="Jujutsu docs (prerelease)" uv run mkdocs serve
```

-----------

We could also want to make the historical versions more easily
distinguishable from the `latest` docs, but that would be more
difficult. We'd either need to rely on javascript or to rebuild the
*previous* version of the docs in addition to the new one on every
release.

See [mkdocs-material banner docs] for a javascript apporach (which would
still need some care in implementing, since we probably want the
prerelease version to either not have a banner or to have a different
banner).

[mkdocs-material banner docs]:
  https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning/?h=version#version-warning
2025-02-07 20:44:59 +00:00
Ilya Grigoriev
f543d24244 mkdocs: get rid of SITE_URL_FOR_MKDOCS
The bug that made it somewhat useful is hopefully fixed now. I
documented the issue in case it reappears.
2025-02-07 20:44:59 +00:00
Ilya Grigoriev
1d95bd6b55 docs and readme: add the new jj logo
It's half-sized (432x432 instead of 864x864) for size and
compressed with `pngquant` (difference not visible with
my eyes).
2025-02-07 20:40:23 +00:00
Ilya Grigoriev
2da0b73c22 favicon: make corners round, make birds a little larger
Also compressed with `pngquant` (I cannot notice the difference
before and after)

Followup to b3099b7
2025-02-07 20:40:12 +00:00
dependabot[bot]
b8598228a3 cargo: bump once_cell in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [once_cell](https://github.com/matklad/once_cell).


Updates `once_cell` from 1.20.2 to 1.20.3
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.2...v1.20.3)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-07 18:40:23 +00:00
dependabot[bot]
a17f28d10b
github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.28.8 to 3.28.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](dd746615b3...9e8d0789d4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-07 15:50:08 +00:00
avmikhailov
a16555f9af index: Return Result from heads
there are many ways to implement `heads` for your custom backend:
one involves calling `self.evaluate_revset` with a proper revset. 
However, it can return an error, but `heads` does not allow it.

In our implementation of the index backend we do exactly the above 
and we ended up with several unwraps which we are trying to avoid 
as it could potentially crash our prod jobs.

P.S. The same logic applies to many methods in this trait, but I am doing one at a time.
2025-02-07 13:45:47 +01:00
Jonathan Frere
42161257ca cli: op undo: show a warning when undoing an undo operation
One common issue that users run into with `jj undo` is that they expect
it to behave like an "undo stack", in which every consecutive `jj undo`
execution moves the current state further back in history. This is
(currently) an incorrect intuition - running multiple `jj undo` commands
back-to-back will only result in the previous non-undo change being
reverted, then un-reverted, then reverted, and so on.

This change adds a hint when the user runs `jj undo` multiple times
consecutively, suggesting that they may be looking for `jj op restore`,
which allows them to revert the whole state of the repository back to
a previous snapshot.
2025-02-07 08:27:16 +01:00
Ilya Grigoriev
6c2ad68330 SECURITY.md: insert an explicit URL
The previous "on this page" statement is wrong more often than not.
Unfortunately there is no "Report a vulnerability" button on
https://github.com/jj-vcs/jj/security/policy, and looking for such a
button from https://github.com/jj-vcs/jj?tab=security-ov-file leads to
confusion.

This is not the end of the world, but I don't see much security downside
to clarifying it (that is, I don't think *not* having a link protects
against phishing in any real way).
2025-02-07 05:09:28 +00:00
Ilya Grigoriev
4ba316d9f7 cli reference: fix jj git push link formatting
Currently, the rendering in
https://jj-vcs.github.io/jj/prerelease/cli-reference/#jj-git-push is
incorrect.

Fixup to 5b5a9e7
2025-02-06 17:35:20 -08:00
Baltasar Dinis
0c446c9069 git: skip running pre-push hooks when subprocessing to match git2 behaviour 2025-02-06 22:43:06 +00:00
Josh Steadmon
975e1e1e24 templater: add optional ellipsis arg to truncate template functions
If an ellipsis arg is given to the truncate_* template functions, append (or
prepend) the ellipsis when the template content is truncated to fit the maximum
width.

Fixes #5085.
2025-02-06 11:34:33 -08:00
Austin Seipp
8144ffcaf8 github: migrate to GHA aarch64 runners
We no longer need to do cross compilation, and these instances seem
pretty fast. Enable them everywhere across the board.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-02-06 08:30:20 -06:00
Yuya Nishihara
6f04f50a80 docs: add query example to fetch contributors from GitHub
I don't know if this is the correct way of using GitHub API, but it works.
2025-02-06 13:57:48 +00:00
Roman Timushev
6bd81c5800 docs: Add git merge to the git command equivalence table
People moving from git may want to know what is the jj equivalent of git merge.
2025-02-06 13:53:25 +00:00
Vincent Ging Ho Yim
2bde34394c github: fix build step names to use imperatives 2025-02-06 23:02:56 +11:00
Yuya Nishihara
6574f12173 local_working_copy: scan directory entries excluded by auto-track pattern
Since we need to scan directory entries recursively in order to detect new
untracked paths, it doesn't make sense to reuse the .gitignore code path.

This change means all untracked file paths are listed in "jj status" even if
the whole directory is untracked. It might be a bit verbose, but should be
okay. Directories like node_modules should be excluded by .gitignore, not by
auto-track pattern.

Fixes #5389
2025-02-06 18:04:57 +09:00
Jacob Hayes
4787dfef16
Fix backends.ssh.allowed-signers type in config-schema 2025-02-06 12:26:49 +07:00
Yuya Nishihara
613742dfbb release: 0.26.0
Reordered some changelog entries in a way that similar items are placed closer.
2025-02-06 10:32:40 +09:00
Yuya Nishihara
1faea485ca git: enable sideband progress on subprocess push
The parsing function is adjusted in case stderr contained unparsed progress
messages.
2025-02-06 01:05:48 +00:00
Yuya Nishihara
5396138ed5 cli: update "only_path" warnings to say fileset expression instead of path
It's technically more correct.
2025-02-06 00:45:44 +00:00
Jonathan Gilchrist
9cd61ece4e docs: Split out commit guidelines into their own section
The commit guidelines feel like they're relevant before documentation
about the code review process - they could conceivably apply while
developing a change.

These often seem to get missed and raised during reviews - giving them
their own section may increase visibility.
2025-02-05 22:04:22 +00:00
Vincent Ging Ho Yim
8f15f7eae9 docs/install-and-setup: clarify wording re: binaries of latest GitHub release 2025-02-05 21:31:47 +00:00
Vincent Ging Ho Yim
0c12636981 docs/install-and-setup: move 'prerelease version' sentence to after 'Cargo Binstall' section
It seems that in 38daa9a the 'Cargo Binstall' section should have been inserted above the 'prelease version' sentence instead.
2025-02-05 21:31:21 +00:00
Vincent Ging Ho Yim
539cd75f90 docs: use styled admonition blocks for warnings
This is consistent with the rest of the docs.
2025-02-05 23:09:02 +11:00
Yuya Nishihara
e091172b63 git: force git subprocess to not localize error messages
Since we parse error messages, we need to disable translation at all.
2025-02-05 16:20:13 +09:00
Martin von Zweigbergk
f67373368e docs: describe how to do a release 2025-02-04 22:05:24 -08:00
Ilya Grigoriev
b95628c398 built-in pager: document key bindings
I mostly focused on:

- keys for absolute beginners
- keys for features for which it's not obvious they
  *have* a key binding
2025-02-05 02:38:11 +00:00
Ilya Grigoriev
f60014f3ee built-in pager: allow configuring streampager options
This also changes the default to be closer to `less -FRX`. Since this
default last changed very recently in #4203, I didn't mention this in
the Changelog.

As discussed in https://github.com/jj-vcs/jj/pull/4203#discussion_r1914372214

I initially kept the config closer to streampager's (see
https://github.com/jj-vcs/jj/compare/main...ilyagr:jj:streamopts?expand=1), but
then decided to make it more generic, smaller, and hopefully easier to
understand.
2025-02-05 02:38:11 +00:00
Ilya Grigoriev
8aa29169ea pager: refactor pager config (no-op)
In the future, we could consider adding a few more
special configs, e.g. `:pagerenv` that strictly uses
the `PAGER` environment variable, and `:unix` that
mimics Git's algorithm of trying `PAGER`, defaulting
to `less`, and setting `LESS`.
2025-02-05 02:38:11 +00:00
Yuya Nishihara
891fe085a6 templater: add DiffStats type to provide raw stats values
I originally considered adding `stats() -> DiffStats` which returns an
unprintable object, with deprecation of `.stat(width)` in favor of
`.stats().<method_to_render>(width)`. However, I couldn't find a good name for
the rendering function. This patch instead made the width parameter optional. I
think that's good because template language doesn't have to be overly strict.

Closes #4154
2025-02-05 00:31:16 +00:00
Yuya Nishihara
b9aef59f0d diff: extract DiffStats type and calculate() function
This will help write template based on diff.stat() result. #4154

show_diff_stats() isn't reimplemented as DiffStats method because I think
DiffStats can be moved to jj-lib.
2025-02-05 00:31:16 +00:00
Yuya Nishihara
7c2d9f2878 diff: map read error of materialized file to BackendError
io::Error in this context means write!(formatter, ..) failed, whereas this is an
error reading file content from the backend.
2025-02-05 00:31:16 +00:00
Yuya Nishihara
7a66122778 diff: rename struct DiffStat to DiffStatEntry
I'm going to add Vec<DiffStatEntry> wrapper as DiffStats.
2025-02-05 00:31:16 +00:00
Yuya Nishihara
e85644994a diff: accumulate diff stat numbers separately
Total aadded/removed counts will be provided as DiffStats methods. This change
might result in slightly bad perf, but that wouldn't matter in practice.
2025-02-05 00:31:16 +00:00
Yuya Nishihara
2bef93bbbe diff: do not format diff stat paths early
This helps extract pure computational part from show_diff_stat(). I'm going to
add DiffStats template type.
2025-02-05 00:31:16 +00:00
Josh Steadmon
227eccefdb templater: add pad_centered template function
Add a new pad_center function that centers content within a minimum
width. If an odd number of fill characters is required, the trailing
fill will be one character longer than the leading fill.

Fixes #5066.
2025-02-04 18:16:20 +00:00
dependabot[bot]
e6dfe1332c cargo: bump clap from 4.5.27 to 4.5.28 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.27 to 4.5.28
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.27...clap_complete-v4.5.28)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 15:58:19 +00:00
Vincent Ging Ho Yim
e995f7eec2 docs/windows: remove unused link
The only usage of this link was removed in 2d0b715.
2025-02-04 12:59:26 +00:00
Vincent Ging Ho Yim
de3c90ac90 docs/install-and-setup: replace linuxbrew with Homebrew
The name `Linuxbrew` is no longer used since Homebrew 2.0.0:
https://brew.sh/2019/02/02/homebrew-2.0.0/
2025-02-04 12:42:21 +00:00
Vincent Ging Ho Yim
c3661d389c docs/windows: link to Git docs on core.autocrlf setting 2025-02-04 12:09:57 +00:00
Vincent Ging Ho Yim
6ff196a191 docs/windows: clarify that no CRLF-to-LF conversion is done by jj on committing
The original description suggests jj somehow converts line endings, when in fact all it
does is commit files as is (as described in the previous paragraph), whether the line
endings are CRLF or LF.
2025-02-04 12:09:57 +00:00
Yuya Nishihara
832e7b3522 cli: squash: use draft description template to generate editor content
Closes #5559
2025-02-04 06:05:58 +00:00
Yuya Nishihara
3cf6b3ca9d cli: split combine_messages() to two parts
Editor template will be rendered by caller.
2025-02-04 06:05:58 +00:00
Yuya Nishihara
e85a5c44cd rewrite: move description handling from squash_commits() to caller
This allows caller to reborrow tx.repo() to render description template. It
also matches the documented behavior.
2025-02-04 06:05:58 +00:00
Ilya Grigoriev
e5d126fba4 pyproject.toml: update to newer syntax
We've been using `uv 0.4` syntax, but we only support `uv` 0.5.1+. The
newer `uv 0.5` syntax follows an approved PEP, and the older syntax will
probably be removed at some point.

I also updated a few comments.
2025-02-04 04:58:26 +00:00
dependabot[bot]
0bcddff473 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [async-trait](https://github.com/dtolnay/async-trait) and [syn](https://github.com/dtolnay/syn).


Updates `async-trait` from 0.1.85 to 0.1.86
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.85...0.1.86)

Updates `syn` from 2.0.96 to 2.0.98
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.96...2.0.98)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 00:30:14 +00:00
Martin von Zweigbergk
834b0f195b cli: bookmarks: don't show Git hint in non-Git repos
Noticed while importing the latest version into the Google repo (some
of our tests failed).
2025-02-03 23:46:05 +00:00
dependabot[bot]
bb87c2e831 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.47.32 to 2.48.1
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](65835784ac...510b3ecd79)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 21:02:45 +00:00
Vincent Ging Ho Yim
ebac2b8a4e docs: add language identifiers to code blocks to improve syntax highlighting 2025-02-03 13:43:24 +00:00
Vincent Ging Ho Yim
5fcd639ea4 docs: remove references to jj amend
`jj amend` is discouraged and no longer a visible alias of `jj squash` since 6d78d92. This
is the only remaining occurrence of it in the docs. We should recommend using `jj
squash` directly instead.
2025-02-03 13:00:51 +00:00
Yuya Nishihara
7898eb9f82 git: split function that queries remote default branch, call only when needed
With git.subprocess = true, it's more important to skip unneeded remote
operations. Each remote command may involve user intervention if authentication
requires manual step.

This change also means that the remote connection is no longer reused in git2
impl. I think the added cost is acceptable. The git2 impl will hopefully be
removed soon, and the remote branch name is needed only when cloning new repo.
2025-02-03 03:43:11 +00:00
Yuya Nishihara
241b8873ba revset: escape symbols in "did you mean" hint
The hint should include expressions that can be copied to -r argument.
2025-02-03 01:31:04 +00:00
Yuya Nishihara
fc7ac4c0bf revset: extract helper that formats remote symbol
I'll add a few more callers.
2025-02-03 01:31:04 +00:00
Yuya Nishihara
5be25bc8cf parser: use backtick to quote name or expression in error message
I don't have any preference about quoting styles, but it looks weird if an
escaped symbol is surrounded by double quotes.
2025-02-03 01:31:04 +00:00
Philip Metzger
f31abfd915 docs: Update the contributing docs to mention the new contributors team
I hope this makes the procedure clearer to new contributors like jakobhellerman.
2025-02-02 16:11:10 +00:00
Vincent Ging Ho Yim
193191cc35 rebase: fix grammar in doc comments 2025-02-02 13:44:25 +00:00
Martin von Zweigbergk
b3099b7b67 docs: add new logo as favicon 2025-02-01 22:43:55 +00:00
Jakob Hellermann
cfe5915c33 cli: complete: complete revset alias symbols for revisions 2025-02-01 17:16:01 +00:00
Jakob Hellermann
f7429f2254 cli: complete: add dynamic completions for -T template aliases 2025-02-01 16:58:46 +00:00
Angel Ezquerra
7c7486678e docs: update Sapling comparison logs 2025-02-01 16:50:53 +00:00
Yuya Nishihara
7e55dd2294 git: check remote existence without using git2::Repository 2025-02-01 11:04:38 +00:00
Yuya Nishihara
bba6f15dfe git: initialize subprocess context without using git2::Repository 2025-02-01 11:04:38 +00:00
Yuya Nishihara
d4023d873d git: instantiate GitSubprocessContext by push_updates() for consistency 2025-02-01 11:04:38 +00:00
Yuya Nishihara
8cad3ac95a git: extract GitFetchImpl that holds implementation-specific objects
This could be a trait object, but we only need a single fetch() function per
implementation.
2025-02-01 11:04:38 +00:00
Yuya Nishihara
6e3c7fcf2d git: extract inner fetch functions out of GitFetch struct
I'm going to add enum that holds either git2::Repository or subprocess context.
"&mut self" will be useless there.
2025-02-01 11:04:38 +00:00
Yuya Nishihara
658cd3e0fd git: resolve git2::Repository internally in GitFetch::new()
git2::Repository will be removed from the subprocess code path.
2025-02-01 11:04:38 +00:00
Yuya Nishihara
9bf29accc2 git: resolve git2::Repository internally in push_updates()
git2::Repository will be removed from the subprocess code path.
2025-02-01 11:04:38 +00:00
Yuya Nishihara
fd76b44d55 cli: transform GitFetch/PushError globally 2025-02-01 11:04:38 +00:00
Yuya Nishihara
27e7672a3b git: store bad pattern object in GitFetchError::InvalidBranchPattern
This helps add From<GitFetchError> impl.
2025-02-01 11:04:38 +00:00
Jakob Hellermann
d212becdba cli: complete: use null Ui to prevent writing warnings into completions
E.g. when the user doesn't have the default command set, you don't want
to get
```
Warning: Cannot define an alias that overrides the built-in command 'log'
```
printed on every completion.
2025-02-01 08:37:22 +00:00
Jakob Hellermann
d7c887c424 cli: add ability to construct null Ui without any output 2025-02-01 08:37:22 +00:00
dependabot[bot]
e554533792 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.47.31 to 2.47.32
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](76a1fec160...65835784ac)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 04:00:26 +00:00
dependabot[bot]
48dea149a5 cargo: bump toml_edit in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [toml_edit](https://github.com/toml-rs/toml).


Updates `toml_edit` from 0.22.22 to 0.22.23
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.22...v0.22.23)

---
updated-dependencies:
- dependency-name: toml_edit
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 04:00:22 +00:00
Yuya Nishihara
67bb2809ed git: test that local progress is emitted through callback 2025-02-01 03:30:39 +00:00
Baltasar Dinis
b35d503bf7 git: show fetch progress with git.subprocess = true 2025-02-01 01:46:31 +00:00
Yuya Nishihara
994c6a457a cli: remove handling of null terminated string from git sideband writer
I think this is copy-paste error from C implementation.
2025-02-01 00:41:51 +00:00
Yuya Nishihara
fc82e99ae9 cli: move cursor to first column after printing git progress bar
This helps sideband writer overwrite the progress bar. Suppose progress
information is less important than sideband messages, it should be okay to
always overwrite the progress bar. The sideband writer will erase the trailing
characters and move the cursor back to the first column, and/or put "\n"
accordingly.
2025-02-01 00:41:51 +00:00
Scott Taylor
66808e5dc1 conflicts: use clearer wording for missing newline comment
The current comment uses `[noeol]`, which can be difficult to
understand. In this commit, the brackets are changed to parentheses to
make it clear that there is no semantic meaning to the comment, and the
wording is changed to be more clear to the user.
2025-01-31 23:38:42 +00:00
Scott Taylor
0b719332aa conflicts: don't mark missing newline in Git-style conflicts
Git doesn't show this information in their "diff3" style either, and it
looks a bit strange having two different bracketed sections of text next
to each other, so I think it would be better to remove it.
2025-01-31 23:38:42 +00:00
Antoine Martin
c2acc49be5 docs: document the ui.movement.edit config option
This option was introduced in #4283, but was not documented apart from
`prev` and `next`'s help text on the --edit/--no-edit flags.
2025-01-31 23:10:34 +00:00
Martin von Zweigbergk
4ac970fcc5 cleanup: prefer MutableRepo::repo() over repo_mut()
When we don't need a mutable reference, we should be using `repo()`.
2025-01-31 18:03:01 +00:00
Martin von Zweigbergk
0f1a6fc4ae rebase: clarify -d/-A/-B arguments with examples 2025-01-31 16:08:15 +00:00
Martin von Zweigbergk
0d00de72c6 cli: git: escape bookmark and remote names in trunk() config
Closes #5359.
2025-01-31 15:54:47 +00:00
Martin von Zweigbergk
ef20b7b7ab revset: add a function for escaping a symbol
When we have e.g. a bookmark name or a remote name and we want to
produce a revset from it, we may need to quote and escape it. This
patch implements a function for that.

Perhaps we'll want to more generally be able to format a whole
`RevsetExpression` later.
2025-01-31 15:54:47 +00:00
Martin von Zweigbergk
b1827e16f1 cli: tests: fix a bad copy&paste 2025-01-31 15:54:47 +00:00
Yuya Nishihara
b637e98127 git: enable sideband message callback globally, disable on fetch without tty
With this and "git --progress" PR #5519, remote git progress will be displayed.

The sideband callback is disabled in git2 code path if progress is not enabled.
If this were enabled, most "git fetch" tests would fail with git2 due to remote
progress messages. Since there's no git2 API to turn the remote progress off,
and the git2 code path is supposed to be phased out, I just inserted ad-hoc
workaround.
2025-01-31 01:47:34 +00:00
Yuya Nishihara
4dd9c5f0a7 git: simplify cast of progress callback 2025-01-31 01:47:34 +00:00
Yuya Nishihara
cdbfd6bc51 git: reimplement get_all_remotes() by using gix API 2025-01-31 00:52:23 +00:00
Yuya Nishihara
7a1cd6a0f6 git: factor out common get_git_backend/repo() helper, simplify error handling 2025-01-31 00:52:23 +00:00
Martin von Zweigbergk
faa689a736 contributing.md: describe how to configure jj fix to run rustfmt 2025-01-30 19:14:43 +00:00
dependabot[bot]
d620ef95d1 github: bump the github-dependencies group with 3 updates
Bumps the github-dependencies group with 3 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action), [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.47.30 to 2.47.31
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](afbe5c1715...76a1fec160)

Updates `astral-sh/setup-uv` from 5.2.1 to 5.2.2
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](b5f58b2abc...4db96194c3)

Updates `github/codeql-action` from 3.28.7 to 3.28.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](6e54559041...dd746615b3)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-30 16:26:12 +00:00
dependabot[bot]
6ee7525add cargo: bump clap_complete in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap_complete](https://github.com/clap-rs/clap).


Updates `clap_complete` from 4.5.43 to 4.5.44
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.43...clap_complete-v4.5.44)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-30 16:25:42 +00:00
Yuya Nishihara
d3f56c0acd git: forward "remote: " lines when they arrive
This patch adds thread-based implementation. On Unix, we can poll pipes without
using thread, but I don't think platform-dependent optimization is needed.
2025-01-30 07:06:42 +00:00
Scott Taylor
95df920070 docs: explain [noeol] conflict marker comment
The meaning of `[noeol]` might not be immediately clear to a user, so it
would be good to document it on our documentation page for conflicts. We
may also want to improve the conflict marker comments we use for this
case in the future (possibly after receiving user feedback).
2025-01-29 23:49:02 +00:00
Jakob Hellermann
3293a1aa97 cli: completion: derive clap::ValueEnum for ColorChoice
This enables autocompletion for `jj --color <TAB>`
2025-01-29 22:51:24 +00:00
Austin Seipp
a4fa4b6dbd github: replace some exact conditionals with prefix matches
This will be more robust long term since e.g. native ARM runners use
`ubuntu-24.04-arm` as their runner image, and the version number really
isn't a big deal in this case.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-29 22:13:19 +00:00
Josh Steadmon
fb4d796cc6 docs: point to jj-vcs for Nix install instructions 2025-01-29 22:03:16 +00:00
dependabot[bot]
c850ddf289 github: bump the github-dependencies group across 1 directory with 3 updates
Bumps the github-dependencies group with 3 updates in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action), [actions/setup-python](https://github.com/actions/setup-python) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.47.25 to 2.47.30
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](1936c8cfe3...afbe5c1715)

Updates `actions/setup-python` from 5.3.0 to 5.4.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](0b93645e9f...42375524e2)

Updates `github/codeql-action` from 3.28.5 to 3.28.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f6091c0113...6e54559041)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-29 20:50:25 +00:00
dependabot[bot]
a9333287ee cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [serde_json](https://github.com/serde-rs/json) and [tempfile](https://github.com/Stebalien/tempfile).


Updates `serde_json` from 1.0.137 to 1.0.138
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.137...v1.0.138)

Updates `tempfile` from 3.15.0 to 3.16.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.15.0...v3.16.0)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-29 20:50:06 +00:00
Austin Seipp
6f1d15bd1a nix: build flake checks in test profile
Overall, building in the test profile should significantly speed up
the overall build pipeline because so many less cycles are spent (on
GHA runners that are certainly at high load). The goal here is to help
reduce CI flake outs due to things timing out; I suspect part of the
problem may be a lot of the ~15 minute time limit being used up just
compiling things.

This is a partial revert of b714592952400ab, which removed this previous
override of the Flake `checks`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-29 20:30:58 +00:00
Austin Seipp
1473c0cd4f nix: split rust toolchains used for shell/CI
This should help reduce time spent on closure downloads.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-29 20:30:58 +00:00
Austin Seipp
1b0f95910b github: track slow tests in CI builds
Some of our builds have been timing out, and one angle I want to look at
is whether any tests are hanging. Nextest can help us keep track of slow
tests in CI where the underlying hosts will have significantly higher
load. In general this should also help keep our tests healthy, IMO.

I don't see any reason why any existing test should take over 20
seconds, but it should at least help control for really slow runners
that have huge latency spikes. we can start there and see how it goes
in practice.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-29 20:30:58 +00:00
Matt Kulukundis
5b5a9e71c3 docs: improve cli docs for consistency and links 2025-01-29 18:01:16 +00:00
Baltasar Dinis
120b9cc766 git: forward remote messages to output 2025-01-28 21:56:04 +00:00
Baltasar Dinis
e8620c31ae git: describe codepaths that require git2 2025-01-28 18:29:01 +00:00
Baltasar Dinis
eb1d70317a cli: relocate git fetch code to cli/src/commands/git/fetch.rs
This code was living in cli/src/git_utils.rs but no one else was using
it
2025-01-28 18:29:01 +00:00
Baltasar Dinis
1be574c219 git: update jj git clone|fetch to use new GitFetch api directly.
* Make the new `GitFetch` api public.
* Move `git::fetch` to `lib/tests/test_git.rs` as `git_fetch`, to minimize
  churn in the tests. Update test call sites to use `git_fetch`
* Delete the `git::fetch` from `lib/src/git.rs`.
* Update `jj git clone` and `git_fetch` in `cli/src/git_utils.rs` to use
  the new api directly. Removing one redundant layer of indirection.
* This fixes #4920 as it first fetches from all remotes before `import_refs()`
  is called, so there is no race condition if the same commit is treated
  differently in different remotes specified in the same command.

Original commit by @essiene
2025-01-28 18:29:01 +00:00
Baltasar Dinis
2f8bbb111a tests: refactor cli tests for git 2025-01-28 18:29:01 +00:00
Jonathan Frere
11fbbf6ad4 docs: fix typo in templating language example 2025-01-28 17:42:12 +00:00
Yuya Nishihara
90e79348e3 diff: remove ad-hoc rename handling in show_diff_stat()
Renamed entries are omitted by iterator/stream since 352a4a0eea8e "copies:
filter rename source entries by CopiesTreeDiffStream."
2025-01-28 01:44:57 +00:00
Ilya Grigoriev
c5e30f9f30 codespell action: use uv to run codespell
The primary goal is to control the version of the `codespell` Python
package that we run via `uv.lock`. See also
https://github.com/jj-vcs/jj/pull/5425.

Also move the action next to the `cargo fmt` action. It might make sense
to split both of them out of `build.yml` if it gets too long, but I
think they should be next to each other since they are so similar in
spirit.
2025-01-28 00:52:17 +00:00
Ilya Grigoriev
f4346f2e51 cli reference & jj log --help: render URLs in CLI refrerence
Fixes #5490 (we can catch other instances of this manually)

Note that it links to the `latest` even if you are looking at the
`prerelease` docs. This is not ideal, but seems annoying to fix.
2025-01-28 00:50:54 +00:00
Bryce Berger
88d3b52081 docs: fix typo '--when.command' -> '--when.commands' 2025-01-28 00:46:54 +00:00
Bryce Berger
1bcd55d077 cli: remove double ": : " on git password prompt
`ui.prompt_password()` already adds ": " to the end of the prompt.
Remove the extra at the (only) callsite, to keep similarity to the above
`terminal_get_pw()`.
2025-01-27 22:59:40 +00:00
Scott Taylor
66faeb4487 conflicts: show "noeol" state in conflict marker comment
This isn't strictly necessary since it doesn't affect parsing, but it
should make it more understandable for the user.
2025-01-27 22:59:06 +00:00
Scott Taylor
326c453064 conflicts: allow missing newlines at end of file
A missing newline would cause the following conflict markers to become
invalid when materializing the conflict, so we add a newline to prevent
this from happening. When parsing, if we parsed a conflict at the end of
the file which ended in a newline, we can check whether the file
actually did end in a newline, and then remove the newline if it didn't.
2025-01-27 22:59:06 +00:00
Scott Taylor
352807c050 conflicts: remove support for non-simplified file conflicts
File conflicts have been simplified during materialization since 0.19.0,
so it should be safe to remove support for unsimplified conflicts now.
This will make implementing the next commit easier, since it won't have
to support unsimplified conflicts.
2025-01-27 22:59:06 +00:00
dependabot[bot]
8cade7b1b0 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.47.24 to 2.47.25
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](6a08c6906b...1936c8cfe3)

Updates `github/codeql-action` from 3.28.4 to 3.28.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ee117c905a...f6091c0113)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 19:13:56 +00:00
dependabot[bot]
c8509df8cd cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [clap_complete](https://github.com/clap-rs/clap) and [insta](https://github.com/mitsuhiko/insta).


Updates `clap_complete` from 4.5.42 to 4.5.43
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.42...clap_complete-v4.5.43)

Updates `insta` from 1.42.0 to 1.42.1
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.42.0...1.42.1)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: insta
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 18:00:41 +00:00
Yuya Nishihara
93d98f11ba cli: git clone: reload workspace after configuring new remote
I originally thought we could just reinitialize RepoLoader, but it wasn't
enough. Since LocalWorkingCopy holds Arc<Store>, we need to reinstantiate both
working copy and repo loader.
2025-01-27 11:03:39 +00:00
Yuya Nishihara
94754b7f49 cli: add helper method to load workspace at arbitrary path
map_workspace_load_error() is slightly adjusted because we now have three
"user" wc path options: 1. cwd (default), 2. -R path, 3. internal API call.
2025-01-27 11:03:39 +00:00
Martin von Zweigbergk
1c9b694839 cargo: bump gix to 0.70.0 2025-01-27 06:46:17 +00:00
Yuya Nishihara
dcca1811fe cli: apply log --limit before --reversed
As I said, I don't have strong feeling about the current behavior, and appears
that "log | head | reverse" is preferred over "log | reverse | head".
"jj evolog" already behaves differently, so I just updated the doc.

Note that the new behavior might be slightly different from git, but nobody
would care. (iirc, in git, topological sorting comes later.)

Closes #5403
2025-01-27 01:35:36 +00:00
Yuya Nishihara
9985dce0ab revset: inline reversed() iterator to caller
The implementation isn't specific to revset, and is short.
2025-01-27 01:35:36 +00:00
Scott Taylor
00069119d8 tests: keep user's $PATH while running jj commands
Currently, the Git subprocess tests only work on Linux due to a default
path being used for the `git` executable when `$PATH` is unset. This can
break if Git isn't installed at the expected path. Also, I believe it is
currently necessary to set the `$TEST_GIT_EXECUTABLE_PATH` environment
variable on Windows for tests to pass. Instead, we should use the user's
`$PATH` to locate the `git` executable, as well as any other executables
that are needed. This also makes `$TEST_GIT_EXECUTABLE_PATH` no longer
necessary, so it can be removed.
2025-01-26 17:16:59 +00:00
Martin von Zweigbergk
fe06ef471b docs: fix a mention of the deleted jj checkout command
Closes #5471.
2025-01-26 17:09:05 +00:00
Jonathan Gilchrist
d8869aea97 Changelog: Fix a broken link
Other links in CHANGELOG.md have the issue number as the text, and the
full URL as a link. They're swapped here, meaning the link points to a
non-existent anchor in the CHANGELOG.md file.
2025-01-26 16:52:44 +00:00
Austin Seipp
139d126b67 github: remove Magic Nix Cache
Apparently, Magic Nix Cache will stop working on Feb 1st, because
the underlying APIs are being sunset by GitHub, and there are no open
replacements for it quite yet. Since it's just an optimization, we'll
have to live with it.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-26 13:09:41 +00:00
Ilya Grigoriev
332b8d774a config-schema.json: fixup to default-command
Before this commit, both VS Code's "Even better TOML" and `taplo`
reported an error saying `["status","--no-pager"] is not of type
"string"` for

```toml 
"$schema" = "https://jj-vcs.github.io/jj/latest/config-schema.json"
ui.default-command = ["status", "--no-pager"]
``` 

I believe the schema was either invalid or just ignored `oneOf` because
it was trivially satisfied whenever the `type` restriction was
satisfied.
2025-01-26 06:03:40 +00:00
Martin von Zweigbergk
e299931692 docs: fix typo in env variable $COMPLETE, and clarify text
Closes #5466.
2025-01-26 02:55:16 +00:00
Yuya Nishihara
fe54c6276e templater: add diff.files() method
This can be used in order to count the number of added files by
diff.files().filter(..).len(), for example. If we add more advanced fileset
predicates, it can also be expressed as diff("added()").files().len(). The
latter would be cheaper to compute.

For #4154, I'll probably add DiffStats template type. It might be doable by
extending diff.files() method, but I don't think it's good idea to write stats
calculation logic in template language.

Closes #5272
2025-01-26 01:54:33 +00:00
Yuya Nishihara
b22347e80b templater: add TreeDiffEntry types
Tests will be added later.
2025-01-26 01:54:33 +00:00
Yuya Nishihara
e21f8e62cb diff: extract function that maps diff entry to status label and char
I'm going to add diff_entry.status() template method.
2025-01-26 01:54:33 +00:00
dependabot[bot]
e58713c135 github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.28.3 to 3.28.4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](dd196fa9ce...ee117c905a)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-25 02:05:53 +00:00
Ilya Grigoriev
5d43e1ddc9 config.md: fixup to bb74a9e, dehesselle not deheselle
Fixes #5456
2025-01-24 20:37:21 +00:00
Bryce Berger
2a57a6bdd2 nix: flake update
bacon 3.6.0 -> 3.8.0, shows output with nextest 0.9.86
https://github.com/Canop/bacon/issues/280

Add python3 to the devshell to be able to run tools installed with uv
2025-01-24 20:12:33 +00:00
dependabot[bot]
f3bbc50453 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.47.23 to 2.47.24
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](7e1dca9e0c...6a08c6906b)

Updates `github/codeql-action` from 3.28.2 to 3.28.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](d68b2d4edb...dd196fa9ce)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-24 07:49:19 +00:00
Bryce Berger
cbb743cfb5 config: add --when.command to scope resolution
Closes #5217

Motivating use case:

    [[--scope]]
    --when.command = ["log"]
    [--scope.ui]
    pager = "less"

This adds a new (optional) field to `--when` config conditions, to
inspect the current command.

`--when.commands` is a list of space-separated values that matches a
space-separated command. To be specific:

    --when.command = ["file"]        # matches `jj file show`, `jj file list`, etc
    --when.command = ["file show"]   # matches `jj file show`, but *NOT* `jj file list`
    --when.command = ["file", "log"] # matches `jj file` *OR* `jj log` (or subcommand of either)

When both `--when.commands` and `--when.repositories` are set, the
intersection is used.
2025-01-24 05:30:07 +00:00
Yuya Nishihara
362be5914f templater: add list.filter(predicate) method
If we add generic diff template, we'll probably want to filter diff entries by
status, etc.

Closes #5291
2025-01-24 03:42:39 +00:00
Yuya Nishihara
af05534108 templater: extract helper that builds lambda expression body
I also made the helper function accept multiple parameters/arguments as doing
that was easy.
2025-01-24 03:42:39 +00:00
Yuya Nishihara
9683d81f03 templater: load diff parameters from config file
It didn't before just because UserSettings wasn't available to the templater.
2025-01-24 03:42:26 +00:00
Yuya Nishihara
3ed77c9bb3 cli: split diff options constructors to not require command args 2025-01-24 03:42:26 +00:00
Yuya Nishihara
fb25e8a4ad cli: add hint to config error occurred within templater 2025-01-24 03:42:26 +00:00
Ilya Grigoriev
bb74a9eaf5 config.md: document how to install Meld (on a Mac, especially)
Installing Meld on a Mac was difficult until quite recently. I also
quickly mentioned how to do it on other OSs.
2025-01-24 02:02:44 +00:00
Ilya Grigoriev
0d55bc56f0 config.md: use meld as example diff editor once more
Followup to 4ecf75ef
2025-01-24 02:02:44 +00:00
bsdinis
35440ce1bd git: spawn a separate git process for network operations
Reasoning:

`jj` fails to push/fetch over ssh depending on the system.
Issue #4979 lists over 20 related issues on this and proposes spawning
a `git` subprocess for tasks related to the network (in fact, just push/fetch
are enough).

This PR implements this.

Implementation Details:

This PR implements shelling out to `git` via `std::process::Command`.
There are 2 sharp edges with the patch:
 - it relies on having to parse out git errors to match the error codes
   (and parsing git2's errors in one particular instance to match the
   error behaviour). This seems mostly unavoidable

 - to ensure matching behaviour with git2, the tests are maintained across the
   two implementations. This is done using test_case, as with the rest
   of the codebase

Testing:

Run the rust tests:
```
$ cargo test
```

Build:
```
$ cargo build
```

Clone a private repo:
```
$ path/to/jj git clone --config='git.subprocess=true' <REPO_SSH_URL>
```

Create new commit and push
```
$ echo "TEST" > this_is_a_test_file.txt
$ path/to/jj describe -m 'test commit'
$ path/to/jj git push --config='git.subprocess=true' -b <branch>
```


Issues Closed

With a grain of salt, but most of these problems should be fixed (or at least checked if they are fixed). They are the ones listed in #4979 .

SSH:
- https://github.com/jj-vcs/jj/issues/63
- https://github.com/jj-vcs/jj/issues/440
- https://github.com/jj-vcs/jj/issues/1455
- https://github.com/jj-vcs/jj/issues/1507
- https://github.com/jj-vcs/jj/issues/2931
- https://github.com/jj-vcs/jj/issues/2958
- https://github.com/jj-vcs/jj/issues/3322
- https://github.com/jj-vcs/jj/issues/4101
- https://github.com/jj-vcs/jj/issues/4333
- https://github.com/jj-vcs/jj/issues/4386
- https://github.com/jj-vcs/jj/issues/4488
- https://github.com/jj-vcs/jj/issues/4591
- https://github.com/jj-vcs/jj/issues/4802
- https://github.com/jj-vcs/jj/issues/4870
- https://github.com/jj-vcs/jj/issues/4937
- https://github.com/jj-vcs/jj/issues/4978
- https://github.com/jj-vcs/jj/issues/5120
- https://github.com/jj-vcs/jj/issues/5166

Clone/fetch/push/pull:
- https://github.com/jj-vcs/jj/issues/360
- https://github.com/jj-vcs/jj/issues/1278
- https://github.com/jj-vcs/jj/issues/1957
- https://github.com/jj-vcs/jj/issues/2295
- https://github.com/jj-vcs/jj/issues/3851
- https://github.com/jj-vcs/jj/issues/4177
- https://github.com/jj-vcs/jj/issues/4682
- https://github.com/jj-vcs/jj/issues/4719
- https://github.com/jj-vcs/jj/issues/4889
- https://github.com/jj-vcs/jj/discussions/5147
- https://github.com/jj-vcs/jj/issues/5238

Notable Holdouts:
 - Interactive HTTP authentication (https://github.com/jj-vcs/jj/issues/401, https://github.com/jj-vcs/jj/issues/469)
 - libssh2-sys dependency on windows problem (can only be removed if/when we get rid of libgit2): https://github.com/jj-vcs/jj/issues/3984
2025-01-23 16:50:53 +00:00
bsdinis
c773c0296f git: test git fetch with more than two remotes and branches
In some cases, there are non trivial codepaths for fetching multiple
branches explicitly. In particular, it might be the case that fetching
works for n = 2 but not n = 3.

This commit changes a cli test to have 3 remotes and 3 branches, and a
lib test with 3 branches, only one of them succeds.
2025-01-23 16:50:53 +00:00
bsdinis
717ae02f62 lib: add type support for refspecs
Fully qualified git refs (e.g., `+refs/head/main:refs/remotes/origin/main`)
are commonly used throughout the git glue code.

It is laborious and error prone to keep parsing refspecs when we need
only a segment of it.

This commits adds a type, `RefSpec`, which does exactly that
2025-01-23 16:50:53 +00:00
Jonathan Frere
55d1c17245 cli: describe: Mark conflicting message/stdin args 2025-01-23 16:11:32 +00:00
Jonathan Frere
e63d1d5114 cli: describe: Add a --edit flag
The --edit flag forces the editor to be shown, even if it would have
been hidden due to the --no-edit flag or another flag that implies it
(such as --message or --stdin).
2025-01-23 16:11:32 +00:00
Yuya Nishihara
8eebac54fc cli: port "file list" to template 2025-01-23 02:21:15 +00:00
Yuya Nishihara
7db343ec32 templater: add TreeEntry type
I'm going to add "file list" template, and I think it's better to provide a file
path as "path", not "self". This patch also adds some tree value properties
which seemed useful.

Tests will be added later.
2025-01-23 02:21:15 +00:00
Yuya Nishihara
5062ae83d3 templater: add RepoPath types
This will be used in "file list" template. Option<RepoPath> type isn't
needed for that, but I think it'll appear somewhere in custom diff template.
The path.parent() method is added mainly for testing Option<RepoPath>.

Tests will be added later.
2025-01-23 02:21:15 +00:00
Yuya Nishihara
ce3100a8cd cli: reformat config/templates.toml
.editorconfig is set to trim trailing whitespace. I also adjusted indent style
and trailing commas.
2025-01-23 02:21:15 +00:00
dependabot[bot]
f264e9556d cargo: bump rustix in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [rustix](https://github.com/bytecodealliance/rustix).


Updates `rustix` from 0.38.43 to 0.38.44
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.43...v0.38.44)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 18:51:24 +00:00
dependabot[bot]
e478f0f507 github: bump the github-dependencies group with 3 updates
Bumps the github-dependencies group with 3 updates: [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action), [taiki-e/install-action](https://github.com/taiki-e/install-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `DeterminateSystems/magic-nix-cache-action` from 8 to 9
- [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)
- [Commits](87b14cf437...6221693898)

Updates `taiki-e/install-action` from 2.47.21 to 2.47.23
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](da41fb311f...7e1dca9e0c)

Updates `github/codeql-action` from 3.28.1 to 3.28.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b6a472f63d...d68b2d4edb)

---
updated-dependencies:
- dependency-name: DeterminateSystems/magic-nix-cache-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 18:51:05 +00:00
Yuya Nishihara
2129c8996f cli: migrate "git remote list" to gix
This one is easy, but porting other "jj git remote" sub commands would require
non-trivial work.

The empty [remote "<name>"] case is covered by existing tests.
2025-01-22 12:39:46 +00:00
Yuya Nishihara
721aecb4cc config: add public ConfigLayer::ensure_table() function
It's not uncommon to insert new table at a certain path. This could be modelled
as set_table(name, new_table), but the caller might want to merge items with
the existing table if any. This function can be used for both cases.
2025-01-22 01:01:04 +00:00
Yuya Nishihara
d9e0fb7974 config: add migration type that runs user-specified function
This was needed in order to migrate fix.tool-command to [fix.tools] table. We
don't have other use cases right now, but there will be something in future.
2025-01-22 01:01:04 +00:00
Yuya Nishihara
ffaaf89f05 config: add migration type that renames and updates value
This will be used in order to migrate boolean value to enum, for example.
2025-01-22 01:01:04 +00:00
Yuya Nishihara
9d77ad5594 config: add separate type for migration error, attach source path
Migration can fail because of unexpected value type, etc.
2025-01-22 01:01:04 +00:00
Ilya Grigoriev
231c968f51 dynamic completion: complete jj git push {-r,-c}
I had to guess whether to complete all revisions or just mutable ones. I'm
guessing that `-r` is sometimes used with immutable revisions (as part of a
revset, or for creating special-purpose branches), while `-c` is almost always
used to push a mutable revision. We can change it later if my guess is wrong.
2025-01-22 00:45:46 +00:00
Ilya Grigoriev
cf69a36a62 dynamic completion: add revision completion to hidden -r arguments
Before this commmit, `jj desc <tab>` resulted in revision completions, but the equivalent `jj desc -r <tab>` didn't.
2025-01-22 00:45:46 +00:00
Yuya Nishihara
a6f20cf42f graph: make reverse_graph() accept separate node id type
Although extra clone/IO wouldn't matter in practice, we can simplify callers
by constructing the edges list to be passed to the graph renderer.
2025-01-22 00:44:42 +00:00
Yuya Nishihara
b70d0c50e1 graph: remove redundant clone from reverse_graph()
All input nodes should be unique.
2025-01-22 00:44:42 +00:00
Ilya Grigoriev
5dbc4ae619 actions: fix codespell CI failure
It is having a fit for some reason, see e.g.
https://github.com/jj-vcs/jj/actions/runs/12897590111/job/35963026619?pr=5414

Not sure what changed, but here is a quick fix.

A likely cause is
https://github.com/codespell-project/codespell/releases/tag/v2.4.0 .
It'd be cool to fix the version of codespell the action uses, but I
couldn't find a way to do it quickly.
2025-01-22 00:20:33 +00:00
dependabot[bot]
cff86ed5fa github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.47.19 to 2.47.21
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](24758ef6e7...da41fb311f)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-21 16:46:20 +00:00
dependabot[bot]
1a2bf50fa1 cargo: bump clap from 4.5.26 to 4.5.27 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.26 to 4.5.27
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.26...clap_complete-v4.5.27)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-21 16:45:52 +00:00
Martin von Zweigbergk
be32d4e3ef cli: rebase: say that -r allows set, clarify -s X vs -r X:: 2025-01-21 06:43:15 +00:00
Yuya Nishihara
4a50a35046 cli: abandon: delete bookmarks pointing to abandoned commits
Based on the discussion in #3505, I think the sliding behavior isn't favored at
least for "jj abandon". This patch changes the default to delete bookmarks.

"jj rebase --skip-emptied" can also be updated if needed. It might be good for
consistency. However, I'm skeptical about changing the default of the internal
API. It's not easy to add generic reporting mechanism for deleted/abandoned
bookmarks. If we added one in report_repo_changes(), redundant message would be
printed on "jj git fetch". So I think callers should enable the deletion
explicitly.

Closes #3505
2025-01-21 02:37:07 +00:00
Yuya Nishihara
686ab24a5c cli: abandon: use transform_descendants() to do reparent/rebase conditionally
This will help implement "jj abandon --retain-bookmarks".
2025-01-21 02:37:07 +00:00
Yuya Nishihara
c50a70906b rewrite: add option to delete abandoned bookmarks
I'll make "jj abandon" delete bookmarks by default. This could be handled by
cmd_abandon(), but we'll need a repo API if we also want to change the behavior
of "jj rebase --skip-emptied".
2025-01-21 02:37:07 +00:00
Yuya Nishihara
ace041ec96 rewrite: add a few more bookmarks to abandoning test, remove redundant assertion 2025-01-21 02:37:07 +00:00
Yuya Nishihara
a3636d8a83 revset: add subject() predicate that matches first line of descriptions
It's generally useful, and we can get by without introducing weird special case
about newline terminator.

Closes #5227
2025-01-21 02:13:04 +00:00
dependabot[bot]
3d7858df26 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [indexmap](https://github.com/indexmap-rs/indexmap) and [serde_json](https://github.com/serde-rs/json).


Updates `indexmap` from 2.7.0 to 2.7.1
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.7.0...2.7.1)

Updates `serde_json` from 1.0.135 to 1.0.137
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.135...v1.0.137)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-21 02:03:44 +00:00
dependabot[bot]
37e2c360cb github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.47.15 to 2.47.19
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](d125c0a835...24758ef6e7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-21 01:40:53 +00:00
Yuya Nishihara
9663509643 cli: handle remote configuration error on "git clone" 2025-01-21 01:19:15 +00:00
Yuya Nishihara
2415c96cd8 cli: split do_git_clone() function to reduce number of arguments 2025-01-21 01:19:15 +00:00
Yuya Nishihara
5577ffd28d revset: split author()/committer() into _name()/email() predicates
It seemed weird that we don't have primitive functions to query either name or
email field. This also fixes mine() to not match against name.
2025-01-21 01:10:00 +00:00
Yuya Nishihara
206acadd49 revset: remove stale comment about regex pattern 2025-01-21 01:10:00 +00:00
George Christou
9314df9e38 formatter: add support for italic text 2025-01-20 17:46:27 +00:00
Valentin Gatien-Baron
019bd8f757 restore: add --into flag, make --to an alias to it
This is a part of #5394.
2025-01-19 20:17:44 +00:00
Ilya Grigoriev
027589d199 cleanup: remove WatchmanConfig Default impl
This one was actually used, but only in a debug command.
2025-01-18 07:49:43 +00:00
Ilya Grigoriev
3404085ec4 cleanup: remove Conflict default impl
The "default" conflict is not a valid conflict, as it
does not have one more add than removes.
2025-01-18 07:49:43 +00:00
Ilya Grigoriev
5daa138cd7 cleanup: remove a bunch of unused Default impls
I noticed that some config structures do not use their "Default" implementation, and have their default specified in a TOML file. I think specifying defaults in TOML is great, and it'd be good to delete those Default implementations, so that it does not diverge from the default in the TOML file.
2025-01-18 07:49:43 +00:00
Yuya Nishihara
83d40d2c42 repo: move rebase_descendants_with_options_return_map() to tests 2025-01-18 01:21:28 +00:00
Yuya Nishihara
15f4ac5f12 repo: add rebase_descendants_*() that takes callback instead of returning map
There's only one non-test caller who doesn't need a complete map of rebased
commits.

FWIW, I tried to rewrite squash_commits() based on transform_descendants() API,
but it wasn't easy because of A-B+A=A rule. The merge order matters here.
2025-01-18 01:21:28 +00:00
Yuya Nishihara
e3efb0d614 rewrite: pass RebaseOptions by reference
Since we've removed DescendantRebaser, it no longer makes sense to pass options
by value. This patch also replaces Default::default() for clarity.
2025-01-18 01:21:28 +00:00
dependabot[bot]
dbb9e7e9f9 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.47.14 to 2.47.15
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](e26ea2a159...d125c0a835)

Updates `astral-sh/setup-uv` from 5.2.0 to 5.2.1
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](4e3dbecc19...b5f58b2abc)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-17 17:37:24 +00:00
Vincent Ging Ho Yim
661c443dbc docs/contributing: fix incorrect bookmark name 2025-01-17 12:23:24 +00:00
Vincent Ging Ho Yim
efb9be4e55 changelog: remove extraneous parentheses 2025-01-17 12:22:05 +00:00
Vincent Ging Ho Yim
a44ec17c9a changelog: add missing comma 2025-01-17 12:22:05 +00:00
Yuya Nishihara
5d1f3d006d repo: pass &Commit to record_abandoned_commit() to simplify error handling 2025-01-17 00:28:25 +00:00
Yuya Nishihara
8833a7adc7 repo: propagate error from record_rewrites() 2025-01-17 00:28:25 +00:00
Yuya Nishihara
cb981c7bf1 git: propagate error from abandon_unreachable_commits() 2025-01-17 00:28:25 +00:00
dependabot[bot]
34447d7ecc github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `taiki-e/install-action` from 2.47.13 to 2.47.14
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](6ae49f1b8b...e26ea2a159)

Updates `astral-sh/setup-uv` from 5.1.0 to 5.2.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](887a942a15...4e3dbecc19)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-16 17:33:22 +00:00
Yuya Nishihara
646bdabe62 absorb: print status if source commit still contains diffs
Closes #5362
2025-01-16 01:09:33 +00:00
Yuya Nishihara
e910ac4040 absorb: wrap absorb_hunks() result in struct
This patch also renames rewritten_commits as I'm going to add rewritten_source
field.
2025-01-16 01:09:33 +00:00
Yuya Nishihara
3711979a8f absorb: show warning if source commit contains file deletion 2025-01-16 01:09:33 +00:00
Bryce Berger
f7431650be templates: add cryptographic_signature display to default formats
Cryptographic signature support in templates was added in
c99c97c6467fee3f3269d2934c10c758e041f834 (#4853), but has to be manually
configured. This adds some defaults to the built-in config.

Instead of having separate `builtin_*_with_sig` aliases, this adds to
the aliases that actually format commits. Since signature verification
is slow, this is disabled by default. To enable it, override
`ui.show-cryptographic-signatures`:

    [ui]
    show-cryptographic-signatures = true
    [template-aliases]
    'format_short_cryptographic_signature(signature)' = ...
    'format_detailed_cryptographic_signature(signature)' = ...

Note that the two formatting functions take
`Option<CryptographicSignature>`, not `CryptographicSignature`. This
allows you to display a custom message if a signature is not found, but
will emit an error if you do not check for signature presence.

    [template-aliases]
    'format_detailed_cryptographic_signature(signature)' = '''
      if(signature,
        "message if present",
        "message if missing",
      )
    '''
2025-01-15 23:29:35 +00:00
dependabot[bot]
83add0f338 github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.47.12 to 2.47.13
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](333ea3e9a4...6ae49f1b8b)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 18:48:37 +00:00
Charlie-83
ae8f6a3837 docs: fix incorrect XDG env variable 2025-01-15 18:06:02 +00:00
Yuya Nishihara
4dd554aa69 revset: parse unicode XID_CONTINUE characters as symbol
Tag and bookmark names are usually ASCII, but they occasionally include Latin
or Han characters.

This doesn't fix the serialization problem, but should mitigate #5359.
2025-01-15 07:24:10 +00:00
Yuya Nishihara
284aee25a9 revset: duplicate Rule::identifier to strict_identifier, replace some usages
The "identifier" rule will be changed to accept unicode characters. It's not
super important to reject non-ASCII string pattern prefixes or alias symbols,
but this is more consistent with templater and fileset. We can relax the rule
later if needed.
2025-01-15 07:24:10 +00:00
Yuya Nishihara
0fbf2913ac revset: extract identifier/at_op parsing tests to separate functions
I'm going to add some unicode characters there, and the current test function
is pretty big.
2025-01-15 07:24:10 +00:00
Yuya Nishihara
f9906dc403 cli: add "git.push-new-bookmarks" config knob to enable --allow-new by default
This goes against our rule that we shouldn't add config knob that changes the
command behavior, but I don't have any other idea to work around the problem.
Apparently, there are two parties, one who always wants to push new bookmarks,
and the other who mildly prefers to push&track new bookmarks explicitly.
Perhaps, for the former, creation of bookmarks means that the target branches
are marked to be pushed.

The added flag is a simple boolean. "non-tracking-only" behavior #5173 could be
implemented, but I don't want to complicate things. It's a failed attempt to
address the issue without introducing config knob.

Closes #5094
Closes #5173
2025-01-15 07:23:43 +00:00
Ilya Grigoriev
c3c2e882c9 docs config: an advanced example of --config syntax
This seems non-trivial to discover and could be useful for people
transitioning from `--config-toml`.
2025-01-15 05:25:23 +00:00
Ilya Grigoriev
ac1be9374b docs templates: document the Email type methods in more detail 2025-01-15 05:07:53 +00:00
Yuya Nishihara
d00c02fb07 cli: git: mention git.private-commits in --allow-private help text
Since the set is empty by default, --allow-private is noop unless the private
revset is configured by user. Let's clarify that.
2025-01-15 00:28:20 +00:00
Yuya Nishihara
d0f44e8350 cli: git: define default "git.private-commits" in config/misc.toml 2025-01-15 00:28:20 +00:00
Robert Jackson
9ed1fde364 docs: Expose config-schema.json in the docs site
When landed and published, this will expose the config file as:

https://jj-vcs.github.io/jj/latest/config-schema.json

Exposing the schema like that will allow users to reference in their
`~/.config/jj/config.toml` like:

```toml
"$schema" = 'https://jj-vcs.github.io/jj/latest/config-schema.json'
```

At which point any user with a configured LSP for TOML files will get
inline documentation, suggestions on valid keys, &c.
2025-01-14 13:45:36 +00:00
Yuya Nishihara
be9483b1cf cargo: remove minus dependency 2025-01-14 01:13:13 +00:00
Yuya Nishihara
fadbbcb430 ui: switch builtin pager to streampager
According to the discussion on Discord, streampager is still maintained.
It brings more dependencies, but seems more reliable in our use case. For
example, the streampager doesn't consume inputs indefinitely whereas minus
does. We can also use OS-level pipe to redirect child stderr to the pager.
2025-01-14 01:13:13 +00:00
Yuya Nishihara
728652fce3 cargo: add os_pipe dependency 2025-01-14 01:13:13 +00:00
Yuya Nishihara
f81f9f91eb cargo: add sapling-streampager dependency
The WTFPL license is added to the allow list. I've never heard about this
license, but it's basically the same as public domain according to wikipedia.
2025-01-14 01:13:13 +00:00
Yuya Nishihara
367befc1f2 cargo: add missing "windows" feature to crossterm dependency
Perhaps, the "windows" feature was enabled through "minus" or "scm-record". If I
removed "minus" in earlier revision, the Windows build of crossterm 0.27.0
failed.
2025-01-14 01:13:13 +00:00
Stephen Jennings
be5eb27f16 fix: Add enabled config for fix tools
Adds an optional `fix.tools.TOOL.enabled` config that disables use of a fix
tool (if omitted, the tool is enabled). This is useful for defining tools in
the user's configuration without enabling them for all repositories:

```toml
# ~/.jjconfig.toml
[fix.tools.rustfmt]
enabled = false
command = ["rustfmt", "--emit", "stdout"]
patterns = ["glob:'**/*.rs'"]
```

Then to enable it in a repository:

```shell
$ jj config set --repo fix.tools.rustfmt.enabled true
```
2025-01-13 17:29:49 +00:00
dependabot[bot]
c04b856bfd cargo: bump the cargo-dependencies group with 4 updates
Bumps the cargo-dependencies group with 4 updates: [crossterm](https://github.com/crossterm-rs/crossterm), [proc-macro2](https://github.com/dtolnay/proc-macro2), [thiserror](https://github.com/dtolnay/thiserror) and [unicode-width](https://github.com/unicode-rs/unicode-width).


Updates `crossterm` from 0.27.0 to 0.28.1
- [Release notes](https://github.com/crossterm-rs/crossterm/releases)
- [Changelog](https://github.com/crossterm-rs/crossterm/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossterm-rs/crossterm/commits)

Updates `proc-macro2` from 1.0.92 to 1.0.93
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.92...1.0.93)

Updates `thiserror` from 2.0.10 to 2.0.11
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.10...2.0.11)

Updates `unicode-width` from 0.1.14 to 0.2.0
- [Commits](https://github.com/unicode-rs/unicode-width/compare/v0.1.14...v0.2.0)

---
updated-dependencies:
- dependency-name: crossterm
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: unicode-width
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 16:03:30 +00:00
dependabot[bot]
1e3f1365a3 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `taiki-e/install-action` from 2.47.10 to 2.47.12
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](0779861fb4...333ea3e9a4)

Updates `github/codeql-action` from 3.28.0 to 3.28.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](48ab28a6f5...b6a472f63d)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 16:02:14 +00:00
Jakob Hellermann
8a8d8859ea cli: autocomplete: complete config values in addition to keys
Now `jj --config ui.con<TAB><TAB>` will autocomplete

```
ui.conflict-marker-style=diff
ui.conflict-marker-style=snapshot
ui.conflict-marker-style=git
```

Booleans are completed as `false`, `true`, the remaining types are left
without completions.
2025-01-13 14:43:40 +00:00
Jakob Hellermann
270b54209e cli: autocomplete: complete --config with possible configuration keys
the trailing `=` is especially nice to have because otherwise fish will
complete the suggestion and insert a space before the cursor.
With the `=`, `jj config --config ui.pagin<TAB>never` works.
2025-01-12 17:53:22 +00:00
Benjamin Tan
c0c582fcee cli: describe: use CommitRewriter::reparent
This makes it clear that the rewritten commit will have the same tree as
the original commit.
2025-01-12 14:56:26 +00:00
Yuya Nishihara
fe86bee64c cargo: bump gix to 0.69.1
The new track_empty flag is set to the default value. It was introduced by the
following commit, and I think the default behavior is good.

d3f5f27732
2025-01-12 10:01:58 +00:00
Benjamin Tan
9aaf98330b cli: git push: allow signing commits on push
This adds the `git.sign-on-push` configuration which can be used to
automatically sign unsigned commits before pushing to a Git remote.
2025-01-12 02:33:32 +00:00
Benjamin Tan
d7f064c311 cli: op log: add --reversed option
Closes #4190.
2025-01-12 01:51:25 +00:00
Benjamin Tan
73d38b5cdc cli: remove GraphLog::Edge type
`jj_lib::graph::GraphEdge` can be used directly instead.
2025-01-12 01:51:25 +00:00
Benjamin Tan
9b45c90cc5 tests: remove unnecessary format_time_range template alias
This is already set in the test configuration.
2025-01-12 01:51:25 +00:00
Yuya Nishihara
89e0a7021a cli: git: store absolute remote path in config file
The "git" CLI chdir()s to the work tree root, so paths in config file are
usually resolved relative to the workspace root. OTOH, jj doesn't modify the
process environment, so libgit2 resolves remote paths relative to cwd, not to
the workspace root. To mitigate the problem, this patch makes "jj git remote"
sub commands to store resolved path in .git/config. It would be nice if we can
reconfigure in-memory git2 remote object to use absolute paths (or set up
in-memory named remote without writing a config file), but there's no usable
API afaik.

This behavior is different from "git remote add"/"set-url". I don't know the
rationale, but these commands don't resolve relative paths, whereas "git clone"
writes resolved path to .git/config. I think it's more consistent to make all
"jj git" sub commands resolve relative paths.
2025-01-12 01:45:03 +00:00
Yuya Nishihara
20b3d02ff2 cli: git: extract absolute_git_source() as utility function 2025-01-12 01:45:03 +00:00
Waleed Khan
7b47368c24 cli: upgrade scm-record from v0.4.0 to v0.5.0 2025-01-11 18:36:12 +00:00
Benjamin Tan
f02ed0050f lib: dag_walk: fix doc comment typos
This commit fixes more typos unintentionally introduced in d9c68e08,
when renaming `jj branch` to `jj bookmark`.
2025-01-11 16:19:52 +00:00
Jakob Hellermann
f1e91cdab5 cli: complete: fix stderr during autocomplete of revsets
When running `cmd.spawn()` rust will by default inherit
the stderr of the parent, so `jj log test<TAB>`, would
print `There is no jj repo in "."` into the prompt.
2025-01-11 12:34:21 +00:00
Yuya Nishihara
98724278c5 templater: add config(name) function
This could be used in order to switch template outputs conditionally, or to
get the default push remote for example.
2025-01-11 01:40:23 +00:00
Yuya Nishihara
4e30fc7215 templater: pass RepoLoader to OperationTemplateLanguage
UserSettings will be obtained from there. If operation template supported
"self.repo() -> Repo" method, RepoLoader would be needed. It's equivalent to
Repo in CommitTemplateLanguage.
2025-01-11 01:40:23 +00:00
Yuya Nishihara
83d237a32c templater: introduce ConfigValue type, port "config list" value to it
I'm going to add config(name) accessor, which returns ConfigValue.
2025-01-11 01:40:23 +00:00
Yuya Nishihara
8e5a18b26e op diff: remove redundant "Change {change_id}" from changed commits graph
This makes the graph compact. Short change ids are usually printed as a part
of commit summary. The --no-graph output is a bit harder to parse, but we can
still discriminate entries by change ids.
2025-01-11 01:23:51 +00:00
Stephen Jennings
4ef8ad8445 docs: Explain what elided revisions are
A common question is asking how to show elided revisions. This FAQ
explains why revisions are elided and how to display them.
2025-01-11 01:18:45 +00:00
Philip Metzger
c40e5210c4 docs: fix the mkdocs admonition I recently added
I didn't check the indentation as it _needs_ to be a codeblock to render correctly.
2025-01-10 23:09:01 +00:00
Philip Metzger
f9dad76512 workflows: fix the pre-release docs workflow
It contained a check for `repo_owner == 'martinvonz'` which meant that it hasn't run in 4 weeks.
This is another great example showing why current CI systems suck, as it always was "successful" in
doing nothing with no warning whatsoever.

I also only noticed it because I wanted to check my latest doc change on the prerelease site.
2025-01-10 22:05:09 +00:00
Evan Martin
1f24d57325 docs: fix typo in in command 2025-01-10 21:21:08 +00:00
Philip Metzger
038c0bab12 docs: Make dynamic completions a bit more visible
In recent times multiple users showed up and were confused _why_ some
completions didn't show up, so make it explicit that they're opt-in
until the Clap upstream has made it so.
Also remove the link to the dynamic completions improvements issue, as it was closed
a while ago.
2025-01-10 20:59:45 +00:00
Benjamin Tan
113984d82c nix: fix error running rust commands in devshell on macOS
The `RUSTFLAGS` enviroment variable needs to be set in a `shellHook` to
allow the command to be interpreted by the shell. Otherwise, it will
just be passed as a string into the `rustc` binary:

```
cargo nextest run --workspace
error: failed to run `rustc` to learn about target-specific information

Caused by:
  process didn't exit successfully: `rustc - --crate-name ___ --print=file-names -Zthreads=0 -C 'link-arg=--ld-path=$(unset' 'DEVELOPER_DIR;' /usr/bin/xcrun --find 'ld)' -C link-arg=-ld_new --target aarch64-apple-darwin --crate-type bin --crate-type rlib --crate-type dylib --crate-type cdylib --crate-type staticlib --crate-type proc-macro --print=sysroot --print=split-debuginfo --print=crate-name --print=cfg` (exit status: 1)
  --- stderr
  error: Unrecognized option: 'find'
```
2025-01-10 20:36:41 +00:00
Benjamin Tan
9f533aeb78 cli: util: rename mangen to install-man-pages
The `jj util install-man-pages` command will generate man pages for all
`jj` subcommands and install them to the provided destination.

Closes #4157.
2025-01-10 18:54:05 +00:00
Benjamin Tan
f9371ccfd6 cargo: bump clap_mangen to 0.2.25 2025-01-10 18:54:05 +00:00
Bryce Berger
b714592952 nix: clean up flake
The build inputs were duplicated, once in packages.jujutsu and again in
devShells.default. This removes the duplication.
2025-01-10 18:15:42 +00:00
dependabot[bot]
f755e8f683 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [clap](https://github.com/clap-rs/clap) and [syn](https://github.com/dtolnay/syn).


Updates `clap` from 4.5.25 to 4.5.26
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.25...clap_complete-v4.5.26)

Updates `syn` from 2.0.95 to 2.0.96
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.95...2.0.96)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-10 16:48:27 +00:00
dependabot[bot]
792bcb634d github: bump taiki-e/install-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.47.9 to 2.47.10
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](df5dec2a2f...0779861fb4)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-10 16:47:05 +00:00
Yuya Nishihara
e124404af3 cli: remove handling of deprecated fix.tool-command config
I originally implemented this as a custom config migration rule, but the next
release is v0.26, so we can just drop support for fix.tool-command.
2025-01-10 05:45:16 +00:00
Yuya Nishihara
b97b7384bb config: allow inline table syntax in mutation and conditional scope API
This is a middle ground. An inline table can still be overwritten or deleted
because it's syntactically a value. It would be annoying if "jj config set
colors.<name> '{ .. }'" couldn't be executed more than once because the existing
item was a table.

#5255
2025-01-10 02:56:21 +00:00
Austin Seipp
1b07df143a github: don't run dependabot or gh-readonly-queue builds twice
Like `main`, we don't want to double build these branches since they will be
handled by `merge_group` already.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-10 02:44:55 +00:00
Yuya Nishihara
f14a6e6f19 git: remove PartialEq requirement from GitPushError
Non-trivial error types usually don't implement Eq/PartialEq.
2025-01-10 01:16:33 +00:00
Yuya Nishihara
ce6119a024 tests: insert insta::allow_duplicates! { .. } per snapshot
This might be a bug of insta, but new snapshots were associated with wrong
assertion blocks if allow_duplicates! { .. } contained multiple assertions.
2025-01-10 01:00:09 +00:00
Bryce Berger
84e619cae6 contributing: recommend bacon over cargo-watch
As of [1], `cargo-watch` is on minimal development and officially
recommends `bacon` as a successor.

[1]: 9f27bc1c96,
2025-01-09 19:39:54 +00:00
dependabot[bot]
cba1966a77 cargo: bump the cargo-dependencies group with 4 updates
Bumps the cargo-dependencies group with 4 updates: [clap](https://github.com/clap-rs/clap), [clap_complete](https://github.com/clap-rs/clap), [thiserror](https://github.com/dtolnay/thiserror) and [tokio](https://github.com/tokio-rs/tokio).


Updates `clap` from 4.5.24 to 4.5.25
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.24...clap_complete-v4.5.25)

Updates `clap_complete` from 4.5.41 to 4.5.42
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.41...clap_complete-v4.5.42)

Updates `thiserror` from 2.0.9 to 2.0.10
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.9...2.0.10)

Updates `tokio` from 1.42.0 to 1.43.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.42.0...tokio-1.43.0)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-09 18:28:37 +00:00
dependabot[bot]
d913ec5173 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `actions/upload-artifact` from 4.5.0 to 4.6.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](6f51ac03b9...65c4c4a1dd)

Updates `taiki-e/install-action` from 2.47.8 to 2.47.9
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](08d473f7b2...df5dec2a2f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-09 17:59:57 +00:00
Jakob Hellermann
80c4222399 cli: oplog: add builtin_op_log_oneline template
Motivation:
* one-line logs can be more readable and fit more content into the screen
* consistency with `jj log -T builtin_log_oneline`
2025-01-09 15:58:40 +00:00
Yuya Nishihara
720c903b99 git: port s/bookmark/branch/ renames to config migration rules
These two are easy. "fix.tool-command" will be processed by a custom migration
rule of Box<dyn Fn(&mut ConfigLayer) -> ..> type.
2025-01-09 07:23:08 +00:00
Yuya Nishihara
30fb1078b2 cli: integrate config migration
The list of migration rules is managed by CliRunner. I don't know if that's
needed, but in theory, an extension may insert migration rules as well as
default config layers.

Migration could be handled by ConfigEnv::resolve_config(), but it seemed rather
complicated because Vec<ConfigMigrationRule> cannot be cloned, and the scope of
these variables are a bit different.
2025-01-09 07:23:08 +00:00
Yuya Nishihara
98eaadf280 config: add simple migration logic to update deprecated variables
Instead of resolving deprecated variables by callers or config object, this
patch adds a function that rewrites deprecated variables globally (and emits
warnings accordingly.)  It's simpler because StackedConfig doesn't have to deal
with renamed variables internally. OTOH, warnings will be issued no matter if
variables are used or not, which might be a bit noisy.

Maybe we can also add "jj config migrate" command that updates user and repo
configs.
2025-01-09 07:23:08 +00:00
Yuya Nishihara
3fbf1249f9 files: use Merge constructor to interleave conflicting diff parts
Since we build a temporary Vec<_> anyway, we can just rely on the Merge API.
2025-01-09 06:54:09 +00:00
Yuya Nishihara
3d4b5e45e7 merge: inline trivial_merge_inner()
All callers pass &[T] now.
2025-01-09 06:54:09 +00:00
Martin von Zweigbergk
8a3ae86b38 merge: update test values to be simpler and more clearly exhaustive 2025-01-09 00:07:05 +00:00
Martin von Zweigbergk
71494299bb merge: make trivial_merge() take a single slice of interleaved terms
I have come to think of conflicts more and more as one positive
followed by a series of diffs and less as two separate sets of adds
and removes. We've already changed the implmentation of `Merge` to be
a single list of interleaved positive and negative terms. I think it's
simpler to use the same format in `trivial_merge()` too.

To keep the diff simple, I just preserved the test case values and
order as is for now even if that means that some of them are now
unintuitive. I'll clean that up in the next commit.
2025-01-09 00:07:05 +00:00
Scott Taylor
7df0f16fe0 resolve: try to resolve all conflicted files in fileset
If many files are conflicted, it would be nice to be able to resolve all
conflicts at once without having to run `jj resolve` multiple times.
This is especially nice for merge tools which try to automatically
resolve conflicts without user input, but it is also good for regular
merge editors like VS Code.

This change makes the behavior of `jj resolve` more consistent with
other commands which accept filesets since it will use the entire
fileset instead of picking an arbitrary file from the fileset.

Since we don't support passing directories to merge tools yet, the
current implementation just calls the merge tool repeatedly in a loop
until every file is resolved, or until an error occurs. If an error
occurs after successfully resolving at least one file, the transaction
is committed with all of the successful changes before returning the
error. This means the user can just close the editor at any point to
cancel resolution on all remaining files.
2025-01-08 23:52:21 +00:00
Scott Taylor
5f77edf503 merge_tools: extract MergeToolFile struct
I'm going to change the merge tools to accept multiple files, and this
will make it easier to pass all the required data about each file.
2025-01-08 23:52:21 +00:00
Austin Seipp
6fec006405 github: don't run build workflows twice on push to main
The new `merge_group` event will already attach all the CI events from the
various workflows where it applies. If the merge queue is drained and merges to
`main`, having `push` on these workflows will cause them to be run again, doubling
the number of CI checks. We don't need `push` anymore, basically.

Also, because checks can be cancelled now, the current double running can
probably lead to an awkward setup like:

- Merge Queue merges A to main, starts `push` workflows
- Merge Queue then merges B and C to main, starts `push` workflows
- `A`'s workflows get cancelled if it's not yet done
- This makes it look like `A` has failing tests somehow, but it doesn't

Therefore, just removing the double builds is the first step to cut down on the
CI times for our repo.

We also run the build workflows on all pushes to NOT main, because that will
help people who want to run CI before opening an actual PR.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-08 22:54:14 +00:00
Austin Seipp
a45eb0b0d5 github: don't cancel concurrent builds on main
This will make sure the build on the main listing still looks clean, instead of
previous builds being cancelled and making it look like things failed.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-08 22:37:25 +00:00
Austin Seipp
6083135f19 lib: remove hack to migrate old git remotes
Deletes another vestigial trace of git in the lib crate.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-08 22:02:21 +00:00
Austin Seipp
e15429ba91 cli: make git support optional
There are some experiments to try and compile `jj` to WebAssembly, so that we
might be able to do things like interactive web tutorials. One step for that
is making `git` support in `jj-cli` optional, because we can stub it out for
something more appropriate and it's otherwise a lot of porting annoyance for
both gitoxide and libgit2.

(On top of that, it might be a useful build configuration for other experiments
of mine where removing the need for the large libgit2 depchain is useful.)

As part of this, we need to mark `jj-lib` as having `default-features = false`
in the workspace dependency configuration; otherwise, the default behavior
for Cargo is to compile with all its default features, i.e. with git support
enabled, ignoring the `jj-cli` features clauses.

Other than that, it is fairly straightforward junk; it largely just sprinkles
some `#[cfg]` around liberally in order to make things work. It also adjusts the
CI pipeline so this is tested there, too, so we can progressively clean it up.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-08 22:02:21 +00:00
Austin Seipp
992066c60c lib: remove use of zstd
`zstd` is only used to write files in the native backend right now. For now,
jettison it, to unbundle some C code that we don't really need.

(Ideally, a future compression library would be pure Rust, but we'll cross that
bridge when we get to it...)

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-08 22:02:21 +00:00
dependabot[bot]
02a758964a cargo: bump rustix in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [rustix](https://github.com/bytecodealliance/rustix).


Updates `rustix` from 0.38.42 to 0.38.43
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.42...v0.38.43)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-08 18:49:07 +00:00
Bryce Berger
b1b2c62c3e diff: add merge-tools.*.diff-expected-exit-codes
Certain tools (`diff`, `delta`) exit with code 1 to indicate there was
a difference. This allows selectively suppressing the "Tool exited with
... status" warning from jj when generating a diff.

example:
```toml
[merge.tools.delta]
diff-expected-exit-codes = [0, 1]
```
2025-01-08 08:03:59 +00:00
Yuya Nishihara
d001291a27 Back out "config: merge and print inline tables as values"
This backs out commit 0de36918e4c020e0e54816f29c47cb57cc9cfbf5. Documentation,
tests, and comments are updated accordingly. I also add ConfigTableLike type
alias as we decided to abstract table-like items away.

Closes #5255
2025-01-08 05:24:55 +00:00
Austin Seipp
664ba1a997 github: install and use nextest for GHA
Our workload is pretty CPU bound, but `nextest` does seem to make a small ~5s
difference for me. Let's see how it works on the CI runners.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-08 03:51:05 +00:00
Austin Seipp
fe9ea505b6 github: add merge_group event to several workflows
These are the workflows that run on PRs, so they need to have the `merge_group`
event added to them if we want to use the merge queue.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-07 21:26:57 -06:00
Austin Seipp
a621d8cf3f docs: improve and convert library design diagram to SVG
It was lovingly hand drawn by yours truly in Excalidraw.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-07 20:32:56 -06:00
Yuya Nishihara
e6c0692655 cli: warn if command matches with aliases inserted by --config* arguments
Closes #5282
2025-01-08 09:40:31 +09:00
Yuya Nishihara
49999a7507 local_working_copy: on snapshot, ignore submodule in ignored directories
Fixes #5246
2025-01-08 09:39:59 +09:00
dependabot[bot]
4a863789c1 cargo: bump the cargo-dependencies group across 1 directory with 4 updates
Bumps the cargo-dependencies group with 4 updates in the / directory: [async-trait](https://github.com/dtolnay/async-trait), [clap](https://github.com/clap-rs/clap), [clap_complete](https://github.com/clap-rs/clap) and [serde_json](https://github.com/serde-rs/json).


Updates `async-trait` from 0.1.84 to 0.1.85
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.84...0.1.85)

Updates `clap` from 4.5.23 to 4.5.24
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.23...clap_complete-v4.5.24)

Updates `clap_complete` from 4.5.40 to 4.5.41
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.41)

Updates `serde_json` from 1.0.134 to 1.0.135
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.134...v1.0.135)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-07 18:58:52 +00:00
Martin von Zweigbergk
4a7d9891e6 rewrite: back out 9d4a973
I think the test case added in the previous commit shows a clear case
for why commit 9d4a973 was a bad idea.
2025-01-07 09:08:46 -08:00
Martin von Zweigbergk
ada946db62 rewrite: add another test of rebasing with same change on both sides 2025-01-07 09:08:46 -08:00
Austin Seipp
252f6d8543 github: migrate all actions to ubuntu-24.04
This gets rid of an annoying warning in the Actions tab, but we already rely on
this for the build-binaries workflow, and it's probably better to be explicit
about this anyway.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-07 10:30:29 -06:00
Scott Taylor
e6a51d6637 git: add dummy conflict to index if necessary
If the parent tree contains conflicts, we want the index to also contain
a conflict to ensure that the use can't accidentally commit conflict
markers using `git commit`. Since we can't represent conflicts with more
than 2 sides in the Git index, we need to add a dummy conflict in this
case. We use ".jj-do-not-resolve-this-conflict" as the dummy conflict to
indicate to the user that they should not attempt to resolve this
conflict using Git.
2025-01-06 19:17:51 -06:00
Scott Taylor
42b390bbc4 git: use merged parent tree for git index
Instead of setting the index to match the tree of HEAD, we now set the
index to the merged parent tree of the working copy commit. This means
that if you edit a merge commit, it will make the Git index look like it
would in the middle of a `git merge` operation (with all of the
successfully-merged files staged in the index).

If there are any 2-sided conflicts in the merged parent tree, then they
will be added to the index as conflicts. Since Git doesn't support
conflicts with more than 2 sides, many-sided conflicts are staged as the
first side of the conflict. The following commit will improve this.
2025-01-06 19:17:51 -06:00
Scott Taylor
9cc8b35251 git: use gix instead of git2 to update index
This will give us more fine-grained control over what files we put in
the index, allowing us to create conflicted index states. We also still
need to use git2 to clean up the merge/rebase state, since gix doesn't
have any function for this currently.
2025-01-06 19:17:51 -06:00
Scott Taylor
8e3ec9c58c git: add tests for index in colocated repo
These tests should still pass after we switch to gix for resetting the
index, so we need to make sure they don't rely on the cached index from
the `git2::Repository` instance.
2025-01-06 19:17:51 -06:00
Scott Taylor
3f72604832 git: make update_git_head accept an expected ref
This change will allow `update_git_head` to be used in `reset_head`.
2025-01-06 19:17:51 -06:00
dependabot[bot]
d859a910e1 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [insta](https://github.com/mitsuhiko/insta) and [syn](https://github.com/dtolnay/syn).


Updates `insta` from 1.41.1 to 1.42.0
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.41.1...1.42.0)

Updates `syn` from 2.0.94 to 2.0.95
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.94...2.0.95)

---
updated-dependencies:
- dependency-name: insta
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 13:24:54 -06:00
Yuya Nishihara
b8653989c1 tests: add convenient method to initialize TestWorkspace with test settings
Functions are renamed, and their arguments are reordered to be consistent with
the TestRepo API.
2025-01-06 22:37:33 +09:00
Yuya Nishihara
127f7e23ac examples: use settings_for_new_workspace() when initializing workspace
Spotted while replacing callers of CommandHelper::settings().
2025-01-06 21:15:28 +09:00
Yuya Nishihara
1f6e207e25 cli: get settings from workspace where makes sense
There should be no difference, but it's more consistent that all workspace/repo
commands use workspace_command.settings(), etc. than command.settings().
2025-01-06 21:15:28 +09:00
Yuya Nishihara
6c14ccd89d cli: resolve settings for newly initialized/cloned workspace
Fixes #5144
2025-01-06 10:39:48 +09:00
Yuya Nishihara
56bd4765f5 cli: get settings from repo or workspace
This ensures that WorkspaceCommandHelper created for initialized/cloned repo
refers to the settings object for that repo, not the settings loaded for the
cwd repo.

I also removed WorkspaceCommandEnvironment::settings() because it doesn't own
a workspace object. It's implementation detail that the env object holds a
copy of workspace.settings(), and the caller should be accessible to the
workspace object.
2025-01-06 10:39:48 +09:00
Yuya Nishihara
393f3acb03 workspace: add settings() getter 2025-01-06 10:39:48 +09:00
Yuya Nishihara
556ef26608 cli: make commands sub modules private
We've removed most of the deprecated commands.
2025-01-06 10:16:15 +09:00
Christian Stoitner
bcfa2b174b added changelog entry for showing untracked files in jj status 2025-01-05 18:53:55 +01:00
Christian Stoitner
ae91f2265f status: show untracked files 2025-01-05 17:43:59 +01:00
Christian Stoitner
3aa97099d6 cli: plumbing to make SnapshotStats available to cmd_status 2025-01-05 17:43:59 +01:00
Christian Stoitner
cb28685901 working_copy: added UntrackedReason::FileNotAutoTracked for files not tracked because of snapshot.auto-track 2025-01-05 17:43:59 +01:00
Yuya Nishihara
2cb3ee5260 cli: restore: add --interactive flag
As Martin suggested, this is the reverse operation of "jj diffedit", and
supports fileset arguments.

https://github.com/jj-vcs/jj/issues/3012#issuecomment-1937353458

Closes #3012
2025-01-05 10:14:29 +09:00
Yuya Nishihara
0d022f1202 cli: fix interactive diff selection to not retain unmatched files
This patch doesn't fix DiffEditor::edit() API, which is fundamentally broken
if matcher argument is specified. I'm not sure if the builtin behavior is
correct or not. Suppose we add "jj diffedit FILESETS..", it would probably make
sense to leave unmatched paths unmodified because it is the command to edit the
destination (or right) tree. This is the edit_diff_external() behavior.

Fixes #5252
2025-01-04 22:21:37 +09:00
Anton Bulakh
c99c97c646 sign: Add templater methods to show signature info
Disclaimer: this is the work of @necauqua and @julienvincent (see
#3141). I simply materialized the changes by rebasing them on latest
`main` and making the necessary adjustments to pass CI.

---

I had to fix an issue in `TestSignatureBackend::sign()`.

The following test was failing:
```
---- test_signature_templates::test_signature_templates stdout ----
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Snapshot: signature_templates
Source: cli/tests/test_signature_templates.rs:28
────────────────────────────────────────────────────────────────────────────────
Expression: stdout
────────────────────────────────────────────────────────────────────────────────
-old snapshot
+new results
────────────┬───────────────────────────────────────────────────────────────────
    0     0 │ @  Commit ID: 05ac066d05701071af20e77506a0f2195194cbc9
    1     1 │ │  Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu
    2     2 │ │  Author: Test User <test.user@example.com> (2001-02-03 08:05:07)
    3     3 │ │  Committer: Test User <test.user@example.com> (2001-02-03 08:05:07)
    4       │-│  Signature: Good test signature
          4 │+│  Signature: Bad test signature
    5     5 │ │
    6     6 │ │      (no description set)
    7     7 │ │
    8     8 │ ◆  Commit ID: 0000000000000000000000000000000000000000
────────────┴───────────────────────────────────────────────────────────────────
```

Print debugging revealed that the signature was bad, because of a
missing trailing `\n` in `TestSignatureBackend::sign()`.

```diff
diff --git a/lib/src/test_signing_backend.rs b/lib/src/test_signing_backend.rs
index d47fef1086..0ba249e358 100644
--- a/lib/src/test_signing_backend.rs
+++ b/lib/src/test_signing_backend.rs
@@ -59,6 +59,8 @@
         let key = (!key.is_empty()).then_some(std::str::from_utf8(key).unwrap().to_owned());

         let sig = self.sign(data, key.as_deref())?;
+        dbg!(&std::str::from_utf8(&signature).unwrap());
+        dbg!(&std::str::from_utf8(&sig).unwrap());
         if sig == signature {
             Ok(Verification::new(
                 SigStatus::Good,
```

```
[lib/src/test_signing_backend.rs:62:9] &std::str::from_utf8(&signature).unwrap() = \"--- JJ-TEST-SIGNATURE ---\\nKEY: \\n5300977ff3ecda4555bd86d383b070afac7b7459c07f762af918943975394a8261d244629e430c8554258904f16dd9c18d737f8969f2e7d849246db0d93cc004\\n\"
[lib/src/test_signing_backend.rs:63:9] &std::str::from_utf8(&sig).unwrap() = \"--- JJ-TEST-SIGNATURE ---\\nKEY: \\n5300977ff3ecda4555bd86d383b070afac7b7459c07f762af918943975394a8261d244629e430c8554258904f16dd9c18d737f8969f2e7d849246db0d93cc004\"
```

Thankfully, @yuja pointed out that libgit2 appends a trailing newline
(see bfb7613d5d192d3c4dc533afa4f2ff0d6b9016c5).

Co-authored-by: necauqua <him@necauq.ua>
Co-authored-by: julienvincent <m@julienvincent.io>
2025-01-04 13:24:08 +01:00
pylbrecht
87b38b3073 sign: provide display in TestSigningBackend
We need to provide a value for the `display` attribute to assert against
in upcoming tests, which verify signature templates.
2025-01-04 13:24:08 +01:00
pylbrecht
638f123459 sign: move TestSigningBackend to lib
We need to make `TestSigningBackend` available for cli tests, as we will
add cli tests for signing related functionality (templates for
displaying commit signatures, `jj sign`) in upcoming commits.

Co-authored-by: julienvincent <m@julienvincent.io>
2025-01-04 13:24:08 +01:00
Yuya Nishihara
9aff586655 config: fall back to $USER if username couldn't be obtained by libc
If jj is compiled against musl libc, not all name services are available, and
getpwuid() can return null even if the system is configured properly. That's
the problem reported as #5231. Suppose operation.username exists mainly for
logging/tracing purposes, it should be better to include something less reliable
than leaving the field empty.

This patch also removes TODO comment about empty hostname/username. It's
unlikely that the hostname is invalid (as that would cause panic on older jj
versions), and $USER would probably be set on Unix.
2025-01-04 17:54:28 +09:00
Yuya Nishihara
fcac7ed39c config: load system host/user name by CLI and insert as env-base layer
Since the default now falls back to "", we can simply override the default by
CLI. We don't have to touch the environment in jj-lib.
2025-01-04 17:54:28 +09:00
Yuya Nishihara
5842e58db6 cli: add test that runs basic jj commands with minimal configuration 2025-01-04 17:54:28 +09:00
Benjamin Tan
6ddc5a7b30 cli: split: remove deprecated --siblings options
This has been replaced by the `--parallelize` option in a9953b3.
2025-01-04 15:43:18 +08:00
Benjamin Tan
62c1c48c7a cli: log, op log, evolog: remove deprecated -l short alias for --limit
This removes the `-l` short alias for `--limit` for `jj log`,
`jj op log` and `jj evolog` initially deprecated in 72438fc.
2025-01-04 15:43:18 +08:00
Benjamin Tan
947281687f cli: remove deprecated jj file commands
The following deprecated commands have been removed for their `jj files`
alternatives:

- `jj cat` was deprecated for `jj file show` in 47bd6f4 and e6c2108.
- `jj chmod` was deprecated for `jj file chmod` in 47bd6f4.
- `jj files` was deprecated for `jj file list` in 5d307e6.
2025-01-04 15:43:18 +08:00
blinry
1ddfc59ee9 docs: Use "branch" consistently when talking about Git's branches
Some places used "bookmark" instead, which makes it harder to
differentiate between jj's and Git's differing concepts.
2025-01-03 10:54:00 -06:00
dependabot[bot]
337663c312 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [async-trait](https://github.com/dtolnay/async-trait), [bstr](https://github.com/BurntSushi/bstr) and [tempfile](https://github.com/Stebalien/tempfile).


Updates `async-trait` from 0.1.83 to 0.1.84
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.84)

Updates `bstr` from 1.11.2 to 1.11.3
- [Commits](https://github.com/BurntSushi/bstr/compare/1.11.2...1.11.3)

Updates `tempfile` from 3.14.0 to 3.15.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.14.0...v3.15.0)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: bstr
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-03 10:15:42 -06:00
Austin Seipp
66151f0888 cli: drop support for jj init --{git, git_repo}
These were deprecated early last year in favor of `jj git init`.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-03 10:15:01 -06:00
Yuya Nishihara
42d7beaf2c cli: config: allow comma in bare TOML string
Fixes #5233
2025-01-03 21:48:41 +09:00
Yuya Nishihara
641ec52540 cli: replace run_ui_editor() and edit_temp_file() with editor type 2025-01-03 10:23:58 +09:00
Yuya Nishihara
e306f84765 cli: add "ui.editor" type that captures configuration
The idea is the same as diff_editor()/selector() API. This object will be passed
in to edit_*description() functions in place of (repo_path, settings) pair.

"ui.editor" isn't specific to editing commit descriptions, but it's mainly used
for that purpose. So I put the wrapper type in description_util.rs.
2025-01-03 10:23:58 +09:00
Yuya Nishihara
e344c21a28 cli: use insta snapshot to test editor error messages 2025-01-03 10:23:58 +09:00
JDSeiler
d5b0aa20cb evolog: Implement --reversed flag
Adds some additional helper functions for converting between jj_lib's
GraphEdge and the graphlog's Edge type.
2025-01-02 20:21:59 -05:00
Yuya Nishihara
c7dd28a292 settings: fall back to empty op hostname/username if default can't be obtained
It's not super important that operation log has a valid user/host name. Let's
allow invalid system configuration. In jj 0.24.0, invalid hostname was panic,
and invalid username was mapped to "Unknown".

Fixes #5231
2025-01-03 10:20:33 +09:00
Samuel Tardieu
39bdd5eb3f perf: use .next_back() to get the last component
Using `.last()` needs to go through all components first instead of
splitting only the last one.
2025-01-02 23:24:04 +01:00
Samuel Tardieu
9048e80fed clippy: enable useless_conversion lint 2025-01-02 23:23:19 +01:00
Samuel Tardieu
373b155bf0 style: remove useless type conversion 2025-01-02 23:23:19 +01:00
Austin Seipp
c9b6ba7b57 nix: run nix flake update
Keeping the wheels greased.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-02 16:14:38 -06:00
Bryce Berger
cc015310ea nix: add name to devshell
This changes the `$name` environment variable from `nix-shell-env` to
`jujutsu-env` when inside the nix-provided devshell.
2025-01-02 13:41:54 -06:00
Bryce Berger
2281450359 nix: use dynamic completions in installPhase
Have been used in nixpkgs since the update to 0.24
2025-01-02 14:37:53 -05:00
Ollivier Robert
602b79f7fd Add a paragraph on how to install jj completions into Powershell.
Change USER into $HOME.
2025-01-02 10:52:55 -06:00
dependabot[bot]
c522b5d4dd cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [bstr](https://github.com/BurntSushi/bstr), [clap_complete_nushell](https://github.com/clap-rs/clap) and [syn](https://github.com/dtolnay/syn).


Updates `bstr` from 1.11.1 to 1.11.2
- [Commits](https://github.com/BurntSushi/bstr/compare/1.11.1...1.11.2)

Updates `clap_complete_nushell` from 4.5.4 to 4.5.5
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete_nushell-v4.5.4...clap_complete_nushell-v4.5.5)

Updates `syn` from 2.0.93 to 2.0.94
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.93...2.0.94)

---
updated-dependencies:
- dependency-name: bstr
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap_complete_nushell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-03 00:52:18 +08:00
Yuya Nishihara
2304db6abf cli: squash: use indoc::formatdoc! to indent formatted instructions nicely 2025-01-02 15:07:09 +09:00
Yuya Nishihara
5adeb6337d cli: squash: inline move_diff() to address "too many arguments" linter warning 2025-01-02 15:07:09 +09:00
Yuya Nishihara
3cdd5717ef cli: squash: extract first half of move_diff() to separate function
I'll inline move_diff() instead. I think the current implementation is organized
this way just because we had "jj move" command.
2025-01-02 15:07:09 +09:00
Yuya Nishihara
d38d6f69aa cli: split sparse sub commands into modules 2025-01-02 15:07:00 +09:00
Yuya Nishihara
cdbe4dfc67 signing: cap number of cached verification results
I didn't notice Signer had a cache when I made the change db6a58d31551 "store:
switch in-memory cache to LRU-based HashMap to cap memory usage."
2025-01-02 15:06:52 +09:00
Austin Seipp
041c4fecb7 release: 0.25.0
Signed-off-by: Austin Seipp <aseipp@pobox.com>
2025-01-01 21:56:15 -06:00
umnikos
ecbee49bf4 docs: fix typo in builtin_immutable_heads() description 2024-12-31 19:24:36 -06:00
Waleed Khan
0651e5a468 ci: check Rust documentation 2024-12-31 17:11:28 -06:00
Waleed Khan
a28edb0d28 docs: fix cargo doc warnings 2024-12-31 17:11:28 -06:00
Waleed Khan
6017c27626 cleanup: remove extra whitespace from build.yml 2024-12-31 17:11:28 -06:00
Waleed Khan
34ec85edbd docs: revert "docs: update installation docs to say that cmake is needed"
This reverts commit b7ba3fc0be734cfa39bb7d6cfc50ed67eee15dce.

As per discussion in https://github.com/martinvonz/jj/pull/5047, I would like to make it so that the default build configuration doesn't need `cmake`.
2024-12-31 17:07:52 -06:00
Waleed Khan
c7b677b86d build: switch back to gix/max-performance-safe feature by default
Using `gix/max-performance` requires `cmake` as a build-time dependency, which could be a significant barrier for contributors (including existing ones, like me, who already work on jj but didn't have `cmake` installed thus far).

This commit switches back to using `gix/max-performance-safe`, which doesn't have the `cmake` dependency, and adds `gix/max-performance` behind the `gix-max-performance` feature for `jj-lib`.

It also adds `gix-max-performance` to the `packaging` feature group, since I'm assuming that packagers will want maximum performance, and are more likely to have `cmake` at hand.

Tested with

```
$ cargo build --workspace
$ cargo build --workspace --features packaging
```

(and the `--features packaging` build failed until I installed `cmake`)
2024-12-31 17:07:52 -06:00
Yuya Nishihara
cff73841ed repo: remove &UserSettings argument from new/rewrite_commit(), use self.settings 2024-12-31 10:51:57 +09:00
Yuya Nishihara
14b52205fb repo: remove &UserSettings argument from start_transaction(), use self.settings 2024-12-31 10:51:57 +09:00
Yuya Nishihara
57806ee8c2 test_commit_builder: reload repo per settings
I'm going to remove the settings argument from start_transaction(),
new_commit(), and rewrite_commit(), and these functions will use the settings
passed in to the constructor.
2024-12-31 10:51:57 +09:00
Yuya Nishihara
475ac4e86a repo: keep copy of UserSettings, remove RepoSettings
The idea is that ReadonlyRepo/MutableRepo hold UserSettings to accomplish
their operations (such as transaction commit.) Later patches will remove
the "settings" argument from a couple of repo methods, which will greatly
reduce the amount of settings refs we had to pass around.

The current UserSettings is set up for the repo, so we wouldn't need a separate
RepoSettings type.
2024-12-31 10:51:57 +09:00
Yuya Nishihara
feb032ee34 settings: make UserSettings cheap to clone
I think the issue #5144 can be fixed cleanly if UserSettings object is
loaded/resolved per repo/workspace, not per CommandHelper.

This change makes it clear that UserSettings is supposed to be shared
immutably.
2024-12-31 10:51:57 +09:00
dependabot[bot]
dbd0174ee8 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [glob](https://github.com/rust-lang/glob), [serde](https://github.com/serde-rs/serde) and [syn](https://github.com/dtolnay/syn).


Updates `glob` from 0.3.1 to 0.3.2
- [Release notes](https://github.com/rust-lang/glob/releases)
- [Changelog](https://github.com/rust-lang/glob/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/glob/compare/0.3.1...v0.3.2)

Updates `serde` from 1.0.216 to 1.0.217
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.216...v1.0.217)

Updates `syn` from 2.0.92 to 2.0.93
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.92...2.0.93)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 16:15:37 +00:00
Yuya Nishihara
4ed3ce0d2b cli: inline CommandHelper::load_template_aliases()
There are no external callers, and WorkspaceCommandEnvironment might have to
keep a separate copy of UserSettings in order to address #5144.
2024-12-30 23:39:18 +09:00
Yuya Nishihara
5515d2fb3c cli: remove unneeded command.clone() from snapshot functions
There aren't mutable borrow issues anymore.
2024-12-30 23:39:18 +09:00
Keane Nguyen
7402362c80 README: replace outdated claim about lacking git blame support 2024-12-29 20:47:54 +01:00
Yuya Nishihara
43ac4faac4 config: preserve key formatting on set_value()
I just noticed toml_edit::Table has a format-preserving insert() API. I think
it's better to retain the user-specified quoting style.
2024-12-29 09:57:04 +09:00
Yuya Nishihara
10783f9e70 config: don't leave empty tables visible by set_value()
It's unlikely that user would want to define all intermediate tables by
"jj config set foo.bar.baz ..".
2024-12-29 09:57:04 +09:00
Yuya Nishihara
4af39e2038 cli: config list: mark values overridden by table or parent value
This fixes the output of "jj config list --include-overridden --include-defaults
ui.pager" and ".. ui.pager.command". The default ui.pager.* should be marked as
overridden by ui.pager if set in user configuration.
2024-12-28 10:28:43 +09:00
Yuya Nishihara
a001fe287a cli: config list: pre-filter variables to be printed
This is simpler, and it's nice that "No matching config" warning is printed
without spawning pager.
2024-12-28 10:28:43 +09:00
Benjamin Tan
8d560748c7 cli: help: fix typos 2024-12-28 04:08:52 +08:00
Philip Metzger
2f14ad25a7 docs: Update the News section of the README
It has seriously gone out of date, so update it for 2025 and mention the new `jj-vcs` org.
The suggestion came from melutovich in the #jujutsu Libera IRC chat.
2024-12-27 20:29:22 +01:00
dependabot[bot]
2a7cc98df7 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [quote](https://github.com/dtolnay/quote) and [syn](https://github.com/dtolnay/syn).


Updates `quote` from 1.0.37 to 1.0.38
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.37...1.0.38)

Updates `syn` from 2.0.91 to 2.0.92
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.91...2.0.92)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-27 15:23:00 +00:00
Scott Taylor
0dbcdf7233 docs: explain long conflict markers
Some checks failed
binaries / Build binary artifacts (linux-aarch64-gnu, ubuntu-24.04, aarch64-unknown-linux-gnu) (push) Has been cancelled
binaries / Build binary artifacts (linux-aarch64-musl, ubuntu-24.04, aarch64-unknown-linux-musl) (push) Has been cancelled
binaries / Build binary artifacts (linux-x86_64-gnu, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Has been cancelled
binaries / Build binary artifacts (linux-x86_64-musl, ubuntu-24.04, x86_64-unknown-linux-musl) (push) Has been cancelled
Codespell / Codespell (push) Has been cancelled
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
binaries / Build binary artifacts (macos-aarch64, macos-14, aarch64-apple-darwin) (push) Has been cancelled
binaries / Build binary artifacts (macos-x86_64, macos-13, x86_64-apple-darwin) (push) Has been cancelled
binaries / Build binary artifacts (win-x86_64, windows-2022, x86_64-pc-windows-msvc) (push) Has been cancelled
nix / flake check (macos-14) (push) Has been cancelled
nix / flake check (ubuntu-latest) (push) Has been cancelled
build / Clippy check (push) Has been cancelled
build / build (, macos-13) (push) Has been cancelled
build / build (, macos-14) (push) Has been cancelled
build / build (, ubuntu-latest) (push) Has been cancelled
build / build (, windows-latest) (push) Has been cancelled
build / build (--all-features, ubuntu-latest) (push) Has been cancelled
build / Build jj-lib without Git support (push) Has been cancelled
build / Check protos (push) Has been cancelled
build / Check formatting (push) Has been cancelled
build / Check that MkDocs can build the docs (push) Has been cancelled
build / Check that MkDocs can build the docs with latest Python and uv (push) Has been cancelled
build / cargo-deny (advisories) (push) Has been cancelled
build / cargo-deny (bans licenses sources) (push) Has been cancelled
2024-12-25 20:34:40 -06:00
Yuya Nishihara
e5361c6cc0 fsmonitor: move default settings to config/misc.toml 2024-12-25 10:44:37 +09:00
Yuya Nishihara
9b60a9df90 signing: move default backend settings to config/misc.toml 2024-12-25 10:44:37 +09:00
Yuya Nishihara
e4a350fcaa config: extract jj-lib's default values to embedded TOML file
It's nice that "jj config list --include-defaults" can show these default
values.

I just copied the jj-cli directory structure, but it's unlikely we'll add more
config/*.toml files.
2024-12-25 10:44:37 +09:00
dependabot[bot]
5c12c2fc8b github: bump astral-sh/setup-uv in the github-dependencies group
Bumps the github-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 5.0.1 to 5.1.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](180f8b4439...887a942a15)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-24 12:54:02 -06:00
Bryce Berger
1d3c3b8ab2 describe: ignore everything below ignore-rest line
This implements "scissor" lines. For example:

    this text is included in the commit message
    JJ: ignore-rest
    this text is not, and is encouraged to be rendered as a diff
    JJ: ignore-rest
    this text is *still not* included in the commit message

When editing multiple commit messages, the `JJ: describe {}` lines
are parsed before the description is cleaned up. That means that the
following will correctly add descriptions to multiple commits:

    JJ: describe aaaaaaaaaaaa
    this text is included in the first commit message
    JJ: ignore-rest
    scissored...
    
    JJ: describe bbbbbbbbbbbb
    this text is included in the first commit message
    JJ: ignore-rest
    scissored...
2024-12-23 16:31:55 -07:00
dependabot[bot]
fc59d7957f github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.9 to 3.28.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](df409f7d92...48ab28a6f5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 13:18:40 -06:00
dependabot[bot]
d56d0857fc cargo: bump the cargo-dependencies group with 4 updates
Bumps the cargo-dependencies group with 4 updates: [anyhow](https://github.com/dtolnay/anyhow), [serde_json](https://github.com/serde-rs/json), [syn](https://github.com/dtolnay/syn) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `anyhow` from 1.0.94 to 1.0.95
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.94...1.0.95)

Updates `serde_json` from 1.0.133 to 1.0.134
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.133...v1.0.134)

Updates `syn` from 2.0.90 to 2.0.91
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.90...2.0.91)

Updates `thiserror` from 2.0.8 to 2.0.9
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.8...2.0.9)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 13:18:29 -06:00
Scott Taylor
542d09c6a9 merge_tools: add "$marker_length" variable
Git supports passing the conflict marker length to merge drivers using
"%L". It would be useful if we also had a way to pass the marker length
to merge tools, since it would allow Git merge drivers to be used with
`jj resolve` in more cases. Without this variable, any merge tool that
parses or generates conflict markers could fail on files which require
conflict markers longer than 7 characters.

https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver
2024-12-23 08:42:10 -06:00
Yuya Nishihara
6374dd0cfe cli: abandon, describe: parse -rREV option properly
I often do "jj log -rREV" to preview the commits to abandon, and it's annoying
that I have to remove -r or insert space to "jj abandon ..".

The implementation is basically the same as b0c7d0a7e262.
2024-12-23 22:58:06 +09:00
Yuya Nishihara
5bd669e892 settings: propagate configuration error of commit and operation parameters
Note that infallible version of whoami::username() would return "Unknown" on
error. I just made it error out, but it's also an option to fall back to an
empty string.
2024-12-23 22:57:57 +09:00
Yuya Nishihara
4a69d0178c settings: cache commit and operation parameters by UserSettings
This helps propagate configuration error. RevsetParseContext is also updated
because it was easier to pass &str in to it.
2024-12-23 22:57:57 +09:00
Yuya Nishihara
d91e355674 cli: git clone: convert local Git remote path to slash-separated path
Since source.contains(':') can't be used for non-local path detection on
Windows, we now use gix::url for parsing. It might be stricter, but I assume it
would be more reliable.

Closes #4188
2024-12-23 09:40:52 +09:00
Yuya Nishihara
6f00c565b2 graph: inline ReverseGraphIterator to callers 2024-12-23 09:28:03 +09:00
Yuya Nishihara
ec853027be graph: make reverse_graph() return nodes in "reversed" order 2024-12-23 09:28:03 +09:00
Yuya Nishihara
d6b84da382 graph: extract function that reverses graph edges
ReverseGraphIterator will be inlined. It doesn't make sense the iterator yields
Item = Result<..> whereas all possible errors are confined by constructor.
2024-12-23 09:28:03 +09:00
Scott Taylor
7bf31c1557 merge_tools: preserve executable bit on resolve 2024-12-22 10:12:22 -06:00
Scott Taylor
afa2f2deca resolve: demo executable bit being lost
Currently, `jj resolve` always sets the file to be non-executable
whenever a conflict is fully resolved. This is confusing, because it can
cause the executable bit to be lost even if every side agrees that the
file is executable.
2024-12-22 10:12:22 -06:00
Yuya Nishihara
2aaf6cc3c9 cli: remove now redundant dunce::simplified() from run_ui_editor()
The tests still use dunce::simplified() so we won't reintroduce the problem.
2024-12-22 09:45:37 +09:00
Yuya Nishihara
78b5766993 repo, workspace: use dunce::canonicalize() to normalize paths
These paths may be printed, compared with user inputs, or passed to external
programs. It's probably better to avoid unusual "\\?\C:\" paths on Windows.

Fixes #5143
2024-12-22 09:45:37 +09:00
Scott Taylor
6baa43624c local_working_copy: store materialized conflict marker length
Storing the conflict marker length in the working copy makes conflict
parsing more consistent, and it allows us to parse valid conflict hunks
even if the user left some invalid conflict markers in the file while
resolving the conflicts.
2024-12-21 11:36:30 -06:00
Scott Taylor
b11ce6bd28 conflicts: escape conflict markers by making them longer
If a file contains lines which look like conflict markers, then we need
to make the real conflict markers longer so that the materialized
conflicts can be parsed unambiguously.

When parsing the conflict, we require that the conflict markers are at
least as long as the materialized conflict markers based on the current
tree. This can lead to some unintuitive edge cases which will be solved
in the next commit.

For instance, if we have a file explaining the differences between
Jujutsu's conflict markers and Git's conflict markers, it could produce
a conflict with long markers like this:

```
<<<<<<<<<<< Conflict 1 of 1
%%%%%%%%%%% Changes from base to side #1
 Jujutsu uses different conflict markers than Git, which just shows the
-sides of a conflict without a diff.
+sides of a conflict without a diff:
+
+<<<<<<<
+left
+|||||||
+base
+=======
+right
+>>>>>>>
+++++++++++ Contents of side #2
Jujutsu uses different conflict markers than Git:

<<<<<<<
%%%%%%%
-base
+left
+++++++
right
>>>>>>>
>>>>>>>>>>> Conflict 1 of 1 ends
```

We should support these options for "git" conflict marker style as well,
since Git actually does support producing longer conflict markers in
some cases through .gitattributes:

https://git-scm.com/docs/gitattributes#_conflict_marker_size

We may also want to support passing the conflict marker length to merge
tools as well in the future, since Git supports a "%L" parameter to pass
the conflict marker length to merge drivers:

https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver
2024-12-21 11:36:30 -06:00
Scott Taylor
369e8ea057 conflicts: refactor conflict marker writing and parsing
These changes make the code a bit more readable, and they will make it
easier to have conflict markers of different lengths in the next commit.
2024-12-21 11:36:30 -06:00
Yuya Nishihara
75ce7f6b7f absorb: abandon source commit if it becomes discardable
I don't think we need --keep-emptied flag. IIRC, "jj squash" has that flag in
order not to squash commit description to the destination commits. Since
"jj absorb" never moves commit description, the source commit is preserved in
that situation.

Closes #5141
2024-12-21 09:19:54 +09:00
Yuya Nishihara
bb6b551a4c commit_builder: add is_empty(), is_discardable(), and .abandon()
It's convenient to test if rewriter.reparent()-ed commit is empty than
implementing the same check based on rewriter.new_parents(). CommitRewriter
is an intermediate state, and it doesn't know whether the commit will be rebased
or reparented.
2024-12-21 09:19:54 +09:00
Yuya Nishihara
f7fd523dd8 rewrite: remove unneeded Result from CommitRewriter::reparent()
reparent() just configures new CommitBuilder instance, which should never fail.
2024-12-21 09:19:54 +09:00
Yuya Nishihara
d0f0e8dcff settings: propagate error from git_settings() and signing_backend() 2024-12-21 09:19:44 +09:00
Yuya Nishihara
52511f491e settings: inline CLI options to callers, propagate type error
This patch moves max_new_file_size() and conflict_marker_style() to CLI, but
there isn't a clear boundary whether the configuration should be managed by
UserSettings or not. I decided to move them to CLI just because we can eliminate
.optional() handling. The default parameters are defined in config/misc.toml.
2024-12-21 09:19:44 +09:00
Yuya Nishihara
3381dd2a2c cli: extract helper function that constructs SnapshotOptions
It's a bit weird that we have to construct a start-tracking matcher by caller,
but it has to be parameterized anyway.
2024-12-21 09:19:44 +09:00
Yuya Nishihara
1b4e210524 help: move "git push --remote" explanation to main paragraph, some clarification
We might add --all-remotes at some point, but "jj git push" doesn't support
bulk push right now.

https://github.com/jj-vcs/jj/discussions/4901#discussioncomment-11607150
2024-12-21 09:19:33 +09:00
Benjamin Tan
dddeb8b526 lib: fix various typos
This commit fixes typos unintentionally introduced in d9c68e08, when
renaming `jj branch` to `jj bookmark`.
2024-12-21 02:46:24 +08:00
dependabot[bot]
7963536068 github: bump astral-sh/setup-uv in the github-dependencies group
Bumps the github-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 4.2.0 to 5.0.1
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](38f3f10444...180f8b4439)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-20 09:43:22 -06:00
Yuya Nishihara
ef49643128 tests: use toml_edit::Value in more places 2024-12-20 19:28:01 +09:00
Yuya Nishihara
d7e0ab6119 tests: use toml_edit to escape editor path, set ui.editor instead of $EDITOR
Most callers don't need the $EDITOR variable.
2024-12-20 19:28:01 +09:00
Yuya Nishihara
36b7f007eb tests: split test_describe() that doesn't depend on fake-editor
Makes it clear that test_describe_editor_env() doesn't need edit_script.
2024-12-20 19:28:01 +09:00
Yuya Nishihara
124970c81b tests: leverage set_up_fake_diff_editor() in test_diffedit_3pane() 2024-12-20 19:28:01 +09:00
Yuya Nishihara
7ab5b6852b tests: accept convertible type by set_config_path(), add_config(), add_env_var()
It's convenient. We occasionally pass format!()-ed value to these functions.
2024-12-20 19:28:01 +09:00
Tim Janik
e3c8dce06a docs/config.md: the op log needs a builtin_op_log_* template
Signed-off-by: Tim Janik <timj@gnu.org>
2024-12-20 06:15:34 +01:00
Jochen Kupperschmidt
07e01c1ba6 docs: Git does not use bookmarks 2024-12-19 20:59:45 -08:00
Anton Älgmyr
85adde923a Add links to release and license badges 2024-12-20 02:07:07 +01:00
Yuya Nishihara
89d3a8b8b7 config: remove check for relative path patterns, add doc about path equivalence
I originally added the check so we would never canonicalize path relative to
random cwd, but Windows CI failed because "/" is a relative path. Suppose user
would want to share the same configuration file between native Windows and WSL,
this check would be too nitpicky.
2024-12-20 09:23:46 +09:00
George Tsiamasiotis
843e0edcf7 jj-lib: fix typo in tests 2024-12-19 11:35:44 -08:00
George Tsiamasiotis
79421c6685 cli: fix typo in comment 2024-12-19 11:35:44 -08:00
George Tsiamasiotis
9ea329f03f changelog: fix typo 2024-12-19 11:35:44 -08:00
dependabot[bot]
c9b78dd5d3 cargo: bump libc from 0.2.168 to 0.2.169 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [libc](https://github.com/rust-lang/libc).


Updates `libc` from 0.2.168 to 0.2.169
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.169/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.168...0.2.169)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-19 12:41:38 -06:00
Yuya Nishihara
f5d450d7c3 cli: resolve conditional config scopes
This is an alternative way to achieve includeIf of Git without adding "include"
directive. Conditional include (or include in general) is a bit trickier to
implement than loading all files and filtering the contents.

Closes #616
2024-12-19 11:09:02 +09:00
Yuya Nishihara
1a0d3ae3d7 cli: keep separate copy of "raw" StackedConfig loaded from files
The next patch will introduce the resolution stage of conditional tables.

Since it wasn't easy to detect misuse of "raw" StackedConfig, I added a thin
newtype.
2024-12-19 11:09:02 +09:00
Yuya Nishihara
38d0ca7234 cli: just parse config args in helper function, apply them by caller
I'm going to add config post-processing stage in order to enable config
variables conditionally, and handle/parse_early_args() doesn't have enough
information to evaluate the condition.

The deprecation warning could be emitted by parse_early_args(), but it's
probably better to print it after --color option is applied.
2024-12-19 11:09:02 +09:00
Yuya Nishihara
eecb8d0746 cli: normalize repository path specified by -R
This will simplify path comparison in config resolver. <cwd>/.. shouldn't match
path prefix <cwd>. Maybe we should do path canonicalization globally (or never
do canonicalization), but I'm not sure where that should be made.
2024-12-19 11:09:02 +09:00
Yuya Nishihara
dc9caa5b0a config: add function to resolve conditional tables
This provides an inline version of Git's includeIf. The idea is that each config
layer has an optional key "--when" to enable the layer, and a layer may have
multiple sub-layer tables "--scope" to inline stuff. Layers can be nested, but
that wouldn't be useful in practice.

I choose "--" prefix to make these meta keys look special (and are placed
earlier in lexicographical order), but I don't have strong opinion about that.
We can use whatever names that are unlikely to conflict with the other config
sections.

resolve() isn't exported as a StackedConfig method because it doesn't make
sense to call resolve() on "resolved" StackedConfig. I'll add a newtype in
CLI layer to distinguish raw config from resolved one. Most consumers will
just see the "resolved" config.

#616
2024-12-19 11:09:02 +09:00
Yuya Nishihara
7d46207fa6 config: wrap ConfigLayer in Arc so it can be cheaply cloned
ConfigLayers will be cloned to new StackedConfig instance at resolution stage.

ConfigFile could own ConfigLayer instead of Arc<_>, but copy-on-write behavior
is probably better for the current users.
2024-12-19 11:09:02 +09:00
Stephen Jennings
3316180dc1 config: Add commit_timestamp(commit) template alias
Adds an extension point for changing which date is displayed in log
formats. The function should return a timestamp, not a formatted string.
2024-12-18 17:55:03 -08:00
Martin von Zweigbergk
822f01648d cli: refer to revset argument using REVSET(S) in synopsis
This should help clarify that the arguments are not just simple change
ids or commit ids.
2024-12-18 10:13:44 -08:00
Martin von Zweigbergk
a414441dd6 cli: refer to fileset argument using FILESETS in synopsis
This should help clarify that the arguments are not just simple paths
(assuming `ui.allow-filesets` is not disabled).
2024-12-18 10:13:44 -08:00
dependabot[bot]
02497e6a12 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [clap_complete](https://github.com/clap-rs/clap) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `clap_complete` from 4.5.39 to 4.5.40
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.39...clap_complete-v4.5.40)

Updates `thiserror` from 2.0.7 to 2.0.8
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.7...2.0.8)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 10:16:05 -06:00
dependabot[bot]
26becafd9f github: bump actions/upload-artifact in the github-dependencies group
Bumps the github-dependencies group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/upload-artifact` from 4.4.3 to 4.5.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](b4b15b8c7c...6f51ac03b9)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-18 10:15:50 -06:00
Tim Janik
779272c105 cli/src/diff_util.rs: fix typo in diff --name-only
cli/tests/cli-reference@.md.snap: update snapshots

Signed-off-by: Tim Janik <timj@gnu.org>
2024-12-18 14:04:40 +01:00
Yuya Nishihara
6bbaf79ca5 settings: parse TOML date-time value as well as string timestamp
We can usually omit quotes in --config=NAME=VALUE, but an RFC3339 string is a
valid TOML date-time expression. It's weird that quoting is required to specify
a date-time value.
2024-12-18 09:51:56 +09:00
Yuya Nishihara
4f08c62fe5 settings: propagate error from UserSettings::from_config()
All variables parsed here are debug options, but it would be annoying if
timestamp options were silently ignored because of a typo.
2024-12-18 09:51:56 +09:00
Yuya Nishihara
8fc06690d2 settings: cache "debug.operation-timestamp" value
This will make error handling easier in later commit.
2024-12-18 09:51:56 +09:00
Martin von Zweigbergk
b836e0ae95 docs/cli: update URLs to from martinvonz user to jj-vcs org
We just migrated to the jj-vcs GitHub org, so we should point to the
new GitHub URLs.
2024-12-17 12:44:44 -08:00
dependabot[bot]
867e204d91 cargo: bump clap_complete in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap_complete](https://github.com/clap-rs/clap).


Updates `clap_complete` from 4.5.38 to 4.5.39
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.39)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 20:45:31 +01:00
dploch
396833f803 absorb: move core functionality from cli to lib 2024-12-17 14:42:30 -05:00
scored wrench
2a563039a5 docs: fix incorrect gitignore path 2024-12-17 10:02:55 -08:00
Yuya Nishihara
f87c6ed337 cli: deprecate --config-toml
Typical usage should now be covered by --config=NAME=VALUE.

Closes #3867
2024-12-17 20:12:12 +09:00
Yuya Nishihara
abf48576ea cli: replace --config-toml=TOML with --config=NAME=VALUE 2024-12-17 20:12:12 +09:00
Scott Taylor
d75cc94c64 gitignore: remove FIXME comment about passing path
I looked through the code for the `ignore` crate, and this optional path
is not used anywhere. The only reason to pass it would be to be able to
get the path from the `Glob` when we call `Gitignore::matched` or
`Gitignore::matched_path_or_any_parents`, but we ignore the returned
`Glob` completely anyway. Passing the path would require an unnecessary
clone of the path for each line in every .gitignore file, so it's better
not to pass it since we don't need it.
2024-12-16 21:02:56 -06:00
Yuya Nishihara
60078e9887 cli: add simpler --config=NAME=VALUE argument
This supersedes #3867. I'll probably replace all uses of --config-toml and
mark --config-toml as deprecated.
2024-12-17 10:33:29 +09:00
Yuya Nishihara
7a1e27daed completion: pass --config-file down to jj command
Spotted while adding --config NAME=VALUE.
2024-12-17 10:33:29 +09:00
Yuya Nishihara
17ad708e44 cli: externalize ConfigArg value from enum, group arguments of the same type
This will simplify handling of multiple ConfigArg items of the same type.
Consecutive --config NAME=VALUE arguments will be inserted to the same
ConfigLayer.
2024-12-17 10:33:29 +09:00
Yuya Nishihara
3f115cbea5 cli: parse "jj config set" value a bit stricter, report syntax error
The same parsing function will be used for --config NAME=VALUE.

I don't think we'll add schema-based type inference anytime soon, so I moved
the value parsing to clap layer.
2024-12-17 10:33:29 +09:00
Milo Moisson
acaf7afc5b docs: document JJ_LOG and --debug 2024-12-17 01:51:35 +01:00
Milo Moisson
f3a9b6d84b cli_util: use JJ_LOG to specify log configuration
Using the default `RUST_LOG` env var, conflicts when
working on rust projects.
2024-12-17 01:51:35 +01:00
Scott Taylor
9591523db4 gitignore: add more detail to errors on invalid pattern 2024-12-16 18:49:48 -06:00
Scott Taylor
7f1d902f57 snapshot: demo unreadable error message on invalid ignore
A user on Discord ran into this error message, and they were confused
about why they were getting it. The next commit will improve the error
messages.
2024-12-16 18:49:48 -06:00
Tim Janik
25117d7d01 docs/github.md: Useful Revsets: fix revset for @ ancestry without remotes
This also fixes a mismatch between using ::@ to list ancestors
and calling them descendants.

Signed-off-by: Tim Janik <timj@gnu.org>
2024-12-16 21:23:09 +01:00
dependabot[bot]
d57425964e github: bump dtolnay/rust-toolchain in the github-dependencies group
Bumps the github-dependencies group with 1 update: [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain).


Updates `dtolnay/rust-toolchain` from 1482605bfc5719782e1267fd0c0cc350fe7646b8 to a54c7afa936fefeb4456b2dd8068152669aa8203
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](1482605bfc...a54c7afa93)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-type: direct:production
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 10:49:31 -06:00
dependabot[bot]
740ee76eeb cargo: bump thiserror in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [thiserror](https://github.com/dtolnay/thiserror).


Updates `thiserror` from 2.0.6 to 2.0.7
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.6...2.0.7)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 10:48:56 -06:00
Martin von Zweigbergk
8907e692d4 help: add more keyword topics (for jj help -k)
We have the keyword-help feature now so let's use it a bit more. I'm
not sure how we should advertise it better, but that's a different
topic anyway.
2024-12-15 23:10:31 -08:00
Matt Kulukundis
c82483b088 cli: make split more explicit that it uses filesets 2024-12-15 18:44:19 -05:00
Benjamin Tan
dc16830151 templater: add Email template type, deprecate Signature.username()
The `Signature.email()` method is also updated to return the new Email
type. The `Signature.username()` method is deprecated for
`Signature.email().local()`.
2024-12-15 16:03:34 +08:00
Yuya Nishihara
e50673d5ac cli: make "jj config edit" always open file, not directory
I don't think the new behavior is strictly better, but it's more consistent
with the other "jj config" commands so we can remove the special case for
"jj config edit".
2024-12-15 16:36:29 +09:00
Yuya Nishihara
ac52e43435 cli: let "config set"/"unset" continue if file to edit can be disambiguated
If the user config path was an empty directory, these commands would fail with
EISDIR instead of "can't set config in path (dirs not supported)". I think this
can be changed later to create new "$dir/config.toml" file. Maybe we can also
change the default path to ~/.config/jj directory, and load all *.toml files
from there?
2024-12-15 16:36:29 +09:00
Yuya Nishihara
bb2b956419 cli: fix "jj config path" not create new file at default path
This patch adds simpler user/repo_config_path() accessors. existing_*_path()
are kinda implementation details (for testing), so they are now private methods.
new_user_config_path() will be removed later.
2024-12-15 16:36:29 +09:00
Yuya Nishihara
ef724d2940 cli: inline write/remove_config_value_to/from_file()
They are short, and it wouldn't make much sense to do load, mutate one entry,
and save in one function.

In later patches, "jj config set"/"unset" will be changed to reuse the loaded
ConfigLayer if the layer can be unambiguously selected.
2024-12-14 22:12:41 +09:00
Yuya Nishihara
a6f705bc92 cli: git: extract helper function that sets up trunk() alias 2024-12-14 22:12:41 +09:00
Yuya Nishihara
d6ca0c9940 config: add convenient ConfigLayer wrapper that provides .save() method
I'm going to remove write/remove_config_value_to/from_file() functions, but I
don't want to copy layer.path.expect(..) to all callers.
2024-12-14 22:12:41 +09:00
Yuya Nishihara
215c82e975 config: add layer.delete_value(name) method
Since .get("path.to.non-table.children") returns NotFound, I made
.delete_value() not fail in that case. The path doesn't exist, so
.delete_value() should be noop.

remove_config_value_from_file() will be inlined to callers.
2024-12-14 22:12:41 +09:00
Yuya Nishihara
4d67d3eeca config: forward write_config_value_to_file() to ConfigLayer::set_value()
write_config_value_to_file() will be inlined to callers.
2024-12-14 22:12:41 +09:00
dependabot[bot]
cf6711437f github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.7 to 3.27.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](babb554ede...df409f7d92)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-14 10:47:59 +09:00
David Crespo
bf24be20ef new: swap order of @ and main in doc comment
I followed the recommendation in the `jj new` doc to use `jj new main @`
to make a merge commit and ended up with a merge commit that GitHub did
not like. The PR diff included both the relevant changes from that
branch plus everything I merged in. @papertigers pointed out that
swapping the two args produces a merge commit GitHub understands better.
Happy to add a line explaining that the order matters, but it might be
too much detail for this spot. The linked doc
https://martinvonz.github.io/jj/latest/working-copy/ also does not
explain this.
2024-12-14 00:47:30 +01:00
Yuya Nishihara
fca92f1e16 cli: add --config-file=PATH argument
This would be useful for scripting purpose. Maybe we can also replace the
current --config-toml=<TOML> use cases by --config-file=<PATH> and simpler
--config=<KEY>=<VALUE>.

https://github.com/martinvonz/jj/issues/4926#issuecomment-2506672165

If we want to add more source variants (such as fd number), it might be better
to add --config-from=<type>:<path|fd|..>. In any case, we'll probably want
--config=<KEY>=<VALUE>, and therefore, we'll need to merge more than one
--config* arguments.
2024-12-13 10:27:03 +09:00
Yuya Nishihara
c29b5a2aa3 cli: set global args to config table without re-parsing as TOML
This should be safer than constructing a parsable TOML form.
2024-12-13 10:27:03 +09:00
Yuya Nishihara
e1ab2477cd docs: mention "jj file untrack" in Git compatibility list
Closes #5059
2024-12-13 10:20:48 +09:00
Yuya Nishihara
0f845b6930 docs: quote "jj file untrack" consistently 2024-12-13 10:20:48 +09:00
Austin Seipp
e7e66d23b1 Back out 89d57ffb: "build: add rust-toolchain.toml"
This backs out commit 89d57ffb29577c99f0f5187b76bff369f19c5886.

This is causing a CI failure because we can't build musl binaries,
presumably because the rust-toolchain file overriding the chosen
musl toolchain for some reason. Backout until we can reapply
a proper fix.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2024-12-12 15:54:30 -06:00
dependabot[bot]
97b14f9874 cargo: bump bstr from 1.11.0 to 1.11.1 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [bstr](https://github.com/BurntSushi/bstr).


Updates `bstr` from 1.11.0 to 1.11.1
- [Commits](https://github.com/BurntSushi/bstr/compare/1.11.0...1.11.1)

---
updated-dependencies:
- dependency-name: bstr
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-12 20:25:50 +01:00
Yuya Nishihara
53b8eeb5ab templater: rename "logical" eq/ne operators
They are the equality operators. There aren't logical, bitwise, arithmetic
thingy.
2024-12-12 22:48:45 +09:00
Benjamin Tan
da5b790f7a templater: add relational operators (>=, >, <=, <)
Closes #5062.
2024-12-12 19:56:29 +08:00
Benjamin Tan
48233a1cb7 templater: add IntoTemplateProperty::try_into_cmp
These methods will be used to add `>`, `>=`, `<`, and `<=` operators to
the template language.
2024-12-12 19:56:29 +08:00
Benjamin Tan
2504703b0d completion: add completion for jj bookmark {create,set} --revision 2024-12-12 19:56:02 +08:00
Yuya Nishihara
0de36918e4 config: merge and print inline tables as values
Before, "jj config get"/"list" and .get() functions processed inline tables as
tables (or directories in filesystem analogy), whereas "set"/"unset" processed
ones as values (or files.) This patch makes all commands and functions process
inline tables as values. We rarely use the inline table syntax, and it's very
hard to pack many (unrelated) values into an inline table. TOML doesn't allow
newlines between { .. }. Our common use case is to define color styles, which
wouldn't be meant to inherit attributes from the default settings.

The default pager setting is flattened in case user overrides pager.env without
changing the command args.
2024-12-12 10:11:51 +09:00
Martin von Zweigbergk
56f208338e rebase: print "Nothing changed" when source set is empty 2024-12-11 11:25:08 -08:00
Martin von Zweigbergk
942d105c74 rebase: add tests of rebasing empty sets
As these tests show, we sometimes print an error when trying to rebase
an empty set, and sometimes we don't say anything at all. It seems to
me like we should say "Nothing changed" in all of these cases.
2024-12-11 11:25:08 -08:00
Waleed Khan
c2b86197f5 docs: collapse contributing.md suggested cargo install commands
Should be easier to copy-and-paste and install everything this way?
2024-12-11 11:05:47 -08:00
Waleed Khan
89d57ffb29 build: add rust-toolchain.toml
Follow-up from discussion at https://discord.com/channels/968932220549103686/1288926971719323762

I don't think we achieved consensus in that thread. We could use `stable` or `nightly` here instead if we prefer. Note that user `rustup` overrides are still respected in the presence of a `rust-toolchain.toml`.
2024-12-11 11:05:47 -08:00
Martin von Zweigbergk
db5e7dd70c docs: update SECURITY.md now that we've enabled vulnerability reporting
We enabled GitHub's private vulnerability reporting a few weeks or
months ago (for CVE-2024-51990), so there's no need to email about
vulnerabilities anymore.
2024-12-11 09:23:00 -08:00
dependabot[bot]
1ceda1fb89 cargo: bump serde in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [serde](https://github.com/serde-rs/serde).


Updates `serde` from 1.0.215 to 1.0.216
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.215...v1.0.216)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-11 23:38:11 +08:00
Yuya Nishihara
71440fce11 cli: simplify formatting of sizes in "file too large" warning message
As Martin spotted, the original code can't prevent "1.0GiB, maximum size allowed
is ~1.0GiB." I personally don't mind if the error message contained the exact
size, so I simply let it print both exact and human byte sizes unconditionally.
2024-12-11 20:19:51 +09:00
Yuya Nishihara
168c7979fe working_copy: on snapshot, warn new large files and continue
I think this provides a better UX than refusing any operation due to large
files. Because untracked files won't be overwritten, it's usually safe to
continue operation ignoring the untracked files. One caveat is that new large
files can become tracked if files of the same name checked out. (see the test
case)

FWIW, the warning will be printed only once if watchman is enabled. If we use
the snapshot stats to print untracked paths in "jj status", this will be a
problem.

Closes #3616, #3912
2024-12-11 20:19:51 +09:00
Yuya Nishihara
f4fdc19d9e working_copy: plumbing to propagate untracked paths to caller 2024-12-11 20:19:51 +09:00
Yuya Nishihara
e1936a2e8b docs: suggest "jj bookmark move" where makes sense
Closes #5067
2024-12-11 10:37:45 +09:00
Yuya Nishihara
32ee480942 formatter: leverage serde to parse color styles
Here we don't use an untagged enum to dispatch deserializer by type. An
untagged enum wouldn't propagate underlying parse errors.
2024-12-11 10:37:34 +09:00
Yuya Nishihara
7ae1a97d2b formatter: rename Style fields to match configuration keys
We could rename fields at serde layer, but it's probably better to use the same
names if possible.
2024-12-11 10:37:34 +09:00
Yuya Nishihara
564ce044fb ui: define default ui.color in config/misc.toml, use serde to parse value
It looks a bit odd that FromStr is forwarded to Deserialize, not the other way
around, but that's convenient because Deserialize can be derived.
2024-12-11 10:37:34 +09:00
dependabot[bot]
bcfe8ef783 github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.6 to 3.27.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](aa57810251...babb554ede)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 14:52:58 -06:00
Yuya Nishihara
0975cb5374 cargo: drop dependency on config crate 2024-12-10 16:08:50 +09:00
Yuya Nishihara
118e76e5c2 config: add ConfigLoadError, replace uses of config::ConfigError 2024-12-10 16:08:50 +09:00
Yuya Nishihara
67f8a59643 config: remove unused ConfigEnvError variant, convert io::Error explicitly
It wasn't obvious which io::Error was mapped to a "file creation" error.
Perhaps, file creation will be moved to caller, but let's make the error
handling explicit so we'll remove the unused error variant later.
2024-12-10 16:08:50 +09:00
Yuya Nishihara
4b1a057fd4 config: construct "unset" error without using config::ConfigError
I also changed the error type from Config to User. They are failures of user
action, not configuration.
2024-12-10 16:08:50 +09:00
Yuya Nishihara
1c7634fcf0 formatter: in tests, use IndexMap to preserve definition order of colors 2024-12-10 12:11:39 +09:00
Yuya Nishihara
1d58bdc8eb config: preserve definition order of table items
toml_edit uses IndexMap internally, so the order of table items is
deterministic.
2024-12-10 12:11:39 +09:00
Yuya Nishihara
e2be4fa1ac config: migrate underlying data structure to toml_edit
This patch does not change the handling of inline tables yet. Both inline and
non-inline tables are merged as before. OTOH, .set_value() is strict about table
types because it should refuse to overwrite a table whereas an inline table
should be overwritten as a value. This matches "jj config set"/"unset"
semantics. rules_from_config() in formatter.rs uses .as_inline_table(), which is
valid because toml_edit::Value type never contains non-inline table.

Since toml_edit::Value doesn't implement PartialEq, stacking tests now use
insta::assert_snapshot!().
2024-12-10 12:11:39 +09:00
Yuya Nishihara
45c80c2a5f local_working_copy: optimize path comparison in prefixed file states
Since all entries in filtered file states share the same directory prefix, we
don't need to compare full file paths.

The added functions take (path, name) instead of (path, sub_path) because the
comparison can be slightly faster if the name is guaranteed to be a single path
component.

Benchmark:
1. original (omitted)
2. per-directory spawn (omitted)
3. per-directory deleted files (previous patch)
4. shorter path comparison (this patch)

gecko-dev (~357k files, ~25k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/gecko-dev debug snapshot
  Time (mean ± σ):     480.1 ms ±   8.8 ms    [User: 3190.5 ms, System: 2127.2 ms]
  Range (min … max):   471.2 ms … 509.8 ms    30 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/gecko-dev debug snapshot
  Time (mean ± σ):     404.0 ms ±   4.4 ms    [User: 1933.4 ms, System: 2148.8 ms]
  Range (min … max):   396.4 ms … 416.9 ms    30 runs

Relative speed comparison
        1.19 ±  0.03  target/release-with-debug/jj-3 -R ~/mirrors/gecko-dev debug snapshot
        1.00          target/release-with-debug/jj-4 -R ~/mirrors/gecko-dev debug snapshot
```

linux (~87k files, ~6k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/linux debug snapshot
  Time (mean ± σ):     204.2 ms ±   3.0 ms    [User: 667.3 ms, System: 545.6 ms]
  Range (min … max):   197.1 ms … 209.2 ms    30 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/linux debug snapshot
  Time (mean ± σ):     191.3 ms ±   3.3 ms    [User: 467.4 ms, System: 542.2 ms]
  Range (min … max):   186.1 ms … 200.6 ms    30 runs

Relative speed comparison
        1.07 ±  0.02  target/release-with-debug/jj-3 -R ~/mirrors/linux debug snapshot
        1.00          target/release-with-debug/jj-4 -R ~/mirrors/linux debug snapshot
```

nixpkgs (~45k files, ~31k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/nixpkgs debug snapshot
  Time (mean ± σ):     173.3 ms ±   6.7 ms    [User: 899.4 ms, System: 889.0 ms]
  Range (min … max):   166.5 ms … 197.9 ms    30 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/nixpkgs debug snapshot
  Time (mean ± σ):     161.7 ms ±   2.5 ms    [User: 739.1 ms, System: 881.7 ms]
  Range (min … max):   156.5 ms … 166.4 ms    30 runs

Relative speed comparison
        1.07 ±  0.04  target/release-with-debug/jj-3 -R ~/mirrors/nixpkgs debug snapshot
        1.00          target/release-with-debug/jj-4 -R ~/mirrors/nixpkgs debug snapshot
```

git (~4.5k files, 0.2k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 30 --runs 50 ..
Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/git debug snapshot
  Time (mean ± σ):      28.8 ms ±   1.0 ms    [User: 33.0 ms, System: 37.6 ms]
  Range (min … max):    26.8 ms …  31.3 ms    50 runs

Benchmark 4: target/release-with-debug/jj-4 -R ~/mirrors/git debug snapshot
  Time (mean ± σ):      28.8 ms ±   1.9 ms    [User: 30.3 ms, System: 36.5 ms]
  Range (min … max):    26.0 ms …  39.2 ms    50 runs

Relative speed comparison
        1.00          target/release-with-debug/jj-3 -R ~/mirrors/git debug snapshot
        1.00 ±  0.08  target/release-with-debug/jj-4 -R ~/mirrors/git debug snapshot
```
2024-12-10 10:51:04 +09:00
Yuya Nishihara
8caec186c1 local_working_copy: filter deleted files per directory (or job)
This greatly reduces the amount of paths to be sent over the channel and the
strings to be hashed.

Benchmark:
1. original (omitted)
2. per-directory spawn (previous patch)
3. per-directory deleted files (this patch)
4. shorter path comparison (omitted)

gecko-dev (~357k files, ~25k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/gecko-dev debug snapshot
  Time (mean ± σ):     710.7 ms ±   9.1 ms    [User: 3070.7 ms, System: 2142.6 ms]
  Range (min … max):   695.9 ms … 740.1 ms    30 runs

Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/gecko-dev debug snapshot
  Time (mean ± σ):     480.1 ms ±   8.8 ms    [User: 3190.5 ms, System: 2127.2 ms]
  Range (min … max):   471.2 ms … 509.8 ms    30 runs

Relative speed comparison
        1.76 ±  0.03  target/release-with-debug/jj-2 -R ~/mirrors/gecko-dev debug snapshot
        1.19 ±  0.03  target/release-with-debug/jj-3 -R ~/mirrors/gecko-dev debug snapshot
```

linux (~87k files, ~6k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/linux debug snapshot
  Time (mean ± σ):     242.3 ms ±   3.3 ms    [User: 656.8 ms, System: 538.0 ms]
  Range (min … max):   236.9 ms … 252.3 ms    30 runs

Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/linux debug snapshot
  Time (mean ± σ):     204.2 ms ±   3.0 ms    [User: 667.3 ms, System: 545.6 ms]
  Range (min … max):   197.1 ms … 209.2 ms    30 runs

Relative speed comparison
        1.27 ±  0.03  target/release-with-debug/jj-2 -R ~/mirrors/linux debug snapshot
        1.07 ±  0.02  target/release-with-debug/jj-3 -R ~/mirrors/linux debug snapshot
```

nixpkgs (~45k files, ~31k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/nixpkgs debug snapshot
  Time (mean ± σ):     190.7 ms ±   4.1 ms    [User: 859.3 ms, System: 881.1 ms]
  Range (min … max):   184.6 ms … 202.4 ms    30 runs

Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/nixpkgs debug snapshot
  Time (mean ± σ):     173.3 ms ±   6.7 ms    [User: 899.4 ms, System: 889.0 ms]
  Range (min … max):   166.5 ms … 197.9 ms    30 runs

Relative speed comparison
        1.18 ±  0.03  target/release-with-debug/jj-2 -R ~/mirrors/nixpkgs debug snapshot
        1.07 ±  0.04  target/release-with-debug/jj-3 -R ~/mirrors/nixpkgs debug snapshot
```

git (~4.5k files, 0.2k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 30 --runs 50 ..
Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/git debug snapshot
  Time (mean ± σ):      30.6 ms ±   1.1 ms    [User: 33.8 ms, System: 39.0 ms]
  Range (min … max):    29.0 ms …  35.0 ms    50 runs

Benchmark 3: target/release-with-debug/jj-3 -R ~/mirrors/git debug snapshot
  Time (mean ± σ):      28.8 ms ±   1.0 ms    [User: 33.0 ms, System: 37.6 ms]
  Range (min … max):    26.8 ms …  31.3 ms    50 runs

Relative speed comparison
        1.06 ±  0.05  target/release-with-debug/jj-2 -R ~/mirrors/git debug snapshot
        1.00          target/release-with-debug/jj-3 -R ~/mirrors/git debug snapshot
```
2024-12-10 10:51:04 +09:00
Yuya Nishihara
99d8703d3b local_working_copy: spawn snapshot job per directory with file count threshold
This change basically means two things:
 a. a directory scan isn't split into too many small jobs, and
 b. a directory scan isn't blocked by recursive visit_directory() calls.
Before, small jobs were created at each recursion depth, so there were silent
time slice before these jobs started producing work.

I don't know if this mitigates the issue #4508, but it's slightly faster on my
Linux machine.

matcher.visit(dir) is moved to caller because it's silly to spawn an empty job.
TreeState::snapshot() already checks that for the root path.

Benchmark:
1. original
2. per-directory spawn (this patch)
3. per-directory deleted files (omitted)
4. shorter path comparison (omitted)

gecko-dev (~357k files, ~25k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/gecko-dev debug snapshot
  Time (mean ± σ):     764.9 ms ±  16.7 ms    [User: 3274.7 ms, System: 2183.3 ms]
  Range (min … max):   731.9 ms … 814.2 ms    30 runs

Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/gecko-dev debug snapshot
  Time (mean ± σ):     710.7 ms ±   9.1 ms    [User: 3070.7 ms, System: 2142.6 ms]
  Range (min … max):   695.9 ms … 740.1 ms    30 runs

Relative speed comparison
        1.89 ±  0.05  target/release-with-debug/jj-1 -R ~/mirrors/gecko-dev debug snapshot
        1.76 ±  0.03  target/release-with-debug/jj-2 -R ~/mirrors/gecko-dev debug snapshot
```

linux (~87k files, ~6k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/linux debug snapshot
  Time (mean ± σ):     268.2 ms ±  11.3 ms    [User: 636.6 ms, System: 518.5 ms]
  Range (min … max):   247.5 ms … 295.2 ms    30 runs

Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/linux debug snapshot
  Time (mean ± σ):     242.3 ms ±   3.3 ms    [User: 656.8 ms, System: 538.0 ms]
  Range (min … max):   236.9 ms … 252.3 ms    30 runs

Relative speed comparison
        1.40 ±  0.06  target/release-with-debug/jj-1 -R ~/mirrors/linux debug snapshot
        1.27 ±  0.03  target/release-with-debug/jj-2 -R ~/mirrors/linux debug snapshot
```

nixpkgs (~45k files, ~31k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 3 --runs 30 ..
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/nixpkgs debug snapshot
  Time (mean ± σ):     201.0 ms ±   8.5 ms    [User: 929.3 ms, System: 917.6 ms]
  Range (min … max):   170.3 ms … 218.5 ms    30 runs

Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/nixpkgs debug snapshot
  Time (mean ± σ):     190.7 ms ±   4.1 ms    [User: 859.3 ms, System: 881.1 ms]
  Range (min … max):   184.6 ms … 202.4 ms    30 runs

Relative speed comparison
        1.24 ±  0.06  target/release-with-debug/jj-1 -R ~/mirrors/nixpkgs debug snapshot
        1.18 ±  0.03  target/release-with-debug/jj-2 -R ~/mirrors/nixpkgs debug snapshot
```

git (~4.5k files, 0.2k dirs)
```
% JJ_CONFIG=/dev/null hyperfine --sort command --warmup 30 --runs 50 ..
Benchmark 1: target/release-with-debug/jj-1 -R ~/mirrors/git debug snapshot
  Time (mean ± σ):      30.3 ms ±   1.1 ms    [User: 40.5 ms, System: 39.4 ms]
  Range (min … max):    28.3 ms …  35.7 ms    50 runs

Benchmark 2: target/release-with-debug/jj-2 -R ~/mirrors/git debug snapshot
  Time (mean ± σ):      30.6 ms ±   1.1 ms    [User: 33.8 ms, System: 39.0 ms]
  Range (min … max):    29.0 ms …  35.0 ms    50 runs

Relative speed comparison
        1.05 ±  0.05  target/release-with-debug/jj-1 -R ~/mirrors/git debug snapshot
        1.06 ±  0.05  target/release-with-debug/jj-2 -R ~/mirrors/git debug snapshot
```

- CPU: 8-core AMD Ryzen 7 PRO 4750U with Radeon Graphics (-MT MCP-)
- speed/min/max: 1600/1400/1700 MHz Kernel: 6.11.10-amd64 x86_64
- Filesystem: ext4
2024-12-10 10:51:04 +09:00
Yuya Nishihara
e98589a15a local_working_copy: extract loop body of visit_directory() to function
It's annoying that rustfmt indents a large code block depending on the length
of parameters and method calls needed to configure the parallel iterator.
2024-12-10 10:51:04 +09:00
Yuya Nishihara
60d3354923 local_working_copy: extract function that visits gitignore-d tracked files
.try_for_each_with() wasn't needed because mpsc::Sender can be Sync from Rust
1.72.0.
2024-12-10 10:51:04 +09:00
Yuya Nishihara
6d23d5ac8b local_working_copy: extract helper function that emits new file value and state
visit_directory() is big. Let's make it fit in one screen.
2024-12-10 10:51:04 +09:00
dependabot[bot]
632c07216f cargo: bump the cargo-dependencies group with 6 updates
Bumps the cargo-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [chrono](https://github.com/chronotope/chrono) | `0.4.38` | `0.4.39` |
| [libc](https://github.com/rust-lang/libc) | `0.2.167` | `0.2.168` |
| [pest](https://github.com/pest-parser/pest) | `2.7.14` | `2.7.15` |
| [pest_derive](https://github.com/pest-parser/pest) | `2.7.14` | `2.7.15` |
| [rustix](https://github.com/bytecodealliance/rustix) | `0.38.41` | `0.38.42` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.4` | `2.0.6` |


Updates `chrono` from 0.4.38 to 0.4.39
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.38...v0.4.39)

Updates `libc` from 0.2.167 to 0.2.168
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.168/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.167...0.2.168)

Updates `pest` from 2.7.14 to 2.7.15
- [Release notes](https://github.com/pest-parser/pest/releases)
- [Commits](https://github.com/pest-parser/pest/compare/v2.7.14...v2.7.15)

Updates `pest_derive` from 2.7.14 to 2.7.15
- [Release notes](https://github.com/pest-parser/pest/releases)
- [Commits](https://github.com/pest-parser/pest/compare/v2.7.14...v2.7.15)

Updates `rustix` from 0.38.41 to 0.38.42
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.41...v0.38.42)

Updates `thiserror` from 2.0.4 to 2.0.6
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.4...2.0.6)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: pest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: pest_derive
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 10:11:15 +09:00
Martin von Zweigbergk
27c2882b10 progress: fix progress to not redraw every time there's new data
I think the idea of the code was try do 30 updates per second even if
events arrive at, say, every 20 milliseconds. If we had reset the
timer every time we printed, we would otherwise reset the timer every
40 milliseconds and end up with 25 updates per second. However, a bug
in the code caused it to print every update because it always set the
threshold to print the next update to `now`. I tried to keep what I
think was the intent of the original code while fixing the bug.
2024-12-09 17:08:56 -08:00
Martin von Zweigbergk
4fc539b801 progress: add test of progress bar's periodic output
The progress bar is supposed to update only 30 times per second, but
as was reported in #5057, that doesn't seem to be what's
happening. This patch adds tests showing how we update the progress
bar for every event.
2024-12-09 17:08:56 -08:00
Yuya Nishihara
0017747f53 cargo: bump url and idna to address security advisory
https://rustsec.org/advisories/RUSTSEC-2024-0421
2024-12-09 23:16:59 +09:00
Yuya Nishihara
ec14d0820b cli: replace uses of .get_table() to include better error indication
This will also help migrate to toml_edit, where a value type doesn't provide
a deserialization helper.
2024-12-09 10:09:23 +09:00
Yuya Nishihara
514803dc09 config: add method to iterate over merged table keys
.get_table() callers will be migrated to .table_keys() + .get() to attach source
indication to error message. It might be a bit more expensive, but we can get by
without introducing table wrapper that tracks source config layers.
2024-12-09 10:09:23 +09:00
Yuya Nishihara
6b67d5eeec cli: drop support for deprecated (singular) [alias] section
This will simplify config API migration. It's been deprecated since jj 0.7.0.
2024-12-09 10:09:23 +09:00
Yuya Nishihara
8c4ba4a144 diff: fix unified diff to emit one-lower start line number for empty hunks
Both Mercurial and Git (xdiff) have a special case for empty hunks.

https://repo.mercurial-scm.org/hg/rev/2b1ec74c961f

I also changed the internal line numbers to start from 0 so we wouldn't have
to think about whether "N - 1" would underflow.

Fixes #5049
2024-12-09 09:52:07 +09:00
Vamsi Avula
0794f87324 templates: align attributes in builtin_log_detailed
Change-Id: Id00000003feb0508bf7f08e0a0275ba7f7695b70
2024-12-08 15:17:43 +05:30
Yuya Nishihara
6c72a3d1c1 docs: revert string formatting change of "jj help util exec"
Appears that "cargo test" parses indented text as a code block, and fails to
run doc tests. Spotted by running "cargo insta test". This doc comment is a CLI
help which is usually rendered to console, so I think markdown annotation should
be minimal.

This backs out commit ed84468cb8f3, "docs: in `jj help util exec`, use Markdown
`warning` admonition."
2024-12-08 18:04:26 +09:00
Yuya Nishihara
85816f2c9b settings: explicitly convert integer to HumanByteSize
Since config::Value type was lax about data types, an integer value could be
deserialized through a string. This won't apply to new toml_edit-based value
types.
2024-12-08 09:19:35 +09:00
Yuya Nishihara
2461602b56 settings: extract HumanByteSize parsing wrapper to FromStr trait
I'm going to rewrite the deserialization constructor to accept either integer
or string, but the from_str() API is generally useful.

This patch also fixes lifetime of the parse error message.
2024-12-08 09:19:35 +09:00
Yuya Nishihara
b681df0c93 config: add .get_value_with() to handle type not implementing Deserialize
The serde::Deserialize interface isn't always useful. Suppose we're going to
make parsing of color tables stricter, we would have to process a string|table
value. It could be encoded as an untagged enum in serde, but that means the
error message has to be static str (e.g. "expected string or color table") and a
detailed error (e.g. "invalid color name: xxx") would be lost. It's way easier
to dispatch based on the ConfigValue type, than on serde data model.
2024-12-08 09:19:35 +09:00
Yuya Nishihara
f3a44ca56b config: omit uninteresting toml_edit::Decor { .. } from test snapshots
It was noisy, and we would see more Decor { .. } when we migrate the value
types to toml_edit.
2024-12-08 09:19:26 +09:00
Yuya Nishihara
b560e72222 config: remove PartialEq from AnnotatedValue
toml_edit::Value doesn't implement PartialEq.
2024-12-08 09:19:26 +09:00
Waleed Khan
652d16369a docs: in sparse-v2.md, use Markdown warning admonition
Renders more nicely on the documentation website.
2024-12-07 13:50:44 -08:00
Waleed Khan
129ea5262f docs: in contributing.md, use Markdown warning admonition
This has special rendering on the website, which makes it stand out more clearly.
2024-12-07 13:50:44 -08:00
Waleed Khan
ed84468cb8 docs: in jj help util exec, use Markdown warning admonition
We're scraping the CLI help text and rendering it as markdown, so we can use an "admonition" to have this warning text render nicer in the web documentation.

You could argue that `!!! warning` is a little weird to see on the CLI. Some alternatives:

- We could opt to not design the CLI help text around markdown and skip the change to the `jj util exec` help in this commit.
- We could adopt some kind of format that can be rendered well in both contexts.
  - Could sticking to specific formatting constructs by convention.
  - Could use/create an actual translation tool from CLI format to Markdwon.
- We could keep separate versions of web and CLI documentation. (Seems like a bad idea for the foreseeable future, because we don't have the resources to constantly keep both up-to-date and sync.)

I'm in favor of just writing Markdown in the CLI help text for now.
2024-12-07 13:50:44 -08:00
Yuya Nishihara
da3c75b3cb config: add convenient method to insert value into layer, migrate callers
The current implementation is silly, which will be reimplemented to be using
toml_edit::DocumentMut. "jj config set" will probably be ported to this API.
2024-12-07 11:06:21 +09:00
Yuya Nishihara
4e1b71b6b1 cli: load default/command-arg layers per source, clean up return types
This patch removes pre-merge steps which depend on the ConfigBuilder API.

Some of Vec<ConfigLayer> could be changed to Vec<config::Config> (or
Vec<ConfigTable>) to drop the layer parameter, but I'm not sure which is
better. parse_config_args() will have to construct ConfigLayer (or ConfigTable +
Option<PathBuf>) if we add support for --config-file=PATH argument.
2024-12-07 11:06:21 +09:00
Yuya Nishihara
d226bc2762 config: extract helper method that inserts multiple layers
Default configuration will be split to per-source layers.
2024-12-07 11:06:21 +09:00
Yuya Nishihara
a7339244b8 ui: define default ui.quiet and .progress-indicator in config/misc.toml 2024-12-07 11:06:21 +09:00
Yuya Nishihara
3a38d7a670 ui: inline and simplify one-line config.get() functions 2024-12-07 11:06:21 +09:00
Martin von Zweigbergk
256218d1e5 cli: respect $NO_COLOR only if it's non-empty
This is what the spec says.
2024-12-06 17:37:10 -08:00
Martin von Zweigbergk
66723276fc cli: make crossterm not respect NO_COLOR
The `NO_COLOR` spec says that user-specified config is supposed to
override the `$NO_COLOR` environment variable, and we do correctly use
the `ColorFormatter` when `ui.color= "always"` is set in the user's
config. However, it turns out that `NO_COLOR=1` still resulted in no
color because `crossterm` also respects the variable, so the color
codes the `ColorFormatter` requested had no effect. Since `crossterm`
doesn't know about our user configs, it cannot decide whether to
respect `$NO_COLOR`, so let's tell `crossterm` to always use the
colors we tell it to use.
2024-12-06 17:37:10 -08:00
Martin von Zweigbergk
23f9cc7a4f tests: fix test to show that NO_COLOR=1 results in no color
This reproduces the problem in reported in #5041.
2024-12-06 17:37:10 -08:00
Remo Senekowitsch
8c6024c71f completion: teach log about files 2024-12-06 20:44:43 +01:00
dependabot[bot]
6288b184ec cargo: bump clap from 4.5.22 to 4.5.23 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.22 to 4.5.23
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.22...clap_complete-v4.5.23)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-06 10:35:16 -06:00
Martin von Zweigbergk
b7ba3fc0be docs: update installation docs to say that cmake is needed
Since we enabled the `zlib-ng` feature from `gix` in 2412fd4, `cmake`
is needed when installing from source.
2024-12-06 08:23:27 -08:00
Yuya Nishihara
f58145b47d cargo: enable zlib-ng and fast sha1 in gix
I noticed miniz_oxide appears in perf samples. While miniz_oxide is safer, I
think zlib-ng is pretty reliable.

https://docs.rs/gix/latest/gix/#performance

libz-ng-sys is downgraded to 1.1.16 due to the Windows linking issue. The
benchmark result is obtained with libz-ng-sys 1.1.20.

https://github.com/rust-lang/libz-sys/issues/225

```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-0,jj-1 \
  'target/release-with-debug/{bin} --ignore-working-copy log README.md'
Benchmark 1: target/release-with-debug/jj-0 ..
  Time (mean ± σ):     256.6 ms ±   4.3 ms    [User: 214.1 ms, System: 38.6 ms]
  Range (min … max):   245.4 ms … 261.2 ms    10 runs

Benchmark 2: target/release-with-debug/jj-1 ..
  Time (mean ± σ):     223.0 ms ±   4.2 ms    [User: 174.7 ms, System: 44.4 ms]
  Range (min … max):   212.4 ms … 225.8 ms    10 runs
```
2024-12-06 14:38:44 +09:00
Yuya Nishihara
c0ce88003e config: introduce our ConfigGetError type
Since most callers don't need to handle loading/parsing errors, it's probably
better to add a separate error type for "get" operations. The other uses of
ConfigError will be migrated later.

Since ConfigGetError can preserve the source name/path information, this patch
also removes some ad-hock error handling codes.
2024-12-06 10:52:10 +09:00
Yuya Nishihara
c3a8fb9f37 cli: config get: break method chaining for ease of error type migration
.get_value() doesn't do type casting, so a Type error wouldn't occur.
2024-12-06 10:52:10 +09:00
Yuya Nishihara
ba739b2f76 config: move ConfigResultExt from settings module
As the name suggests, this helper should be in the config module.
2024-12-06 10:52:10 +09:00
Yuya Nishihara
dfa5a6b1e4 settings: remove useless Result type from settings.with_repo()
I have no idea how this method will be redesigned, but it's unlikely we'll do
some fallible I/O or parsing here.
2024-12-06 10:52:10 +09:00
Yuya Nishihara
aa4f48c7b7 config: wrap per-layer table access API
The added function is not "get_table(name) -> Result<Table, Error>" because
callers need to know whether the value was missing or shadowed by non-table
value. We just don't have this problem in template/revset-aliases because these
tables are top-level items.
2024-12-06 10:52:10 +09:00
Waleed Khan
b1baf2190e docs: add "Release highlights" section to latest release (v0.24.0)
As per a [Discord discussion](https://discord.com/channels/968932220549103686/968932220549103689/1314291772893171784), we thought it might be nice to include additional information in the changelog/release notes, similar to certain other open-source projects.

For example: In [the Rust 1.82 release notes](https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html), they include a one-line description of the project as well as installation instructions, and then go over several release highlights.

Possible future process:

- We can put the release highlights into `CHANGELOG.md` itself, so that it can undergo the normal review process.
- We'll add the static description/installation text to each release as we publish it (which doesn't need to be duplicated for each version in `CHANGELOG.md`).
2024-12-05 16:59:11 -08:00
Shane Sveller
23a89f0d61 nix: Add explicit flake input for nixpkgs
Previous source of truth:
895a65f8d5/flake-registry.json (L302-L311)
2024-12-05 16:13:30 -06:00
Martin von Zweigbergk
32d2a85539 release: release version 0.24.0 2024-12-04 12:46:44 -08:00
Shane Sveller
5553baf540 cli: git push: describe private commits that would fail push 2024-12-04 13:12:30 -06:00
dependabot[bot]
644e5f0c43 cargo: bump the cargo-dependencies group with 4 updates
Bumps the cargo-dependencies group with 4 updates: [anyhow](https://github.com/dtolnay/anyhow), [clap](https://github.com/clap-rs/clap), [thiserror](https://github.com/dtolnay/thiserror) and [tokio](https://github.com/tokio-rs/tokio).


Updates `anyhow` from 1.0.93 to 1.0.94
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.93...1.0.94)

Updates `clap` from 4.5.21 to 4.5.22
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.21...clap_complete-v4.5.22)

Updates `thiserror` from 2.0.3 to 2.0.4
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.3...2.0.4)

Updates `tokio` from 1.41.1 to 1.42.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.41.1...tokio-1.42.0)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-04 13:03:47 -06:00
dependabot[bot]
4a5673dc33 github: bump EmbarkStudios/cargo-deny-action
Bumps the github-dependencies group with 1 update: [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action).


Updates `EmbarkStudios/cargo-deny-action` from 2.0.3 to 2.0.4
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](2d8c9929d8...e2f4ede4a4)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-04 13:02:16 -06:00
Martin von Zweigbergk
7618b52b79 cli: consider "JJ:" lines as comments also when not followed by space
We currently ignore lines prefixed with "JJ: " (including the space)
in commit messages and in the list of sparse paths from `jj sparse
edit`. I think I included the trailing space in the prefix simply
because that's how we render comments line we insert before we ask the
user to edit the file. However, as #5004 says, Git doesn't require a
space after their "#" prefix. Neither does Mercurial after their "HG:"
prefix. So let's follow their lead and not require the trailing
space. Seems useful especially for people who have their editor
configured to strip trailing spaces.
2024-12-04 10:40:56 -08:00
Scott Taylor
fa7d9c09fc merge_tools: add merge-conflict-exit-codes option
Some Git merge drivers can partially resolve conflicts, leaving some
conflict markers in the output file. In that case, they exit with a code
between 1 and 127 to indicate that there was a conflict remaining in the
file (since Git uses a shell to run the merge drivers, and shells often
use exit codes above 127 for special meanings such as exiting due to a
signal):

https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver

We should support this convention as well, since it will allow many Git
merge drivers to be used with Jujutsu, but we don't run our merge tools
through a shell, so there is no reason to treat exit codes 1 through 127
specially. Instead, we let the user specify which exact exit codes
should indicate conflicts. This is also better for cross-platform
support, since Windows may use different exit codes than Linux for
instance.
2024-12-03 20:08:31 -06:00
Yuya Nishihara
8748a1e73a config: remove merge() function which is used only in tests 2024-12-04 10:33:22 +09:00
Yuya Nishihara
4134511cfa cli: migrate remainder of Config::get() to StackedConfig::get() 2024-12-04 10:33:22 +09:00
Yuya Nishihara
548f85d62d formatter: migrate tests to be using StackedConfig 2024-12-04 10:33:22 +09:00
Yuya Nishihara
d6efd12bc8 completion: remove redundant rebuild of default config 2024-12-04 10:33:22 +09:00
Yuya Nishihara
c4eaef6dfc completion: migrate to UserSettings API
config::Config will soon be replaced with StackedConfig, and UserSettings
provides more convenient methods than StackedConfig.
2024-12-04 10:33:22 +09:00
Yuya Nishihara
0a0f8c5116 config: remove unneeded Result wrapping from resolved_config_values() 2024-12-04 10:33:22 +09:00
Yuya Nishihara
23b24120fb config: migrate resolved_config_values() to new getter API
When I wrote the original lookup function, I didn't notice that the root config
value could be accessed as &config.cache without cloning. That's the only reason
I added split_safe_prefix().
2024-12-04 10:33:22 +09:00
Yuya Nishihara
b0c7d0a7e2 cli: duplicate: parse -rREV option properly
Since "jj duplicate" interface is now quite similar to "jj rebase", it's
annoying that "-r" isn't parsed properly.
2024-12-04 08:38:44 +09:00
Remo Senekowitsch
a9d4886997 completion: teach absorb and bookmark move about revisions
These were simply forgotten in dd6479f104818513906b346e8909d76e956db038
2024-12-04 00:33:01 +01:00
Martin von Zweigbergk
4e125b6c2f cli: move ci alias to config file
I would like to delete this alias altogether. By moving it to the
config to start with, users can start overriding it themselves, as
`commit` or whatever they prefer.
2024-12-03 10:37:54 -08:00
dependabot[bot]
efe23be34a github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.5 to 3.27.6
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f09c1c0a94...aa57810251)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-03 10:41:49 -06:00
Yuya Nishihara
5db4f0bca0 cli: make jj desc and jj st aliases visible again
This backs out commit fd271d39ad54 "cli: make `jj desc` and `jj st` aliases
hidden."

As we discussed in https://github.com/martinvonz/jj/pull/4811, completion of
flags doesn't work for hidden aliases.
2024-12-03 23:10:24 +09:00
Yuya Nishihara
21a6a244b3 local_working_copy: move DirectoryToVisit closer to new snapshotter type 2024-12-03 11:13:46 +09:00
Yuya Nishihara
88c3008724 local_working_copy: move common parameters out of visit_directory() arguments
The shared lifetime looks a bit wonky, but we don't have a reason to assign
per-field lifetime right now.
2024-12-03 11:13:46 +09:00
Yuya Nishihara
1a1d36979a local_working_copy: extract immutable part of snapshot() to separate struct
I'll move common parameters to the snapshotter type.
2024-12-03 11:13:46 +09:00
Yuya Nishihara
4e2e9e9b69 local_working_copy: reorder code to place snapshot functions closer 2024-12-03 11:13:46 +09:00
Yuya Nishihara
94c6d6eaff cli: leverage new settings.get(path) in merge-tools lookup
We no longer need to clone the entire table to get around the path expression
syntax of the config crate.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
13ccd92766 settings: do not store "debug.randomness-seed" in stringified form
While this is a debug option, it doesn't make sense to store an integer value
as a string. We can parse the environment variable instead. The variable is
temporarily parsed into i64 because i64 is the integer type of TOML.
toml_edit::Value doesn't implement any other integer conversion functions.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
3890e426e5 settings: migrate get() to StackedConfig, rename .stacked_config to .config
The goal is to remove dependency on config::Config and replace the underlying
table type to toml_edit::Table. Other StackedConfig::merge() users will be
migrated in the next batch.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
8fadac4114 config: add StackedConfig method that looks up value from all layers
This patch adds separate .get<T>(), .get_table(), and .get_value() methods
because generic value types in toml_edit don't implement Deserialize.

There's a bit of naming inconsistency right now. Low-level generic object is
called an "item", whereas mid-level generic object is called a "value". These
objects will become different types when we migrate to toml_edit.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
8557a0ff0a config: add basic abstraction over &str, [&str], and &ConfigNamePathBuf
We usually look up config objects with a static name, but commands like "jj
config get" has to parse a user input as a config key. We could consolidate the
name type to &ConfigNamePathBuf, but it would be inconvenient if all callers had
to do config.get(&"..".parse()?). It's also undesirable to pass a raw user input
in to .get() as a string.

I originally considered making it support fallible conversion, but doing that
would complicate error handling path. Not all functions should return an error.
It's better to do fallible parsing at the call sites instead.

A string config name is parsed into a temporary ConfigNamePathBuf object. If the
allocation cost matters, the output path type can be abstracted.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
5946ef3880 settings: forward .get_table(key) to underlying config object
This will help migrate away from config::Config. We'll probably need a better
abstraction to preserve error source indication, but let's leave that for now.

.get_table() isn't forwarded to .get::<T>() because ConfigTable won't implement
Deserialize if we migrate to toml_edit.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
ba07c3bc54 config: reexport table and value types
These types will be replaced with toml_edit::Table and ::Value respectively.
2024-12-03 09:42:47 +09:00
Yuya Nishihara
824cd132cc local_working_copy: leverage iterator API of channel receiver 2024-12-03 09:39:17 +09:00
Yuya Nishihara
934a2ba478 local_working_copy: remove useless Result wrapping in snapshot() 2024-12-03 09:39:17 +09:00
Yuya Nishihara
d2c0d92fa2 local_working_copy: propagate read_dir error from snapshot() 2024-12-03 09:39:17 +09:00
Yuya Nishihara
8f55680787 local_working_copy: remove unneeded boxing of diff stream 2024-12-03 09:39:17 +09:00
dependabot[bot]
05fc855185 github: bump astral-sh/setup-uv in the github-dependencies group
Bumps the github-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](5f42d5af6c...38f3f10444)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 13:45:17 -06:00
dependabot[bot]
1783c69799 cargo: bump the cargo-dependencies group with 3 updates
Bumps the cargo-dependencies group with 3 updates: [indexmap](https://github.com/indexmap-rs/indexmap), [syn](https://github.com/dtolnay/syn) and [tracing-subscriber](https://github.com/tokio-rs/tracing).


Updates `indexmap` from 2.6.0 to 2.7.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.6.0...2.7.0)

Updates `syn` from 2.0.89 to 2.0.90
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.89...2.0.90)

Updates `tracing-subscriber` from 0.3.18 to 0.3.19
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.18...tracing-subscriber-0.3.19)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 13:44:47 -06:00
Ilya Grigoriev
1f554e801f cli util gc: document the fact that evolution log limits gc
This is a followup to the discussion in
https://github.com/martinvonz/jj/pull/4953#discussion_r1855328098
2024-12-02 09:09:57 -08:00
Yuya Nishihara
e5de0fc742 cargo: bump gix to 0.68.0 2024-12-02 00:33:35 +09:00
Yuya Nishihara
9b8a42cd4f cargo: bump thiserror to 2.0.3
There are breaking changes in derive syntax, but appears that we don't have any
bad parameter forms.

https://github.com/dtolnay/thiserror/releases/tag/2.0.0
2024-12-02 00:33:35 +09:00
Essien Ita Essien
694dfb8722 jj-lib: abstract a lower-level api for fetching from git.
* GitFetch{} separates `fetch()` from `import_refs()`.
* This allows more control over the stages of the fetch so multiple fetches
  can happen before `import_refs` resolves the changes in the local jj repo.
* Implement `git::fetch` in terms of the new api.
* Add a test case for initial fetch from a repo without `HEAD` set. This tests
  the default branch retrieving behaviour.

Issue: #4923
2024-12-01 12:22:09 +00:00
Remo Senekowitsch
a8c35db43d completion: suggest file paths incrementally
If there are multiple files in a subdirectory that are candidates for
completion, only complete the common directory prefix to reduce the number of
completion candidates shown at once.

This matches the normal shell completion of file paths.
2024-12-01 11:06:36 +01:00
Yuya Nishihara
0ca6f00421 merged_tree: slightly adjust doc comment of inner trees() helpers 2024-11-30 10:20:43 +09:00
Yuya Nishihara
4931b2ba04 merged_tree: remove redundant .clone() from TreeDiffStreamImpl::new() 2024-11-30 10:20:43 +09:00
Yuya Nishihara
c741e3db39 merged_tree: use Merge<Tree> to represent pending trees in TreeDiffStreamImpl
This seems a slightly better in that MergedTree no longer represent a subtree.
2024-11-30 10:20:43 +09:00
Yuya Nishihara
991b0e8b68 merged_tree: keep Arc<Store> globally in TreeDiffStreamImpl
I'll replace treeN: MergedTree with Merge<Tree>, and it's simpler to just
keep the store by TreeDiffStreamImpl.
2024-11-30 10:20:43 +09:00
Yuya Nishihara
a3ca6c6f46 tests: do not pass in commit objects loaded from different store
For the same reason as 37c41d0eaf4d.
2024-11-30 10:20:43 +09:00
dependabot[bot]
aef61a6045 github: bump astral-sh/setup-uv in the github-dependencies group
Bumps the github-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 4.0.0 to 4.1.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](d8db0a86d3...5f42d5af6c)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-30 10:12:06 +09:00
Yuya Nishihara
36e83ed260 cli: move get_new_config_file_path() to commands::config
There aren't any other callers internally, and it is a thin wrapper around
ConfigEnv functions which can be easily implemented.
2024-11-30 10:01:35 +09:00
Yuya Nishihara
e1cd1a00e6 config: update comment about new config file creation
Spotted while updating get_new_config_file_path() callers. "jj config path"
isn't a command to create new config file.
2024-11-30 10:01:35 +09:00
Yuya Nishihara
ecf25a1d9b tests: extract helper that instantiates StackedConfig with commit timestamp 2024-11-30 10:01:05 +09:00
Yuya Nishihara
da01734639 settings: own StackedConfig by UserSettings, migrate tests to use config layer
UserSettings::get_*() will be changed to look up a merged value from
StackedConfig, not from a merged config::Value. This will help migrate away
from the config crate.

Not all tests are ported to ConfigLayer::parse() because it seemed a bit odd
to format!() a TOML document and parse it to build a table of configuration
variables.
2024-11-30 10:01:05 +09:00
Yuya Nishihara
512d85bfad config: add convenient function that constructs ConfigLayer from TOML string
This will be used mainly in tests. The default layers might also be migrated to
per-file layers constructed by ConfigLayer::parse().
2024-11-30 10:01:05 +09:00
Remo Senekowitsch
d76bcd9305 docs: clarify dynamic completion features 2024-11-29 21:22:38 +01:00
Ilya Grigoriev
1b96d27b66 cli git fetch: clarify the error for invalid branch names/globs
Create a clearer error when a branch name contains `*`.

Slightly clarify the error message for `?` in branch name/glob.
2024-11-29 11:27:05 -08:00
Ilya Grigoriev
d3a51cebb7 docs: add example glob to jj help git fetch
I tried to clarify the text a bit as well.
2024-11-29 11:27:05 -08:00
dependabot[bot]
e4231f2ce3 cargo: bump libc from 0.2.166 to 0.2.167 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [libc](https://github.com/rust-lang/libc).


Updates `libc` from 0.2.166 to 0.2.167
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.167/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.166...0.2.167)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-30 00:20:14 +09:00
dependabot[bot]
172fbd40f1 cargo: bump tracing in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [tracing](https://github.com/tokio-rs/tracing).


Updates `tracing` from 0.1.40 to 0.1.41
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.40...tracing-0.1.41)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-28 20:56:42 -06:00
dependabot[bot]
18012f170f github: bump EmbarkStudios/cargo-deny-action
Bumps the github-dependencies group with 1 update: [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action).


Updates `EmbarkStudios/cargo-deny-action` from 2.0.2 to 2.0.3
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](f87fcad0e6...2d8c9929d8)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-28 20:56:32 -06:00
Remo Senekowitsch
5fcc549eab completion: teach commands about files
This is heavily based on Benjamin Tan's fish completions:
https://gist.github.com/bnjmnt4n/9f47082b8b6e6ed2b2a805a1516090c8

Some differences include:
- The end of a `--from`, `--to` ranges is also considered.
- `jj log` is not completed (yet). It has a different `--revisions` argument
  that requires some special handling.
2024-11-28 15:34:39 +01:00
Martin von Zweigbergk
a5690beab5 test_merged_tree: avoid a temporary lifetime extension
I think it's just clearer to assign the owned value to the variable
than to assign a reference to a temporary value.
2024-11-27 18:53:28 -08:00
Martin von Zweigbergk
409be2e1c4 store: make get_tree() functions take owned repo path
The function needs an owned value, so we might as well pass it one and
avoid a few clone calls.
2024-11-27 18:53:28 -08:00
Martin von Zweigbergk
b9e93923a9 merged_tree: avoid two unnecessary clones 2024-11-27 18:53:28 -08:00
Yuya Nishihara
bf17067861 config: replace LayeredConfigs with StackedConfig
Parsed --config-toml args are simply appended because the caller knows there
should be no preloaded CommandArg layers.
2024-11-28 08:45:48 +09:00
Yuya Nishihara
08b1ebe934 config: extract remainder of LayeredConfigs methods to free functions 2024-11-28 08:45:48 +09:00
Yuya Nishihara
a890e1a7f8 config: move read_user/repo_config() methods to ConfigEnv 2024-11-28 08:45:48 +09:00
Yuya Nishihara
1d485b75b6 config: manage repo config path by ConfigEnv
This will help migrate LayeredConfigs methods to ConfigEnv.
2024-11-28 08:45:48 +09:00
Yuya Nishihara
6ec3fcc359 cli: fix typo in config path not found error 2024-11-28 08:45:48 +09:00
Austin Seipp
d6c651f13c nix: fix usage of new parallel linker in Darwin devShell
As reported by @lilyball on Discord, the recent nixpkgs update this week brought
with it an entirely new Darwin stdenv, which had a behavioral change versus
the prior one, causing builds inside the developer shell to fail, as the new
super-duper parallel linker could not be used.

However, rather than giving up and throwing away *billions* of precious CPU
cycles, @emilazy and @lilyball instead doubled down and diagnosed the issue. The
result is a new impenetrable line of Nix code, which is fully explained by the
comments within.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2024-11-27 13:58:01 -06:00
Remo Senekowitsch
92adf896b9 completion: include bookmarks and tags in revisions 2024-11-27 18:46:04 +01:00
Remo Senekowitsch
faa4fff621 completion: allow calling jj multiple times 2024-11-27 18:46:04 +01:00
dependabot[bot]
f84893bd48 cargo: bump libc from 0.2.165 to 0.2.166 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [libc](https://github.com/rust-lang/libc).


Updates `libc` from 0.2.165 to 0.2.166
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.166/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.165...0.2.166)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-27 15:56:31 +00:00
Yuya Nishihara
8e54e3e70e local_working_copy: move comment about conflict file to the right place
I also removed "now" from "is now a normal file" because conflicts should be
materialized as a normal file. "is a normal file" is tested by caller.
2024-11-27 23:06:47 +09:00
dependabot[bot]
e41ffa7ada cargo: bump libc from 0.2.164 to 0.2.165 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [libc](https://github.com/rust-lang/libc).


Updates `libc` from 0.2.164 to 0.2.165
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.165/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.164...0.2.165)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 17:03:17 -06:00
Yuya Nishihara
3186d91a65 config: rename ConfigEnv methods to clarify the intent
We'll add existing/new_repo_config_path() methods.
2024-11-27 07:49:22 +09:00
Yuya Nishihara
39063f18ff config: make ConfigEnv public, replace global functions 2024-11-27 07:49:22 +09:00
Yuya Nishihara
724928028b config: make ConfigEnv methods not consume self
ConfigEnv will be owned by CommandHelper, and some of the LayeredConfigs
methods will be moved there.
2024-11-27 07:49:22 +09:00
Yuya Nishihara
99c790f55c config: extract user path resolution from ConfigEnv
The idea is that ConfigEnv will be a public interface that helps load/create
config file. The path doesn't have to be resolved multiple times.
2024-11-27 07:49:22 +09:00
Yuya Nishihara
34ef4d8587 config: inline check() function in test cases
It becomes hard to maintain because the next patch will change the function
signature.
2024-11-27 07:49:22 +09:00
Scott Taylor
7f57866332 merge_tools: allow setting conflict marker style per-tool
I left the "merge-tool-edits-conflict-markers" option unchanged,
since removing it would likely break some existing configurations. It
also seems like it could be useful to have a merge tool use the default
conflict markers instead of requiring the conflict marker style to
always be set for the merge tool (e.g. if a merge tool allows the user
to manually edit the conflicts).
2024-11-26 09:05:17 -06:00
Yuya Nishihara
e029e8d5db config: rename AnnotatedValue::path to ::name
The field name "path" was confusing because we'll probably add a source file
path to AnnotatedValue.
2024-11-26 18:17:52 +09:00
Yuya Nishihara
14798d3f50 config: add doc comment to AnnotatedValue 2024-11-26 18:17:52 +09:00
Yuya Nishihara
0b23bbb3bb config: move resolved_config_values() to free function
This will help replace cli LayeredConfigs with new StackedConfig. I originally
considered moving this function to jj-lib, but the current API is very specific
to the CLI use case, and wouldn't be reused for building a merged sub table. We
might instead want to extract some bits as a library function.
2024-11-26 18:17:52 +09:00
Yuya Nishihara
0df1ac65b5 cli: call resolved_config_values() directly from cmd_config_list()
There's only one caller, and it will become accessible through
command.settings().
2024-11-26 18:17:52 +09:00
Benjamin Tan
ee37409c5e cli: simplify-parents: add default revsets.simplify-parents config
This adds a new `revsets.simplify-parents` configuration option (similar
to `revsets.fix`) which serves as the default revset for `jj
simplify-parents` if no `--source` or `--revisions` arguments are
provided.
2024-11-26 13:19:25 +08:00
Nathanael Huffman
f6210aa99d cli: Fix error message grammar and associated tests 2024-11-25 18:22:33 -06:00
Scott Taylor
ea72a4af28 tutorial: fix conflict formatting issue 2024-11-25 16:47:59 -06:00
dependabot[bot]
61f0000626 github: bump astral-sh/setup-uv in the github-dependencies group
Bumps the github-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 3.2.3 to 4.0.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](e779db7426...d8db0a86d3)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 15:13:12 -06:00
dependabot[bot]
73e456ab69 cargo: bump hashbrown in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [hashbrown](https://github.com/rust-lang/hashbrown).


Updates `hashbrown` from 0.15.1 to 0.15.2
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/commits)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 15:12:50 -06:00
Martin von Zweigbergk
5aee10aa6d faq: add an entry about why merge commits are often empty 2024-11-25 10:46:52 -08:00
Yuya Nishihara
f819dc9345 config: extract layer management from cli LayeredConfigs to jj-lib
Layers are now constructed per file, not per source type. This will allow us
to report precise position where bad configuration variable is set. Because
layers are now created per file, it makes sense to require existence of the
file, instead of ignoring missing files which would leave an empty layer in the
stack. The path existence is tested by ConfigEnv::existing_config_path(), so I
simply made the new load_file/dir() methods stricter. However, we still need
config::File::required(false) flag in order to accept /dev/null as an empty
TOML file.

The lib type is renamed to StackedConfig to avoid name conflicts. The cli
LayeredConfigs will probably be reorganized as an environment object that
builds a StackedConfig.
2024-11-25 12:09:21 +09:00
Yuya Nishihara
d75aaf3f8d config: move ConfigSource to jj-lib, make it Copy + Ord
I decided to add `path: Option<PathBuf>` as a separate field, not a parameter
of ConfigSource::Repo|User variants.
2024-11-25 12:09:21 +09:00
Yuya Nishihara
75b8e9b857 config: split ConfigSource::Env reflecting layer precedence
I'll make LayeredConfigs store a list of layers sorted by source (or
precedence), where multiple layers can exist for the same source.
2024-11-25 12:09:21 +09:00
Ilya Grigoriev
8c5b39e286 cli util gc: document the fact that it does not abandon operations
This UI will probably change eventually, but let's document it for now.
2024-11-24 15:48:12 -08:00
Martin von Zweigbergk
10c90a5099 merged_tree: propagate errors from conflict iterator 2024-11-23 13:53:04 -08:00
Martin von Zweigbergk
9ec216a2bb cli_util: take conflicts by value to print_conflicted_paths()
I'm about to make the vector contain `BackendResult<MergedTreeValue>`,
which is harder to work with behind a mutable reference.
2024-11-23 13:53:04 -08:00
Austin Seipp
81251c9382 nix: flake update
Signed-off-by: Austin Seipp <aseipp@pobox.com>
2024-11-23 15:04:57 -06:00
Ilya Grigoriev
118072a9ac cli git push: short alias -N for --allow-new
I commonly end up typing it. It's long and hard to complete
2024-11-23 11:08:26 -08:00
Scott Taylor
472fd35ff1 docs: rewrite conflict marker docs to include new styles
The current docs for conflict markers start out by introducing Git
"diff3" conflict markers, and then discussing how Jujutsu's conflict
markers are different from Git's. I don't think this is the best way to
explain it, because it requires the reader to first understand Git's
conflict markers before they go on to learn Jujutsu's conflict markers.
Instead, I think it's better to explain the conflict markers from
scratch with a more detailed example so that we don't assume any
particular knowledge of Git.

This structure also works better with the new config option, since we
can talk about the default option first, and then go on to explain
alternatives later.
2024-11-23 08:28:47 -06:00
Scott Taylor
26f5d6150c conflicts: add "git" conflict marker style
Adds a new "git" conflict marker style option. This option matches Git's
"diff3" conflict style, allowing these conflicts to be parsed by some
external tools that don't support JJ-style conflicts. If a conflict has
more than 2 sides, then it falls back to the similar "snapshot" conflict
marker style.

The conflict parsing code now supports parsing Git-style conflict
markers in addition to the normal JJ-style conflict markers, regardless
of the conflict marker style setting. This has the benefit of allowing
the user to switch the conflict marker style while they already have
conflicts checked out, and their old conflicts will still be parsed
correctly.

Example of "git" conflict markers:

```
<<<<<<< Side #1 (Conflict 1 of 1)
fn example(word: String) {
    println!("word is {word}");
||||||| Base
fn example(w: String) {
    println!("word is {w}");
=======
fn example(w: &str) {
    println!("word is {w}");
>>>>>>> Side #2 (Conflict 1 of 1 ends)
}
```
2024-11-23 08:28:47 -06:00
Scott Taylor
ec6220d51f merge: add as_slice() method
This was already possible using `merge.iter().as_slice()`, but I think
this is cleaner.
2024-11-23 08:28:47 -06:00
Scott Taylor
54c453ba8d conflicts: extract function for materializing hunk
This will make it easier to add a second code path for Git-style
conflicts in a later commit.
2024-11-23 08:28:47 -06:00
Scott Taylor
d2b06b9cf9 conflicts: add "snapshot" conflict marker style
Adds a new "snapshot" conflict marker style which returns a series of
snapshots, similar to Git's "diff3" conflict style. The "snapshot"
option uses a subset of the conflict hunk headers as the "diff" option
(it just doesn't use "%%%%%%%"), meaning that the two options are
trivially compatible with each other (i.e. a file materialized with
"snapshot" can be parsed with "diff" and vice versa).

Example of "snapshot" conflict markers:

```
<<<<<<< Conflict 1 of 1
+++++++ Contents of side #1
fn example(word: String) {
    println!("word is {word}");
------- Contents of base
fn example(w: String) {
    println!("word is {w}");
+++++++ Contents of side #2
fn example(w: &str) {
    println!("word is {w}");
>>>>>>> Conflict 1 of 1 ends
}
```
2024-11-23 08:28:47 -06:00
Scott Taylor
863cba309f conflicts: extract header output into separate functions
This will make it easier to add separate logic for "snapshot" style
conflict markers in the next commit.
2024-11-23 08:28:47 -06:00
Scott Taylor
3c182687e8 conflicts: replace "if let" with "let else"
This makes the code a bit easier to follow in my opinion.
2024-11-23 08:28:47 -06:00
Scott Taylor
e5cb9f94f6 conflicts: add "ui.conflict-marker-style" config
Adds a new "ui.conflict-marker-style" config option. The "diff" option
is the default jj-style conflict markers with a snapshot and a series of
diffs to apply to the snapshot. New conflict marker style options will
be added in later commits.

The majority of the changes in this commit are from passing the config
option down to the code that materializes the conflicts.

Example of "diff" conflict markers:

```
<<<<<<< Conflict 1 of 1
+++++++ Contents of side #1
fn example(word: String) {
    println!("word is {word}");
%%%%%%% Changes from base to side #2
-fn example(w: String) {
+fn example(w: &str) {
     println!("word is {w}");
>>>>>>> Conflict 1 of 1 ends
}
```
2024-11-23 08:28:47 -06:00
Martin von Zweigbergk
d409a318f4 local_working_copy: fix documentation of TreeState::snapshot()
Since commit e716fe7, the function no longer returns a tree id. That
commit updated the function documentation to match the new behavior,
but then the old documentation was restored in commit ef83f2b, perhaps
by a bad merge conflict resolution.
2024-11-22 23:06:06 -08:00
Martin von Zweigbergk
4d19a0a539 cli: hyphenate "working-copy commit" in --ignore-working-copy help
The help text of `--ignore-working-copy` had one instance with hyphen
and one without.
2024-11-22 23:05:46 -08:00
Yuya Nishihara
a5c96bcf70 settings: simply forward .get_<type>(key) to .get::<type>(key)
There's a subtle difference in error message, but the conversion function to be
called is the same. The error message now includes "for key <key>", which is
nice.
2024-11-23 10:20:27 +09:00
Yuya Nishihara
fc57139340 settings: add forwarding getters, replace most of .config() uses
.get_table() isn't implemented because it isn't cheap to build a HashMap,
and a table of an abstract Value type wouldn't be useful. Maybe we'll
instead provide an iterator of table keys.

.config() is renamed to .raw_config() to break existing callers.
2024-11-23 10:20:27 +09:00
Yuya Nishihara
f239b1e8f0 settings: pass around &UserSettings instead of &config::Config consistently
Ui::with_config() is unchanged because I'm not sure if UserSettings should be
constructed earlier. I assume UserSettings will hold an immutable copy of
LayerdConfigs object, whereas Ui has to be initialized before all config layers
get loaded.
2024-11-23 10:20:27 +09:00
Yuya Nishihara
6d26d53eab config: add type alias for config::ConfigError
We'll probably add our own ConfigError type later.
2024-11-23 10:20:27 +09:00
Yuya Nishihara
c0250f1904 config: rename jj_cli::config::ConfigError to ConfigEnvError
It was confusing because we often refer to config::ConfigError as ConfigError.
2024-11-23 10:20:27 +09:00
Yuya Nishihara
5ad0f3dcf6 config: extract ConfigNamePathBuf to jj-lib
I'm planning to rewrite config store layer by leveraging toml_edit instead of
the config crate. It will allow us to merge config overlays in a way that
deprecated keys are resolved within a layer prior to merging, for example.

This patch moves ConfigNamePathBuf to jj-lib where new config API will be
hosted. We'll probably extract LayeredConfigs to this module, but we'll first
need to split environment dependencies from it.
2024-11-23 10:20:27 +09:00
Scott Taylor
6e959fa12c conflicts: allow stripped trailing whitespace in diffs
Some editors strip trailing whitespace on save, which breaks any diffs
which have context lines, since the parsing function expects them to
start with a space. There's no visual difference between " \n" and "\n",
so it seems reasonable to accept both.
2024-11-22 18:00:05 -06:00
Scott Taylor
efacbcbd45 conflicts: demo failed parse of diff with empty line 2024-11-22 18:00:05 -06:00
Scott Taylor
9674852dc7 conflicts: allow CRLF line endings on conflict markers
Currently, conflict markers ending in CRLF line endings aren't allowed.
I don't see any reason why we should reject them, since some
editors/tools might produce CRLF automatically on Windows when saving
files, which would break the conflicts otherwise.
2024-11-22 18:00:05 -06:00
Scott Taylor
ee7f829d4c conflicts: demo failed parse of markers with CRLF 2024-11-22 18:00:05 -06:00
Lars Francke
ffe5519fd0 Update tutorial for 0.23 and clarify diffedit 2024-11-22 21:23:12 +01:00
Tim Janik
86a2a2b1e9 docs/git-compatibility.md: preserve working copy files when co-locating
Use `jj new && jj undo` instead of `jj new @-` at the end of converting to a
co-located repository, because the latter "stashes" newly added working copy
changes into a sibling commit.

See also: https://github.com/martinvonz/jj/discussions/4945

Signed-off-by: Tim Janik <timj@gnu.org>
2024-11-22 19:20:36 +01:00
dependabot[bot]
d5bf1489e4 cargo: bump proc-macro2 in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [proc-macro2](https://github.com/dtolnay/proc-macro2).


Updates `proc-macro2` from 1.0.91 to 1.0.92
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.91...1.0.92)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-22 10:54:14 -06:00
Benjamin Tan
4f2e72a140 cli: simplify-parents: avoid multiple rebases of the same commits
The previous iteration of `jj simplify-parents` would only reparent the
commits in the target set in the `MutableRepo::transform_descendants`
callback. The subsequent `MutableRepo::rebase_descendants` call invoked
when the transaction is committed would then rebase all descendants of
the target set, which includes the commits in the target set again.

This commit updates the `MutableRepo::transform_descendants` callback to
perform rebasing of all descendants within the callback. All descendants
of the target set will only be rebased at most once.
2024-11-22 20:23:03 +08:00
Benjamin Tan
1ae8f0c77a tests: add additional test case for jj simplify-parents
This test shows that the current implementation of `jj simplify-parents`
perform unnecessary rebases of descendant commits.
2024-11-22 20:23:03 +08:00
Yuya Nishihara
c6bb019d41 diff: optimize allocation of histogram entries for unique words
```
group                             new                     old
-----                             ---                     ---
bench_diff_git_git_read_tree_c    1.00     34.5±0.26µs    1.32     45.7±0.11µs
bench_diff_lines/modified/10k     1.00     28.2±0.10ms    1.19     33.5±0.69ms
bench_diff_lines/modified/1k      1.00      2.6±0.01ms    1.15      3.0±0.01ms
bench_diff_lines/reversed/10k     1.00     21.5±0.22ms    1.08     23.3±0.18ms
bench_diff_lines/reversed/1k      1.00   364.8±11.96µs    1.22    445.1±8.99µs
bench_diff_lines/unchanged/10k    1.00  1761.3±13.85µs    1.66      2.9±0.07ms
bench_diff_lines/unchanged/1k     1.00    163.6±1.25µs    1.47    240.7±2.72µs
```

```
% hyperfine --sort command --warmup 3 --runs 5 -L bin jj-0,jj-1 \
  'target/release-with-debug/{bin} --ignore-working-copy \
  file annotate lib/src/revset.rs'
Benchmark 1: target/release-with-debug/jj-0 ..
  Time (mean ± σ):      1.144 s ±  0.011 s    [User: 1.088 s, System: 0.053 s]
  Range (min … max):    1.131 s …  1.159 s    5 runs

Benchmark 2: target/release-with-debug/jj-1 ..
  Time (mean ± σ):      1.026 s ±  0.008 s    [User: 0.975 s, System: 0.048 s]
  Range (min … max):    1.015 s …  1.035 s    5 runs
```
2024-11-22 08:20:55 +09:00
Yuya Nishihara
656a614f29 signing: pass &UserSettings in to backends
If we add our own config storage implementation, .get_<type>() functions will
be moved to UserSettings object.
2024-11-22 08:20:45 +09:00
Yuya Nishihara
7a7962bb9f signing: propagate config errors 2024-11-22 08:20:45 +09:00
Yuya Nishihara
5e13a4a719 signing: remove unused SignInitError::Backend variant, simplify error handling
Since signing backends shouldn't do non-trivial work at the initialization
stage, the error type would never be categorized as an internal error.
2024-11-22 08:20:45 +09:00
Yuya Nishihara
99c269816c cargo: bump toml_edit to 0.22.22
toml_edit now has separate immutable and mutable Document types. Other than
that, the API should be compatible.
2024-11-22 08:20:35 +09:00
Austin Seipp
e060ef20c1 cli: delete deprecated jj merge command
This has been deprecated for almost a year now.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2024-11-21 11:50:03 -06:00
Austin Seipp
037134147d cli: delete deprecated jj checkout command
This has been deprecated for almost a year now.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2024-11-21 11:50:03 -06:00
dependabot[bot]
ec30979e0e github: bump EmbarkStudios/cargo-deny-action
Bumps the github-dependencies group with 1 update: [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action).


Updates `EmbarkStudios/cargo-deny-action` from 2.0.1 to 2.0.2
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](8371184bd1...f87fcad0e6)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-21 10:20:03 -06:00
dependabot[bot]
6d3d5319dd cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [proc-macro2](https://github.com/dtolnay/proc-macro2) and [syn](https://github.com/dtolnay/syn).


Updates `proc-macro2` from 1.0.89 to 1.0.91
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.89...1.0.91)

Updates `syn` from 2.0.87 to 2.0.89
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.87...2.0.89)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-21 10:19:47 -06:00
Yuya Nishihara
7906b3f4a5 annotate: remove unneeded BString<->Vec round-trip 2024-11-21 10:50:37 +09:00
Yuya Nishihara
59a79fdcc0 conflicts: extract materialize_merge_result_to_bytes() helper
We have many callers of materialize_merge_result() who just want in-memory
buffer.
2024-11-21 10:50:37 +09:00
Yuya Nishihara
4cdead34b4 conflicts: make materialize_merge_result() accept reference type
Because the output of diff.hunks() is a list of [&BStr]s, a Merge object
reconstructed from a diff hunk will be Merge<&BStr>. I'm not pretty sure if
we'll implement conflict diffs in that way, but this change should be harmless
anyway.
2024-11-21 10:50:37 +09:00
Yuya Nishihara
2aa913b035 conflicts: extract inner block of materialize_merge_result()
I'm going to make materialize_merge_result() accept reference type. This patch
extracts large non-generic part to a separate function.
2024-11-21 10:50:37 +09:00
Yuya Nishihara
5cc0bd0950 rewrite: fix duplicated commits to be rebased onto destination
I believe this was an oversight. "jj duplicate" should duplicate commits (=
patches), not trees.

This patch adds a separate test file because test_rewrite.rs is pretty big, and
we'll probably want to migrate CLI tests to jj-lib.
2024-11-21 10:49:51 +09:00
dependabot[bot]
a6c18e8353 github: bump the github-dependencies group with 2 updates
Bumps the github-dependencies group with 2 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `astral-sh/setup-uv` from 3.2.2 to 3.2.3
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](2e657c127d...e779db7426)

Updates `github/codeql-action` from 3.27.4 to 3.27.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ea9e4e3799...f09c1c0a94)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-20 11:42:59 -06:00
Remo Senekowitsch
ac0e531de5 cli: provide short flags for rev ranges 2024-11-20 18:20:24 +01:00
Emily
c0a9e20222 docs: tighten the "previewing docs" section 2024-11-20 06:06:53 +01:00
Emily
cedaa5bb00 docs: migrate from poetry to uv
Our docs are built with MkDocs, which requires Python and several deps.

Previously those deps were managed with Poetry, which is also written in Python.
This commit replaces Poetry with `uv`, a Rust-based Python
project/package manager, and thus removes several steps from the docs
build process.

Before:

  <install Python>
  <install pipx>
  pipx install poetry
  poetry install
  poetry run -- mkdocs serve

After:

  <install uv>
  uv run mkdocs serve
2024-11-20 06:06:53 +01:00
Yuya Nishihara
1973c712a3 log: emit working-copy branch first if included in the revset
The working-copy revision is usually the latest commit, but it's not always
true. This patch ensures that the wc branch is emitted first so the graph node
order is less dependent on rewrites.
2024-11-20 10:50:16 +09:00
Yuya Nishihara
fb79f2024d tests: do Result wrapping/unwrapping by graph helper 2024-11-20 10:50:16 +09:00
Yuya Nishihara
05c7da3db0 diff: reuse precomputed hash values
This isn't always fast because it increases the chance of cache miss, but in
practice, it makes "jj file annotate" faster. It's still slower than
"git blame", though.

Maybe we should also change the hash function.

```
group                             new                     old
-----                             ---                     ---
bench_diff_git_git_read_tree_c    1.00     45.2±0.38µs    1.29     58.4±0.32µs
bench_diff_lines/modified/10k     1.00     32.7±0.24ms    1.05     34.4±0.17ms
bench_diff_lines/modified/1k      1.00      2.9±0.00ms    1.06      3.1±0.01ms
bench_diff_lines/reversed/10k     1.00     22.7±0.18ms    1.02     23.2±0.29ms
bench_diff_lines/reversed/1k      1.00    439.0±9.46µs    1.19    523.9±6.05µs
bench_diff_lines/unchanged/10k    1.00      2.9±0.06ms    1.20      3.5±0.02ms
bench_diff_lines/unchanged/1k     1.00    240.8±1.03µs    1.30    312.1±1.05µs
```

```
% hyperfine --sort command --warmup 3 --runs 10 -L bin jj-0,jj-1 \
  'target/release-with-debug/{bin} --ignore-working-copy file annotate lib/src/revset.rs'
Benchmark 1: target/release-with-debug/jj-0 ..
  Time (mean ± σ):      1.604 s ±  0.259 s    [User: 1.543 s, System: 0.057 s]
  Range (min … max):    1.348 s …  1.917 s    10 runs

Benchmark 2: target/release-with-debug/jj-1 ..
  Time (mean ± σ):      1.183 s ±  0.026 s    [User: 1.118 s, System: 0.062 s]
  Range (min … max):    1.155 s …  1.237 s    10 runs
```
2024-11-20 10:36:08 +09:00
Yuya Nishihara
2c653564fd diff: extract narrowed view type from DiffSource, add type-safe local index
I'm going to add a Vec of precomputed hashes, and the Vec will be owned by
DiffSource.
2024-11-20 10:36:08 +09:00
Yuya Nishihara
ed18b5bdc5 diff: cache hash values by Histogram
Since patience diff is recursive, it makes some sense to reuse precomputed
hash values. This patch migrates Histogram to remembering hashed values. The
precomputed values will be cached globally by DiffSource.

Technically, Histogram doesn't have to keep a separate copy of hash values, but
this appears to give better perf than slicing text and hash value from two Vecs.
2024-11-20 10:36:08 +09:00
Luke Randall
068fa0f37e revset: allow tags() to take a pattern for an argument
This makes it more consistent with `bookmarks()`.

Co-authored-by: Austin Seipp <aseipp@pobox.com>
2024-11-20 00:47:23 +00:00
dependabot[bot]
c5b93a0c36 cargo: bump rustix in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [rustix](https://github.com/bytecodealliance/rustix).


Updates `rustix` from 0.38.40 to 0.38.41
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.40...v0.38.41)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-19 16:09:40 -06:00
Herman J. Radtke III
3be6f03938 docs: git blame comparison
Add comparison for git blame and jj file annotate.
2024-11-19 09:58:28 -08:00
Essien Ita Essien
173c11cf97 Ignore .cargo/insta.yaml 2024-11-19 13:57:52 +00:00
Yuya Nishihara
296961cb19 cli: git push: do not push new bookmarks by default
If you have multiple remotes to push to, you might want to keep some changes
(such as security patches) in your private fork. Git CLI has one upstream remote
per branch, but jj supports multiple tracking remotes, and therefore "jj git
push" can start tracking new remotes automatically.

This patch makes new bookmarks not eligible for push by default. I considered
adding a warning, but it's not always possible to interrupt the push shortly
after a warning is emitted.

--all implies --allow-new because otherwise it's equivalent to --tracked. It's
also easier to write a conflict rule with --all/--deleted/--tracked than with
two of them.

-c/--change doesn't require --allow-new because it is the flag to create new
tracking bookmark.

#1278
2024-11-19 21:11:22 +09:00
Ilya Grigoriev
0c6c47101f nightly clippy fixes 2024-11-18 18:49:05 -08:00
Martin von Zweigbergk
7323c2e5e0 cli: delete the deprecated jj move command
I hope we'll have support for copies and renames in not too long. It's
good to have as many versions before that as possible without support
for `jj move`, in case we want to later use that to record a moved
file (maybe as an alias for `jj file move`).
2024-11-18 13:24:16 -08:00
dependabot[bot]
b70c700cd3 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [libc](https://github.com/rust-lang/libc) and [serde_json](https://github.com/serde-rs/json).


Updates `libc` from 0.2.162 to 0.2.164
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.162...0.2.164)

Updates `serde_json` from 1.0.132 to 1.0.133
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.132...v1.0.133)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 12:03:31 -06:00
Remo Senekowitsch
ecd64aae8e completion: teach config about keys
There are two known limitations right now:

- Only statically known keys are suggested.

- Keys that the user did not set are still suggested for `jj config get`.
  Running that suggestion may result in an error. The error message will be
  appropriate though and there is some value in letting the user know that
  this config value theoretically exists. Some users may try to explore what
  configurations are available via the completions.
2024-11-18 13:19:02 +01:00
Remo Senekowitsch
1a121eae99 completion: teach forget about workspaces 2024-11-18 12:33:39 +01:00
Yuya Nishihara
200581164e completion: pad empty argument at $_CLAP_COMPLETE_INDEX
This is a workaround for command name completion. On zsh, the complete position
is specified by $_CLAP_COMPLETE_INDEX, and an empty argument isn't padded. So,
for "jj <TAB>", { args = ["jj"], index = 1 } is provided, and our expand_args()
helpfully fills in the default command "log". Since args[index] is now "log",
no other commands are listed.
2024-11-18 17:06:24 +09:00
Yuya Nishihara
896ab5f179 cli: fix "config unset" to not delete a whole table
It can be a footgun, and is inconsistent because other mutation commands can't
overwrite a table.
2024-11-18 09:15:40 +09:00
Ilya Grigoriev
694e20d255 README: Add libera IRC badge 2024-11-17 13:36:49 -08:00
Martin von Zweigbergk
a22620d30c docs: say that support for Git's remote config is partial
#4889
2024-11-16 17:41:08 -08:00
Tim Janik
e8bbd89ec1 docs/community_tools.md: add paragraph about jj-fzf
Signed-off-by: Tim Janik <timj@gnu.org>
2024-11-16 23:49:03 +01:00
Tim Janik
68d4ec7f0a docs/community_tools.md: restore alphabetical order
Signed-off-by: Tim Janik <timj@gnu.org>
2024-11-16 23:49:03 +01:00
Martin von Zweigbergk
9fc3255238 docs: try to clarify that changes are not a first-class concept 2024-11-16 14:20:37 -08:00
Martin von Zweigbergk
e11bc5dc04 log: describe how to see all revisions, point to revset docs
Perhaps it would be nice to point to the revset docs from every
argument that takes a revset, or perhaps that would be too
verbose. `jj log` is perhaps where most people first run into revset
syntax, so I hope pointing to the docs from there is a good start.
2024-11-16 14:00:38 -08:00
Martin von Zweigbergk
77477aef99 log: describe the different graph node symbols in help text
It's not obvious what the symbols mean. We've had a few questions
about it on Discord recently.
2024-11-16 11:03:04 -08:00
Philip Metzger
eb91547e52 docs: Make the Roadmap a toplevel website entry
This also fixes the Xethub link, by pointing it to the web archive, which no longer is available.
2024-11-16 11:11:59 +01:00
Remo Senekowitsch
2def0ced7d completion: teach operation commands about ids 2024-11-16 11:01:42 +01:00
Remo Senekowitsch
dd6479f104 completion: teach commands about revisions 2024-11-16 10:30:15 +01:00
Remo Senekowitsch
68c0fd6350 completion: add help text for bookmark names 2024-11-16 10:00:24 +01:00
Ilya Grigoriev
9d31e74992 README: add Contributing bullet
People will often look for contributing instructions from
https://github.com/martinvonz/jj, this makes it easier
to find.

It will also lead more people to read the contributing
docs on the website as opposed to GitHub. This is becoming
desireable, see the discussion at
https://github.com/martinvonz/jj/pull/4822#discussion_r1844611306
2024-11-15 20:30:12 -08:00
Ilya Grigoriev
7f971df3cd README: Remove the "build passing" badge
It feels extraneous. People can just look at whether the GitHub UI shows a checkmark
next to the commit.
2024-11-15 19:18:35 -08:00
dependabot[bot]
4729c77852 github: bump DeterminateSystems/nix-installer-action
Bumps the github-dependencies group with 1 update: [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action).


Updates `DeterminateSystems/nix-installer-action` from 15 to 16
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](b92f66560d...e50d5f73bf)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 17:22:18 -06:00
Benjamin Tan
4db4f413a7 revset: add fork_point function
This can be used to find the fork point (best common ancestors) of a
revset with an arbitrary number of commits, which cannot be expressed
currently in the revset language.
2024-11-16 04:08:01 +08:00
Yuya Nishihara
1ed2ef0405 cli: git: document about default fetch/push --remote 2024-11-15 22:32:36 +09:00
Yuya Nishihara
7ed829ecf4 cli: git push: borrow repo from transaction
This forces us to think about which repo should be used. It doesn't matter in
find_bookmarks_to_push() because moved "push-{change_id}" bookmarks are
prioritized. It doesn't matter in print_commits_ready_to_push() either, which
uses the repo only for ancestry lookup, but I think tx.repo() is better here.
2024-11-15 22:32:36 +09:00
Benjamin Tan
5a793f61d6 cli: duplicate: add --destination, --insert-after, and --insert-before options 2024-11-15 19:42:13 +08:00
Benjamin Tan
5138836cdc rewrite: add duplicate_commits{,onto_parents} functions 2024-11-15 19:42:13 +08:00
Benjamin Tan
0a7f6bd8aa rewrite: extract compute_commits_heads function
This will be shared between `move_commits` and the new
`duplicate_commits` function to be added.
2024-11-15 19:42:13 +08:00
Benjamin Tan
43169680ce cli: warn if trunk() cannot be resolved after each transaction
Closes #4846.
2024-11-15 18:31:28 +08:00
dependabot[bot]
68b72ad51f github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.3 to 3.27.4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](396bb3e453...ea9e4e3799)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 08:07:54 +09:00
dependabot[bot]
78c45a3af3 cargo: bump the cargo-dependencies group across 1 directory with 2 updates
Bumps the cargo-dependencies group with 2 updates in the / directory: [bstr](https://github.com/BurntSushi/bstr) and [clap_complete](https://github.com/clap-rs/clap).


Updates `bstr` from 1.10.0 to 1.11.0
- [Commits](https://github.com/BurntSushi/bstr/compare/1.10.0...1.11.0)

Updates `clap_complete` from 4.5.37 to 4.5.38
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.37...clap_complete-v4.5.38)

---
updated-dependencies:
- dependency-name: bstr
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 08:07:06 +09:00
Remo Senekowitsch
e4fdc91b69 completion: teach git commands about bookmarks 2024-11-14 21:14:35 +01:00
Remo Senekowitsch
26ccc1c4cc completion: teach jj about aliases
This makes completions suggest aliases from the user or repository
configuration. It's more useful for long aliases that aren't used very often,
but serve the purpose of "executable documentation" of complex and useful jj
commands.

An earlier patch "resolved" aliases, meaning that any arguments following an
alias would be completed as if the normal command had been used. So it only
made sure that using aliases doesn't degrade the rest of the completions.
Commit ID: 325402dc9463ccaa70822069b3e94716d9cc7417
2024-11-14 19:51:11 +01:00
Remo Senekowitsch
ef8e664555 deps: update clap 2024-11-14 19:51:11 +01:00
Joaquín Triñanes
5e8c7660a6 nix: Remove apps output
It was redundant once mainProgram was set for the package
2024-11-14 12:19:13 -06:00
Joaquín Triñanes
f1a9f62e3d nix: Add meta field to package
The most relevant part (and the reason for this change) is the addition
of the `mainProgram` attribute. This allows getting the executable name
from inside nix expressions with ease:

```
# before
lib.getExe' jujutsu "jj"
# or
"${jujutsu}/bin/jj"
```

```
# now
lib.getExe jujutsu
```
2024-11-14 12:19:13 -06:00
Robin Stocker
560d66ecee docs: switch all toml examples to heading notation 2024-11-14 16:29:49 +00:00
dploch
e1416981aa cli_util: consolidate update_stale command fully into cli_util 2024-11-14 11:12:02 -05:00
dploch
49890fa2d9 cli_util: enable automatic update of stale workspaces if config is set
This significantly reduces toil for multi-workspace users, resolving issue #3820
2024-11-14 11:12:02 -05:00
dploch
2c54848e63 cli_util: move snapshotting of the stale working copy into cli_util
This centralizes the logic so cli helpers can use it.
2024-11-14 11:12:02 -05:00
dploch
afb07110b2 cli_util: move update-stale checkout operation into cli_util
This centralizes the logic so the cli helpers can use it.
2024-11-14 11:12:02 -05:00
dploch
0a5bc2bbed workspace: move recovery commit logic into lib for sharing
This is to facilitate automatic update-stale in extensions and in the CommandHelper layer.
2024-11-14 11:12:02 -05:00
dploch
afe25464fe working_copy: move freshness calculation into lib for sharing
This is to facilitate automatic update-stale in extensions and in the CommandHelper layer.
2024-11-14 11:12:02 -05:00
Remo Senekowitsch
c656fd30af completion: teach commands about bookmark names 2024-11-14 16:45:04 +01:00
Remo Senekowitsch
7c5235345a completion: make config available
Completion functions need information from the config, e.g. for completing
user-configured aliases.
2024-11-14 16:45:04 +01:00
Remo Senekowitsch
966eeb952e completion: teach remote about existing names 2024-11-14 16:08:44 +01:00
Yuya Nishihara
bfb7613d5d cargo: bump gix to 0.67.0 (includes minor behavior change)
- gix::object::tree::diff::change::Event::Rewrite is flattened
- diff options are extracted to separate type
  2b81e6c8bd
- signature text now includes a trailing newline
  4a6bbb1b79

The last change means that our SecureSig { sig } returned by GitBackend is now
terminated by '\n'. I think this is harmless since textual signature is usually
ends with '\n'.
2024-11-14 22:38:04 +09:00
Yuya Nishihara
eed32954b2 cargo: import fix_filter from gix::filter::plumbing
I don't think we need to declare these dependencies separately because both
are the optional dependencies enabled by the git feature.

We'll also need the gix's "attributes" feature at some point.
> attributes - Query attributes and excludes. Enables access to pathspecs,
> worktree checkouts, filter-pipelines and submodules.
https://docs.rs/gix/0.67.0/gix/index.html#feature-flags
2024-11-14 22:38:04 +09:00
Yuya Nishihara
168f07283a cargo: whitespace cleanup, sort dependencies 2024-11-14 22:38:04 +09:00
Yuya Nishihara
48521d66b4 stacked_table: add context to error type, propagate lock error 2024-11-14 22:37:54 +09:00
Yuya Nishihara
aaf7f33804 lock: propagate error from lock(), remove panic from unlock path 2024-11-14 22:37:54 +09:00
Yuya Nishihara
6ffd4d4f63 lock: compile and test fallback module on all platforms
This should help detect Windows issues early. Also fixed clippy warning.
2024-11-14 22:37:54 +09:00
Yuya Nishihara
1a2479cd2f lock: reorganize platform-specific implementations as sub modules
This will help rust-analyzer scan both fallback and unix impls.
2024-11-14 22:37:54 +09:00
Martin von Zweigbergk
dfc67e7051 simple_op_heads_store: propagate errors when trying to remove op head
Any errors other than `NotFound` are unexpected.
2024-11-13 23:05:24 -08:00
Martin von Zweigbergk
de6da1a088 transaction: propagate errors from commit() 2024-11-13 23:05:24 -08:00
Martin von Zweigbergk
73186c1da4 op_heads_store: allow methods to return errors
This was crashing our server at Google (when attempting to read from a
repo that didn't exist).
2024-11-13 23:05:24 -08:00
Martin von Zweigbergk
5b844f630c cargo: upgrade to sapling-renderdag
There is now an updated version of `esl01-renderdag` published from
the Sapling repo, so let's use that. That lets us remove the
`bitflags` 1.x dependency and the `itertools` 0.10.x non-dev
dependency.
2024-11-13 09:38:25 -08:00
Remo Senekowitsch
a650af3fcd completion: improve docs for activation
It was not clear if users should activate both sets of completions if they
wanted the dynamic ones.

The shell commands for activating the dynamic completions are aligned with the
static version. Users can choose / are responsible themselves to add the
activation to their shell config if they want them in every shell instance.

For fish specifically, `script | source` is more idiomatic than
`source (script | psub)`.
2024-11-13 18:13:54 +01:00
dependabot[bot]
8292d01c77 github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.2 to 3.27.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9278e42166...396bb3e453)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-13 15:30:29 +00:00
dependabot[bot]
1e527dd293 cargo: bump serde in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [serde](https://github.com/serde-rs/serde).


Updates `serde` from 1.0.214 to 1.0.215
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.214...v1.0.215)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 23:30:52 +00:00
dependabot[bot]
7f0eae0be8 github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.1 to 3.27.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](4f3212b617...9278e42166)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 15:35:44 -06:00
Yuya Nishihara
7be4904982 tests: fix flakiness in shallow Git repo test
This test reliably failed if I dropped tv_nsec part from statx().

Since we reload the repo now, several assertions get "fixed". I've added
index().has_id() test to clarify that it's still broken.
2024-11-12 20:27:51 +09:00
Benjamin Tan
9ea91dc02a cli: rebase -b: add support for --insert-after and --insert-before options 2024-11-12 16:38:17 +08:00
Benjamin Tan
1c1117f6fb tests: modify rebase --after --before test case
This commit modifies the commit graph used in the `rebase --after
--before` test case to add an additional branch of commits for use in
tests for `rebase -b` along with `--after --before`.
2024-11-12 16:38:17 +08:00
Benjamin Tan
d6a2034e01 cli: rebase: extract out common print_move_commits_stats function
This commit extracts out the common code printing out the
`MoveCommitsStats` information into a shared function. The printed
output was also inconsistent between `-r` and `-s`/`-b` code paths, so I
standardized it to say "Rebased ? commits onto destination" for both
cases.
2024-11-12 16:04:53 +08:00
Yuya Nishihara
062a1bceb9 local_working_copy: on check out, skip entries conflicting with untracked dirs
This seems more consistent because file->directory conflicts are skipped.
2024-11-12 16:12:12 +09:00
Yuya Nishihara
f3a75c5c46 local_working_copy: on check out, ignore diff of Git submodule ids
This is different from skipped paths because the file state has to remain as
FileType::GitSubmodule in order to ignore the submodule directory when
snapshotting.

Fixes #4825.
2024-11-12 16:12:12 +09:00
Yuya Nishihara
4983db563f local_working_copy: migrate Git submodule test to MergedTreeBuilder
I also removed tx.commit() because the test doesn't rely on the committed
operation.
2024-11-12 16:12:12 +09:00
Benjamin Tan
1aad724798 repo: remove MutableRepo::rebase_descendants_return_map
This function is merely a simple wrapper around
`MutableRepo::rebase_descendants_with_options_return_map`.
2024-11-12 14:00:00 +08:00
Benjamin Tan
44ec7d0ee9 repo: remove MutableRepo::rebase_descendants_with_options
This function had no callers within the codebase.
2024-11-12 14:00:00 +08:00
Benjamin Tan
cb1e0fbafc rewrite: rebase_commits_with_options: avoid cloning of new parents 2024-11-12 14:00:00 +08:00
dependabot[bot]
793e9f100f github: bump github/codeql-action in the github-dependencies group
Bumps the github-dependencies group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.27.0 to 3.27.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](662472033e...4f3212b617)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 21:23:15 -06:00
dependabot[bot]
d1261fc829 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [rustix](https://github.com/bytecodealliance/rustix) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `rustix` from 0.38.39 to 0.38.40
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.39...v0.38.40)

Updates `thiserror` from 1.0.68 to 1.0.69
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.68...1.0.69)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 21:23:02 -06:00
Emily
7d9e9a1434 docs: remove trailing whitespace in .md 2024-11-12 01:16:08 +01:00
Emily
f56f07bd54 editorconfig: only skip trimming whitespace for .rs files 2024-11-12 01:16:08 +01:00
Emily
235d57c053 docs: beginnings of an 'editor setup' section 2024-11-12 01:16:08 +01:00
Yuya Nishihara
ce3436b92b cli: add "absorb" machinery and command
The destination commits are selected based on annotation, which I think is
basically the same as "hg absorb" (except for handling of consecutive hunks.)
However, we don't compute a full interleaved delta right now, and the hunks are
merged in the same way as "jj squash". This means absorbed hunks might produce
conflicts if no context lines exist. Still I think this is more intuitive than
selecting destination commits based on patch commutativity.

I've left inline comments to the tests where behavior is different from "hg
absorb", but these aren't exhaustively checked.

Closes #170
2024-11-12 08:26:42 +09:00
Yuya Nishihara
3144a8cb9e annotate: add line_ranges() and compact_line_ranges() iterator
They allow callers to test range overlaps with e.g. diff hunks. "jj absorb"
will leverage compact_line_ranges().
2024-11-12 08:26:42 +09:00
Yuya Nishihara
077bac8be1 annotate: add low-level function to specify starting file content
In "jj absorb", we'll need to calculate annotation from the parent tree. It's
usually identical to the tree of the parent commit, but this is not true for a
merge commit. Since I'm not sure how we'll process conflict trees in general,
this patch adds a minimal API to specify a single file content, not a
MergedTree.
2024-11-12 08:26:42 +09:00
Yuya Nishihara
85e0a8b068 annotate: add option to not search all ancestors of starting commit
The primary use case is to exclude immutable commits when calculating line
ranges to absorb. For example, "jj absorb" will build annotation of @ revision
with domain = mutable().
2024-11-12 08:26:42 +09:00
Remo Senekowitsch
325402dc94 completion: resolve aliases 2024-11-11 21:41:42 +01:00
Ilya Grigoriev
bfbd04b083 cli: document b as a "default alias"
Unlike other documented aliases, it can be redefined by the user.
However, I think it's important for people to be informed that `b`
exists.

I left `amend`, `unamend`, and `ci` undocumented, since I think we were
considering removing or at least discouraging them.
2024-11-11 09:44:18 -08:00
Benjamin Tan
0e67ef9184 rewrite: avoid abandoned commit parent lookup in rebase_commit_with_options
This is an optimization to avoid fetching the parent commit of an
abandoned commit after rebasing, given that it might not even be used.
2024-11-12 01:33:12 +08:00
Benjamin Tan
9bd7e7707f repo: add docs for MutableRepo::rebase_descendants_* functions 2024-11-12 01:33:12 +08:00
Benjamin Tan
d7f929fefb repo: group MutableRepo::rebase_descendants_* functions together 2024-11-12 01:33:12 +08:00
Benjamin Tan
18faaf72a3 rewrite: remove DescendantRebaser
Due to the gradual rewrite to use the
`MutableRepo::transform_descendants` API, `DescendantRebaser` is no
longer used in `MutableRepo::rebase_descendants`, and is only used
(indirectly through `MutableRepo::rebase_descendants_return_map`) in
`rewrite::squash_commits`.

`DescendantRebaser` is removed since it contains a lot of logic
similar to `MutableRepo::transform_descendants`. Instead,
`MutableRepo::rebase_descendants_with_options_return_map` is rewritten
to use `MutableRepo::transform_descendants` directly, and the other
`MutableRepo::rebase_descendants_{return_map,with_options}` functions
call `MutableRepo::rebase_descendants_with_options_return_map` directly.

`MutableRepo::rebase_descendants_return_rebaser` is also removed.
2024-11-12 01:33:12 +08:00
Benjamin Tan
51675a2c14 cli: include commit summary when attempting to modify an immutable commit
Previously, attempting to modify an immutable commit only showed the
ID of the commit being modified, which wasn't very helpful when trying
to figure out which immutable commit is being modified at a quick
glance.

This commit prints the commit summary as a hint to make it simpler for
the user to see what the immutable commit is without having to run
`jj show <commit-id>`.
2024-11-12 01:17:41 +08:00
Martin von Zweigbergk
6f38131193 cargo-deny: temporarily allow chrono-english crate
https://rustsec.org/advisories/RUSTSEC-2024-0395 recommends migrating
off od `chrono-english`, but that doesn't seem easy. I've spent a few
hours on it already.
2024-11-11 07:04:21 -08:00
Martin von Zweigbergk
02486dc064 fallback: replace use of backoff crate of by own implementation
https://rustsec.org/advisories/RUSTSEC-2024-0384 says to migrate off
of the `instant` crate because it's unmaintained. We depend on it only
via the `backoff` crate. That crate also seems unmaintained. So this
patch replaces our use of `backoff` by a custom implementation.

I initially intended to migrate to the `backon` crate, but that made
`lock::tests::lock_concurrent` tests fail. The test case spawns 72
threads (on my machine) and lets them all lock a file, and then it
waits 1 millisecond before releasing the file lock. I think the
problem is that their version of jitter is implemented as a random
addition of up to the initial backoff value. In our case, that means
we would add at most a millisecond. The `backoff` crate, on the other
hand does it by adding -50% to +50% of the current backoff value. I
think that leads to a much better distribution of backoffs, while
`backon`'s implementation means that only a few threads can lock the
file for each backoff exponent.
2024-11-11 07:04:21 -08:00
Yuya Nishihara
6739dccc6d templater: add != operator as user would probably expect that it exists 2024-11-10 21:41:24 +09:00
Yuya Nishihara
b556fc6093 templater: add abstraction to implement equality operation per type
Maybe we can add comparison of ids, commits, etc., but I don't have a practical
use case right now. If we add lt/gt, it might make sense to implement them on
Timestamp type.

I also changed lhs.and_then(..) to (lhs, rhs).map(..) since we don't need
short-circuiting behavior here.
2024-11-10 21:41:24 +09:00
Remo Senekowitsch
c9e751ae6e completion: teach rename about local bookmarks 2024-11-10 07:26:22 +01:00
dploch
5cbd348f2f config-schema: add missing docs for auto-track 2024-11-09 12:58:01 -05:00
Remo Senekowitsch
db2b5890f8 util: add exec command for arbitrary aliases 2024-11-09 11:49:33 +01:00
Emily
d77ca1526a cli: reference config settings for 'jj log -T' and 'jj git push -c' 2024-11-09 02:46:00 +01:00
Yuya Nishihara
7d34194502 cli: do not swallow error when checking working copy that became immutable
.is_err() doesn't always mean the commit is immutable.

Fixes #4798
2024-11-09 09:40:29 +09:00
Martin von Zweigbergk
fd271d39ad cli: make jj desc and jj st aliases hidden
Visible aliases interfere with shell completion, at least in Fish. For
example, `jj des<tab>` stops as `jj desc` without adding a space
afterwards, which make me stop for a second and wonder if I need to
add more letters. They also show up in the auto-complete menu.

I added "[aliases: <name>]" as part of the first line of the help text
so the `jj help` output still looks the same.
2024-11-08 13:57:08 -08:00
Martin von Zweigbergk
cd88bafd05 cli: workspace update-stale: set description on recovery commit
The recover commit we create in some cases (when an operation has been
lost) doesn't currently have a description. That makes it easy to miss
that it's special.
2024-11-08 12:32:24 -08:00
dploch
41631bc0e6 test_git: fix some clippy ref errors 2024-11-08 13:59:37 -05:00
Martin von Zweigbergk
1240487cb6 next/prev: delete obsolete help text about inferring --edit
We don't infer `--edit` since 37421583b259.
2024-11-08 10:40:52 -08:00
dependabot[bot]
2aadae14a6 cargo: bump tempfile in the cargo-dependencies group
Bumps the cargo-dependencies group with 1 update: [tempfile](https://github.com/Stebalien/tempfile).


Updates `tempfile` from 3.13.0 to 3.14.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.13.0...v3.14.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-09 00:32:20 +08:00
Austin Seipp
643d772bab github: cancel in-progress builds on stale PRs
When a pull request is pushed multiple times in sequence (e.g. to fix small
errors that are noticed post-submission), a backlog of builds ends up needing
to be cleared out before your new update can be built. If you push N times, N
builds are queued and need to clear out first.

This can cause higher latency for *every* PR since the pool of public runners
is shared, and in general seems uneconomical and inefficient since 99% of the
time you want to build the new version ASAP.

Luckily GHA has a universal solution to this: use the `concurrency` directive
to group all builds for a PR under a name, and when a new build appears in that
group, cancel all builds in the group that are in-progress.

Taken from this useful blog post: "Simple trick to save environment and money
when using GitHub Actions"

https://turso.tech/blog/simple-trick-to-save-environment-and-money-when-using-github-actions

Signed-off-by: Austin Seipp <aseipp@pobox.com>
2024-11-08 09:27:42 -06:00
Martin von Zweigbergk
86de9139b3 docs: fix a few typos noticed by automation at Google 2024-11-07 23:04:11 -08:00
Benjamin Tan
d7e06e6462 rebase: allow -r with --skip-emptied 2024-11-08 14:35:17 +08:00
Benjamin Tan
9b99f4810c rewrite: move_commits: do not allow emptying of descendants 2024-11-08 14:35:17 +08:00
Benjamin Tan
5b8e755909 rebase: add test showing unexpected commit abandoning 2024-11-08 14:35:17 +08:00
Yuya Nishihara
b0eb7764d4 cli: add helper method that resolves user symbols in revset expression
revset_util::evaluate() is inlined as there's only one caller.
2024-11-08 10:34:02 +09:00
Yuya Nishihara
62e4943c04 revset: reorganize expression resolution/evaluation methods
Both user and programmatic expressions use the same .evaluate() function now.
optimize() is applied globally after symbol resolution. The order shouldn't
matter, but it might be nicer because union of commit refs could be rewritten
to a single Commits(Vec<CommitId>) node.
2024-11-08 10:34:02 +09:00
Yuya Nishihara
e55d03a2ee revset: introduce type-safe user/resolved expression states
This helps add library API that takes resolved revset expressions. For example,
"jj absorb" will first compute annotation within a user-specified ancestor range
such as "mutable()". Because the range expression may contain symbols, it should
be resolved by caller.

There are two ideas to check resolution state at compile time:
<https://github.com/martinvonz/jj/pull/4374>

 a. add RevsetExpressionWrapper<PhantomState> and guarantee inner tree
    consistency at public API boundary
 b. parameterize RevsetExpression variant types in a way that invalid variants
    can never be constructed

(a) is nice if we want to combine "resolved" and "unresolved" expressions. The
inner expression types are the same, so we can just calculate new state as
Resolved & Unresolved = Unresolved. (b) is stricter as the compiler can
guarantee invariants. This patch implements (b) because there are no existing
callers who need to construct "resolved" expression and convert it to "user"
expression.

.evaluate_programmatic() now requires that the expression is resolved.
2024-11-08 09:56:33 +09:00
Yuya Nishihara
e83072b98f revset: ignore Present node when building backend expression
This will become safe as I'm going to add static check that the expression does
never contain CommitRef(_)s. We could make Present(_) unconstructible, but the
existence of Present node is harmless.
2024-11-08 09:56:33 +09:00
Yuya Nishihara
e6ea88aac0 revset: add visitor-like tree rewriting function, reimplement symbol resolution
I'm going to add RevsetExpression<State> type parameter, but the existing tree
transformer can't rewrite nodes to different state because the input and the
output must be of the same type. (If they were of different types, we couldn't
reuse the input subtree by Rc::clone().) The added visitor API will handle
state transitions by mapping RevsetExpression::<St1>::<Kind> to
RevsetExpression::<St2>::<Kind>.

CommitRef and AtOperation nodes are processed by specialized methods because
these nodes will depend on the State type. OTOH, Present node won't be
State-dependent, so it's inspected by the common fold_expression() method.

An input expression is not taken as an &Rc<RevsetExpression> but a &_ because
we can't reuse the allocation behind the Rc.
2024-11-08 09:56:33 +09:00
Yuya Nishihara
78d68f98f5 revset: group RevsetExpression constructors by resolved/user/generic
They'll become different in State types.
2024-11-08 09:56:33 +09:00
Benjamin Tan
1372b39341 template: add support for logical equality operator 2024-11-08 01:55:18 +08:00
Tim Janik
aa040021da docs/FAQ.md: answer monitoring jj log with watch and TUIs
* Suggested `watch --color` as recommended by @ilyagr
* Answered monitoring `jj log` with watch and TUIs
* Minor wording fix.
* Adjusted watch(1) and hwatch links as suggested by @ilyagr

Signed-off-by: Tim Janik <timj@gnu.org>
2024-11-07 09:40:25 -08:00
dependabot[bot]
aacddb2b25 cargo: bump the cargo-dependencies group with 2 updates
Bumps the cargo-dependencies group with 2 updates: [libc](https://github.com/rust-lang/libc) and [tokio](https://github.com/tokio-rs/tokio).


Updates `libc` from 0.2.161 to 0.2.162
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.162/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.161...0.2.162)

Updates `tokio` from 1.41.0 to 1.41.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.41.0...tokio-1.41.1)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-07 11:07:52 -06:00
Benjamin Tan
1c817f8932 cli: error when jj rebase -b is used without --destination
Closes #4770.
2024-11-07 21:49:51 +08:00
Martin von Zweigbergk
f9cfa5c9ce github: don't run release workflow after editing release
It was temporarily enabled in the hope that we could trigger the
failed workflow for 0.23.0 by editing the release (that didn't work).
2024-11-06 22:30:03 -08:00
Martin von Zweigbergk
5046157e96 github: run release workflow on publish and edit
The 0.23.0 release actions failed to run. Perhaps it's because I used
"save as draft" (to view the markdown) before I published it. Running
the actions on publish seems more correct.
2024-11-06 21:54:39 -08:00
491 changed files with 87148 additions and 41732 deletions

View File

@ -1,5 +1,16 @@
[profile.dev]
debug = "none"
incremental = false
[target.x86_64-unknown-linux-gnu]
rustflags = ["-Clink-arg=-fuse-ld=mold"]
[target.aarch64-unknown-linux-gnu]
rustflags = ["-Clink-arg=-fuse-ld=mold"]
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
# NOTE: on Windows, build with the static CRT, so that produced .exe files don't # NOTE: on Windows, build with the static CRT, so that produced .exe files don't
# depend on vcruntime140.dll; otherwise the user requires visual studio if they # depend on vcruntime140.dll; otherwise the user requires visual studio if they
# download a raw .exe # download a raw .exe
[target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"] rustflags = ["-Ctarget-feature=+crt-static"]

3
.config/nextest.toml Normal file
View File

@ -0,0 +1,3 @@
[profile.ci]
slow-timeout = { period = "5s", terminate-after = 20 }
fail-fast = false

View File

@ -2,10 +2,12 @@ root = true
[*] [*]
end_of_line = lf end_of_line = lf
# Turned off because some editors otherwise remove trailing spaces within insert_final_newline = true
# multi-line string literals (intellij-rust/intellij-rust#5368). trim_trailing_whitespace = true
trim_trailing_whitespace = false
[*.rs] [*.rs]
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
# Turned off because some editors otherwise remove trailing spaces within
# multi-line string literals (intellij-rust/intellij-rust#5368).
trim_trailing_whitespace = false

2
.gitattributes vendored
View File

@ -1,3 +1,3 @@
Cargo.lock linguist-generated=true merge=binary Cargo.lock linguist-generated=true merge=binary
flake.lock linguist-generated=true merge=binary flake.lock linguist-generated=true merge=binary
poetry.lock linguist-generated=true merge=binary uv.lock linguist-generated=true merge=binary

3
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,3 @@
# The maintainers own all files.
# See GOVERNANCE.md for the list of current maintainers.
* @jj-vcs/maintainers

View File

@ -10,7 +10,7 @@ assignees: ''
## Description ## Description
<!-- Thanks for your report! Please describe your problem or request here. <!-- Thanks for your report! Please describe your problem or request here.
For questions, use https://github.com/martinvonz/jj/discussions/new instead. For questions, use https://github.com/jj-vcs/jj/discussions/new instead.
Feel free to remove any of the sections below if they don't seem useful. --> Feel free to remove any of the sections below if they don't seem useful. -->

View File

@ -5,7 +5,7 @@ each commit representing one logical change. Address code review comments by
rewriting the commits rather than adding commits on top. Use force-push when rewriting the commits rather than adding commits on top. Use force-push when
pushing the updated commits (`jj git push` does that automatically when you pushing the updated commits (`jj git push` does that automatically when you
rewrite commits). Merge the PR at will once it's been approved. See rewrite commits). Merge the PR at will once it's been approved. See
https://github.com/martinvonz/jj/blob/main/docs/contributing.md for details. https://github.com/jj-vcs/jj/blob/main/docs/contributing.md for details.
Note that you need to sign Google's CLA to contribute. Note that you need to sign Google's CLA to contribute.
--> -->

View File

@ -0,0 +1,50 @@
name: Configure Windows Builders
description: |
This action configures the Windows builders to run tests.
runs:
using: "composite"
steps:
# The GitHub Actions hosted Windows runners have a slow persistent
# `C:` drive and a temporary `D:` drive with better throughput. The
# repository checkout is placed on `D:` by default, but the user
# profile is on `C:`, slowing down access to temporary directories
# and the Rust toolchain we install. Since our build environment is
# ephemeral anyway, we can save a couple minutes of CI time by
# placing everything on `D:`.
#
# Some projects have reported even bigger wins by mounting a VHDX
# virtual drive with a ReFS file system on it, with or without the
# native Dev Drive feature available in Windows 2025, but it seems
# to make things slightly slower for us overall compared to `D:`.
# Further investigation and experimentation would be welcome!
#
# See: <https://chadgolden.com/blog/github-actions-hosted-windows-runners-slower-than-expected-ci-and-you>
- name: 'Set up D: drive'
shell: pwsh
run: |
# Set up D: drive
# Short file names are disabled by default on the `D:` drive,
# which breaks some of our tests. Enable them.
#
# This has a slight performance penalty, and wont be possible
# if we switch to ReFS/Dev Drives. The alternatives are to
# reduce CI coverage for the security mitigation the tests are
# checking, or arrange for those tests to take a separate path
# to a drive that supports short file names to use instead of
# the primary temporary directory.
fsutil 8dot3name set D: 0
# Move the temporary directory to `D:\Temp`.
New-Item -Path D:\ -Name Temp -ItemType directory
# Copy the effective permissions without inheritance.
$Acl = Get-Acl -Path $env:TMP
$Acl.SetAccessRuleProtection($true, $true)
Set-Acl -Path D:\Temp -AclObject $Acl
Add-Content -Path $env:GITHUB_ENV @"
TMP=D:\Temp
TEMP=D:\Temp
RUSTUP_HOME=D:\.rustup
CARGO_HOME=D:\.cargo
"@

View File

@ -3,7 +3,7 @@ updates:
- package-ecosystem: "cargo" - package-ecosystem: "cargo"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "weekly"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
commit-message: commit-message:
prefix: "cargo:" prefix: "cargo:"
@ -14,7 +14,7 @@ updates:
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "weekly"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
commit-message: commit-message:
prefix: "github:" prefix: "github:"

5
.github/scripts/count-cargo-lock-packages vendored Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
# This is extremely approximate because the Cargo.lock file contains
# dependencies for all features and platforms, but it helps us keep an eye on
# things.
grep -c '^\[\[package\]\]' Cargo.lock

View File

@ -2,21 +2,16 @@
# Set up a virtual environment with the required tools, build, and deploy the docs. # Set up a virtual environment with the required tools, build, and deploy the docs.
# #
# Run from the root directory of the project as # Run from the root directory of the project as
# .github/scripts/docs-build-deploy 'https://martinvonz.github.io' prerelease main # .github/scripts/docs-build-deploy prerelease main
# All arguments after the first are passed to `mike deploy`, run # All arguments after the first are passed to `mike deploy`, run
# `poetry run -- mike deploy --help` for options. Note that `mike deploy` # `uv run -- mike deploy --help` for options. Note that `mike deploy`
# creates a commit directly on the `gh-pages` branch. # creates a commit directly on the `gh-pages` branch.
set -ev set -ev
export "SITE_URL_FOR_MKDOCS=$1"; shift
# Affects the generation of `sitemap.xml.gz` by `mkdocs`. See # Affects the generation of `sitemap.xml.gz` by `mkdocs`. See
# https://github.com/jimporter/mike/issues/103 and # https://github.com/jimporter/mike/issues/103 and
# https://reproducible-builds.org/docs/source-date-epoch/ # https://reproducible-builds.org/docs/source-date-epoch/
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct docs/ mkdocs.yml) export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct docs/ mkdocs.yml)
# https://github.com/python-poetry/poetry/issues/1917 and
# https://github.com/python-poetry/poetry/issues/8623
export PYTHON_KEYRING_BACKEND=keyring.backends.fail.Keyring
poetry install # Only really needed once per environment unless there are updates
# TODO: `--alias-type symlink` is the # TODO: `--alias-type symlink` is the
# default, and may be nicer in some ways. However, # default, and may be nicer in some ways. However,
# this requires deploying to GH Pages via a "custom GitHub Action", as in # this requires deploying to GH Pages via a "custom GitHub Action", as in
@ -24,4 +19,4 @@ poetry install # Only really needed once per environment unless there are updat
# Otherwise, you get an error: # Otherwise, you get an error:
# > Site contained a symlink that should be dereferenced: /main. # > Site contained a symlink that should be dereferenced: /main.
# > For more information, see https://docs.github.com/github/working-with-github-pages/troubleshooting-jekyll-build-errors-for-github-pages-sites#config-file-error. # > For more information, see https://docs.github.com/github/working-with-github-pages/troubleshooting-jekyll-build-errors-for-github-pages-sites#config-file-error.
poetry run -- mike deploy --alias-type copy "$@" uv run -- mike deploy --alias-type copy "$@"

39
.github/scripts/dragon-bureaucrat vendored Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# This script invokes the forbidden power of an ancient evil in order to defend
# the one thing we hold most dear: bureaucratic norms
# Many thanks to Phabricator (and Evan) for the vintage ASCII art (Apache 2.0)
# <https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/scripts/repository/commit_hook.php#L203>
rejection_reason=${1:-"No reason provided. The Dragons have spoken."}
cat >&2 <<'EOF'
+---------------------------------------------------------------+
| * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
+---------------------------------------------------------------+
\
\ ^ /^
\ / \ // \
\ |\___/| / \// .\
\ /V V \__ / // | \ \ *----*
/ / \/_/ // | \ \ \ |
@___@` \/_ // | \ \ \/\ \
0/0/| \/_ // | \ \ \ \
0/0/0/0/| \/// | \ \ | |
0/0/0/0/0/_|_ / ( // | \ _\ | /
0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
,-} _ *-.|.-~-. .~ ~
* \__/ `/\ / ~-. _ .-~ /
\____(Oo) *. } { /
( (..) .----~-.\ \-` .~
//___\\\\ \ DENIED! ///.----..< \ _ -~
// \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~
EOF
cat >&2 <<EOF
$rejection_reason
EOF
exit 1

View File

@ -5,7 +5,7 @@ on:
branches: branches:
- main - main
permissions: read-all permissions: {}
jobs: jobs:
binaries: binaries:
@ -22,10 +22,10 @@ jobs:
os: ubuntu-24.04 os: ubuntu-24.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- build: linux-aarch64-musl - build: linux-aarch64-musl
os: ubuntu-24.04 os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl target: aarch64-unknown-linux-musl
- build: linux-aarch64-gnu - build: linux-aarch64-gnu
os: ubuntu-24.04 os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu target: aarch64-unknown-linux-gnu
- build: macos-x86_64 - build: macos-x86_64
os: macos-13 os: macos-13
@ -37,44 +37,39 @@ jobs:
os: windows-2022 os: windows-2022
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 15 # NOTE (aseipp): keep in-sync with the build.yml timeout limit timeout-minutes: 20 # NOTE (aseipp): tests aren't run but sometimes builds take a while
name: Build binary artifacts name: Build binary artifacts
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Install packages (Ubuntu) - name: Install packages (Ubuntu)
if: matrix.os == 'ubuntu-24.04' if: startsWith(matrix.os, 'ubuntu')
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with: with:
toolchain: stable toolchain: stable
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: Build release binary - name: Build release binary
shell: bash shell: bash
run: | run: cargo build --target ${{ matrix.target }} --verbose --release --features vendored-openssl
CARGO_CMD=cargo
if [[ "${{ matrix.target }}" = aarch64-unknown-linux* ]]; then
echo "Downloading 'cross' binary for aarch64-linux..."
wget -c https://github.com/cross-rs/cross/releases/download/v0.2.5/cross-x86_64-unknown-linux-gnu.tar.gz -O - | tar -xz
CARGO_CMD=$PWD/cross
fi
$CARGO_CMD build --target ${{ matrix.target }} --verbose --release --features packaging,vendored-openssl
- name: Setup artifact directory - name: Set up artifact directory
shell: bash shell: bash
run: | run: |
outdir="target/${{ matrix.target }}/release" outdir="target/${{ matrix.target }}/release"
BIN=$outdir/jj BIN=$outdir/jj
[[ "${{ matrix.os }}" == "windows-latest" ]] && BIN+=".exe" [[ "${{ matrix.os }}" == "windows-latest" ]] && BIN+=".exe"
mkdir -p target/out mkdir -p target/out
cp $BIN target/out cp $BIN target/out
- name: Publish binary artifact - name: Publish binary artifact
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with: with:
name: jj-${{ matrix.target }} name: jj-${{ matrix.target }}
path: target/out path: target/out

View File

@ -1,27 +0,0 @@
name: nix
on:
push:
branches:
- main
pull_request:
permissions: read-all
jobs:
nix:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-14]
runs-on: ${{ matrix.os }}
timeout-minutes: 15 # NOTE (aseipp): keep in-sync with the build.yml timeout limit
name: flake check
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
- uses: DeterminateSystems/nix-installer-action@b92f66560d6f97d6576405a7bae901ab57e72b6a
- uses: DeterminateSystems/magic-nix-cache-action@87b14cf437d03d37989d87f0fa5ce4f5dc1a330b
- run: nix flake check -L --show-trace

View File

@ -1,185 +0,0 @@
name: build
on:
push:
pull_request:
permissions: read-all
env:
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
build:
strategy:
fail-fast: false
matrix:
# macos-13 is x86; macos-14 is ARM
os: [ubuntu-latest, macos-13, macos-14, windows-latest]
cargo_flags: [""]
include:
- os: ubuntu-latest
cargo_flags: "--all-features"
runs-on: ${{ matrix.os }}
# TODO FIXME (aseipp): keep the timeout limit to ~15 minutes. this is long
# enough to give us runway for the future, but also once we hit it, we're at
# the "builds are taking too long" stage and we should start looking at ways
# to optimize the CI.
#
# at the same time, this avoids some issues where some flaky, bugged tests
# seem to be causing multi-hour runs on Windows (GPG signing issues), which
# is a problem we should fix. in the mean time, this will make these flakes
# less harmful, as it won't cause builds to spin for multiple hours, requiring
# manual cancellation.
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# The default version of gpg installed on the runners is a version baked in with git
# which only contains the components needed by git and doesn't work for our test cases.
#
# This installs the latest gpg4win version, which is a variation of GnuPG built for
# Windows.
#
# There is some issue with windows PATH max length which is what all the PATH wrangling
# below is for. Please see the below link for where this fix was derived from:
# https://github.com/orgs/community/discussions/24933
- name: Setup GnuPG [windows]
if: ${{ matrix.os == 'windows-latest' }}
run: |
$env:PATH = "C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\ProgramData\chocolatey\bin"
[Environment]::SetEnvironmentVariable("Path", $env:PATH, "Machine")
choco install --yes gpg4win
echo "C:\Program Files (x86)\Gpg4win\..\GnuPG\bin" >> $env:GITHUB_PATH
# The default version of openssh on windows server is quite old (8.1) and doesn't have
# all the necessary signing/verification commands available (such as -Y find-principals)
- name: Setup ssh-agent [windows]
if: ${{ matrix.os == 'windows-latest' }}
run: |
Remove-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Remove-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
choco install openssh --pre
- name: Install Rust
uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
with:
toolchain: 1.76
- name: Build
run: cargo build --workspace --all-targets --verbose ${{ matrix.cargo_flags }}
- name: Test
run: cargo test --workspace --all-targets --verbose ${{ matrix.cargo_flags }}
env:
RUST_BACKTRACE: 1
build-no-git:
name: Build jj-lib without Git support
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Install Rust
uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
with:
toolchain: 1.76
- name: Build
run: cargo build -p jj-lib --no-default-features --verbose
check-protos:
name: Check protos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
with:
toolchain: stable
- run: sudo apt update && sudo apt-get -y install protobuf-compiler
- name: Generate Rust code from .proto files
run: cargo run -p gen-protos
- name: Check for uncommitted changes
run: git diff --exit-code
rustfmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
with:
toolchain: nightly
components: rustfmt
- run: cargo +nightly fmt --all -- --check
mkdocs:
name: Check that MkDocs can build the docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b
with:
python-version: 3.11
- name: Install poetry (latest release)
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd
with:
poetry-version: latest
- name: Install dependencies
run: poetry install
- name: Check that `mkdocs` can build the docs
run: poetry run -- mkdocs build --strict
mkdocs-old-poetry:
name: Check that MkDocs can build the docs with Poetry 1.8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b
with:
python-version: 3.11
- name: Install poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd
with:
# Test with the version of Poetry in Debian stable. If this starts
# failing, we should increase this version and document the minimum
# necessary version of Poetry in contributing.md.
#
# One way to install old `poetry` is using `pipx`:
# pipx install 'poetry<1.4' --suffix -1.3
poetry-version: 1.8
- name: Install dependencies
run: poetry install
- name: Check that `mkdocs` can build the docs
run: poetry run -- mkdocs build --strict
cargo-deny:
runs-on: ubuntu-latest
strategy:
matrix:
checks:
- advisories
- bans licenses sources
# Prevent sudden announcement of a new advisory from failing ci:
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: EmbarkStudios/cargo-deny-action@8371184bd11e21dcf8ac82ebf8c9c9f74ebf7268
with:
command: check ${{ matrix.checks }}
clippy-check:
name: Clippy check
permissions:
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
with:
toolchain: stable
components: clippy
- run: cargo +stable clippy --all-features --workspace --all-targets -- -D warnings

349
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,349 @@
name: ci
on:
pull_request:
merge_group:
concurrency:
group: >-
${{ github.workflow }}-${{
github.event.pull_request.number
|| github.event.merge_group.head_ref
}}
cancel-in-progress: true
permissions: {}
jobs:
test:
strategy:
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix:
build: [linux-x86_64-gnu, 'linux-x86_64-gnu, no git2', linux-aarch64-gnu, macos-x86_64, macos-aarch64, windows-x86_64]
include:
- build: linux-x86_64-gnu
os: ubuntu-24.04
cargo_flags: "--all-features"
- build: 'linux-x86_64-gnu, no git2'
os: ubuntu-24.04
cargo_flags: "--no-default-features --features git"
# Ensure we dont link to `libgit2`.
LIBGIT2_NO_VENDOR: 1
- build: linux-aarch64-gnu
os: ubuntu-24.04-arm
cargo_flags: "--all-features"
- build: macos-x86_64
os: macos-13
cargo_flags: ""
- build: macos-aarch64
os: macos-14
cargo_flags: ""
- build: windows-x86_64
os: windows-2022
cargo_flags: ""
runs-on: ${{ matrix.os }}
# TODO FIXME (aseipp): keep the timeout limit to ~20 minutes. this is long
# enough to give us runway for the future, but also once we hit it, we're at
# the "builds are taking too long" stage and we should start looking at ways
# to optimize the CI, or the CI is flaking out on some weird spiked machine
#
# at the same time, this avoids some issues where some flaky, bugged tests
# seem to be causing multi-hour runs on Windows (GPG signing issues), which
# is a problem we should fix. in the mean time, this will make these flakes
# less harmful, as it won't cause builds to spin for multiple hours, requiring
# manual cancellation.
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Set up Windows Builders
if: startswith(matrix.os, 'windows')
uses: ./.github/actions/setup-windows
- name: Install Rust
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: 1.84
- uses: taiki-e/install-action@86c23eed46c17b80677df6d8151545ce3e236c61
with:
tool: nextest,taplo-cli
- name: Install mold
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50
with:
make-default: false
- name: Build
run: >-
cargo build
--config .cargo/config-ci.toml
--workspace
--all-targets
--verbose
${{ matrix.cargo_flags }}
env:
LIBGIT2_NO_VENDOR: ${{ matrix.LIBGIT2_NO_VENDOR || '0' }}
- name: Test
run: >-
cargo nextest run
--config .cargo/config-ci.toml
--workspace
--all-targets
--verbose
--profile ci
${{ matrix.cargo_flags }}
env:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
LIBGIT2_NO_VENDOR: ${{ matrix.LIBGIT2_NO_VENDOR || '0' }}
no-git:
name: build (no git)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: 1.84
- name: Build
run: cargo build -p jj-cli --no-default-features --verbose
build-nix:
name: nix flake
strategy:
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-14]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
persist-credentials: false
- uses: DeterminateSystems/nix-installer-action@21a544727d0c62386e78b4befe52d19ad12692e3
- run: nix flake check -L --show-trace
check-protos:
name: check (protos)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: stable
- run: sudo apt update && sudo apt-get -y install protobuf-compiler
- name: Generate Rust code from .proto files
run: cargo run -p gen-protos
- name: Check for uncommitted changes
run: git diff --exit-code
check-rustfmt:
name: check (rustfmt)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: nightly
components: rustfmt
- run: cargo +nightly fmt --all -- --check
check-clippy:
name: check (clippy)
permissions:
checks: write
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: stable
components: clippy
- run: cargo +stable clippy --all-features --workspace --all-targets -- -D warnings
check-cargo-deny:
runs-on: ubuntu-24.04
strategy:
matrix:
checks:
- advisories
- bans
- licenses
- sources
# Prevent sudden announcement of a new advisory from failing ci:
continue-on-error: ${{ matrix.checks == 'advisories' }}
name: check (cargo-deny, ${{ matrix.checks }})
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1
with:
command: check ${{ matrix.checks }}
check-codespell:
name: check (codespell)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: 3.11
- name: Install uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
with:
# If you bump the version, also update docs/contributing.md
# and all other workflows that install uv
version: "0.5.1"
- name: Run Codespell
run: uv run -- codespell && echo Codespell exited successfully
check-doctests:
name: check (doctests)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: 1.84
# NOTE: We need to run `cargo test --doc` separately from normal tests:
# - `cargo build --all-targets` specifies: "Build all targets"
# - `cargo test --all-targets` specifies: "Test all targets (does not include doctests)"
- name: Run doctests
run: cargo test --workspace --doc
env:
RUST_BACKTRACE: 1
- name: Check `cargo doc` for lint issues
env:
RUSTDOCFLAGS: "--deny warnings"
run: cargo doc --workspace --no-deps
check-mkdocs:
name: check (mkdocs)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: 3.11
- name: Install uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
with:
# If you bump the version, also update docs/contributing.md
# and all other workflows that install uv
version: "0.5.1"
- name: Check that `mkdocs` can build the docs
run: uv run -- mkdocs build --strict
# An optional job to alert us when uv updates break the build
check-mkdocs-latest:
name: check (latest mkdocs, optional)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
# 'only-managed' means that uv will always download Python, even
# if the runner happens to provide a compatible version
- name: Check that `mkdocs` can build the docs
run: uv run --python-preference=only-managed -- mkdocs build --strict
check-zizmor:
name: check (zizmor)
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
- name: Run zizmor
run: uvx zizmor --format sarif . > results.sarif
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b
with:
sarif_file: results.sarif
category: zizmor
# Count the (very approximate) number of dependencies in Cargo.lock and bail at a certain limit.
check-cargo-lock-bloat:
name: check (Cargo.lock dependency count)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Check total dependency count in Cargo.lock
run: |
total_deps=$(./.github/scripts/count-cargo-lock-packages)
if [ "$total_deps" -gt "${TOTAL_DEP_LIMIT}" ]; then
./.github/scripts/dragon-bureaucrat \
"Cargo.lock has too many dependencies ($total_deps > ${TOTAL_DEP_LIMIT}). The Dragon banishes thee!
You can raise the limit in \`.github/workflows/ci.yml\` if necessary, but
consider whether its possible to trim things down first."
else
echo "Counted $total_deps Cargo.lock dependencies." \
"This is within the allowed limit of ${TOTAL_DEP_LIMIT}."
fi
env:
# This limit *can* be raised, we just want to be aware if we exceed it
TOTAL_DEP_LIMIT: 500
# Block the merge if required checks fail, but only in the merge
# queue. See also `required-checks-hack.yml`.
required-checks:
name: required checks (merge queue)
if: ${{ always() && github.event_name == 'merge_group' }}
needs:
- test
- no-git
- build-nix
- check-protos
- check-rustfmt
- check-clippy
- check-cargo-deny
- check-codespell
- check-doctests
- check-mkdocs
# - check-mkdocs-latest
# - check-zizmor
- check-cargo-lock-bloat
runs-on: ubuntu-latest
steps:
- name: Block merge if required checks fail
if: >-
${{
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
}}
run: exit 1

View File

@ -1,22 +0,0 @@
name: Codespell
on:
push:
branches:
- main
pull_request:
permissions: read-all
jobs:
codespell:
name: Codespell
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630
with:
check_filenames: true
check_hidden: true
skip: ./target,./.jj,*.lock
ignore_words_list: crate,NotIn,Wirth

View File

@ -3,7 +3,11 @@ name: Enable auto-merge for Dependabot PRs
on: on:
pull_request: pull_request:
permissions: read-all concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs: jobs:
dependabot-auto-merge: dependabot-auto-merge:
@ -11,7 +15,7 @@ jobs:
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: ${{ github.actor == 'dependabot[bot]' }} if: ${{ github.actor == 'dependabot[bot]' }}
steps: steps:
- name: Enable auto-merge for Dependabot PRs - name: Enable auto-merge for Dependabot PRs

View File

@ -5,31 +5,39 @@ on:
branches: branches:
- main - main
permissions: permissions: {}
contents: write
jobs: jobs:
prerelease-docs-build-deploy: prerelease-docs-build-deploy:
if: github.repository_owner == 'martinvonz' # Stops this job from running on forks # IMPORTANT: this workflow also functions as a test for `docs-deploy-website-latest-release` in
# releases.yml. Any fixes here should probably be duplicated there.
permissions:
contents: write
if: github.repository_owner == 'jj-vcs' # Stops this job from running on forks
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-24.04]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
# `.github/scripts/docs-build-deploy` will need to `git push` to the docs branch
persist-credentials: true
- run: "git fetch origin gh-pages --depth=1" - run: "git fetch origin gh-pages --depth=1"
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with: with:
python-version: 3.11 python-version: 3.11
- name: Install poetry - name: Install uv
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
with: with:
poetry-version: latest version: "0.5.1"
- name: Install dependencies, compile and deploy docs - name: Install dependencies, compile and deploy docs
run: | run: |
git config user.name 'jj-docs[bot]' git config user.name 'jj-docs[bot]'
git config user.email 'jj-docs[bot]@users.noreply.github.io' git config user.email 'jj-docs[bot]@users.noreply.github.io'
.github/scripts/docs-build-deploy 'https://martinvonz.github.io/jj' prerelease --push export MKDOCS_SITE_NAME="Jujutsu docs (prerelease)"
export MKDOCS_PRIMARY_COLOR="blue grey"
.github/scripts/docs-build-deploy prerelease --push
- name: "Show `git diff --stat`" - name: "Show `git diff --stat`"
run: git diff --stat gh-pages^ gh-pages || echo "(No diffs)" run: git diff --stat gh-pages^ gh-pages || echo "(No diffs)"

View File

@ -2,7 +2,7 @@ name: Release
on: on:
release: release:
types: [created] types: [published]
permissions: read-all permissions: read-all
@ -23,7 +23,7 @@ jobs:
os: ubuntu-24.04 os: ubuntu-24.04
target: x86_64-unknown-linux-musl target: x86_64-unknown-linux-musl
- build: linux-aarch64-musl - build: linux-aarch64-musl
os: ubuntu-24.04 os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl target: aarch64-unknown-linux-musl
- build: macos-x86_64 - build: macos-x86_64
os: macos-13 os: macos-13
@ -38,32 +38,26 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- name: Install packages (Ubuntu) - name: Install packages (Ubuntu)
if: matrix.os == 'ubuntu-24.04' if: startsWith(matrix.os, 'ubuntu')
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with: with:
toolchain: stable toolchain: stable
target: ${{ matrix.target }} target: ${{ matrix.target }}
- name: Download cross-compilation tool (linux-aarch64)
if: matrix.target == 'aarch64-unknown-linux-musl'
run: wget -c https://github.com/cross-rs/cross/releases/download/v0.2.5/cross-x86_64-unknown-linux-gnu.tar.gz -O - | tar -xz
- name: Build release binary - name: Build release binary
shell: bash shell: bash
run: | run: cargo build --target ${{ matrix.target }} --verbose --release --features vendored-openssl
CARGO_CMD=cargo
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-musl" ]; then
CARGO_CMD=$PWD/cross
fi
$CARGO_CMD build --target ${{ matrix.target }} --verbose --release --features packaging,vendored-openssl
- name: Build archive - name: Build archive
shell: bash shell: bash
run: | run: |
outdir="target/${{ matrix.target }}/release" outdir="target/${{ matrix.target }}/release"
staging="jj-${{ github.event.release.tag_name }}-${{ matrix.target }}" staging="jj-${RELEASE_TAG_NAME}-${{ matrix.target }}"
mkdir "$staging" mkdir "$staging"
cp {README.md,LICENSE} "$staging/" cp {README.md,LICENSE} "$staging/"
if [ "${{ matrix.os }}" = "windows-2022" ]; then if [ "${{ matrix.os }}" = "windows-2022" ]; then
@ -76,6 +70,8 @@ jobs:
tar czf "$staging.tar.gz" -C "$staging" . tar czf "$staging.tar.gz" -C "$staging" .
echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
fi fi
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: Upload release archive - name: Upload release archive
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
env: env:
@ -87,7 +83,7 @@ jobs:
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
docs-release-archive: docs-release-archive:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
permissions: permissions:
contents: write contents: write
@ -97,20 +93,24 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with: with:
python-version: 3.11 python-version: 3.11
- name: Install poetry - name: Install uv
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
with: with:
poetry-version: latest version: "0.5.1"
- name: Compile docs and zip them up - name: Compile docs and zip them up
run: | run: |
poetry install uv run mkdocs build
poetry run -- mkdocs build -f mkdocs-offline.yml archive="jj-${RELEASE_TAG_NAME}-docs-html.tar.gz"
archive="jj-${{ github.event.release.tag_name }}-docs-html.tar.gz"
tar czf "$archive" -C "rendered-docs" . tar czf "$archive" -C "rendered-docs" .
echo "ASSET=$archive" >> $GITHUB_ENV echo "ASSET=$archive" >> $GITHUB_ENV
env:
MKDOCS_OFFLINE: true
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: Upload release archive - name: Upload release archive
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
env: env:
@ -122,26 +122,31 @@ jobs:
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
docs-deploy-website-latest-release: docs-deploy-website-latest-release:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
# `.github/scripts/docs-build-deploy` will need to `git push` to the docs branch
persist-credentials: true
- run: "git fetch origin gh-pages --depth=1" - run: "git fetch origin gh-pages --depth=1"
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with: with:
python-version: 3.11 python-version: 3.11
- name: Install poetry - name: Install uv
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
with: with:
poetry-version: latest version: "0.5.1"
- name: Install dependencies, compile and deploy docs to the "latest release" section of the website - name: Install dependencies, compile and deploy docs to the "latest release" section of the website
run: | run: |
git config user.name 'jj-docs[bot]' git config user.name 'jj-docs[bot]'
git config user.email 'jj-docs[bot]@users.noreply.github.io' git config user.email 'jj-docs[bot]@users.noreply.github.io'
# Using the 'latest' tag below makes the website default # Using the 'latest' tag below makes the website default
# to this version. # to this version.
.github/scripts/docs-build-deploy 'https://martinvonz.github.io/jj' "${{ github.event.release.tag_name }}" latest --update-aliases --push .github/scripts/docs-build-deploy "${RELEASE_TAG_NAME}" latest --update-aliases --push
env:
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: "Show `git diff --stat`" - name: "Show `git diff --stat`"
run: git diff --stat gh-pages^ gh-pages || echo "(No diffs)" run: git diff --stat gh-pages^ gh-pages || echo "(No diffs)"

View File

@ -0,0 +1,18 @@
name: pr
on:
pull_request:
permissions: {}
jobs:
# The actual `required-checks` job is defined in `ci.yml` and only
# runs for `merge_group` events. This hack ensures that it doesnt
# block the merge for pull requests.
required-checks:
name: required checks (merge queue)
if: false
runs-on: ubuntu-latest
# Should never be run
steps:
- run: exit 1

View File

@ -7,13 +7,13 @@ on:
push: push:
branches: [ main ] branches: [ main ]
# Declare default permissions as read only. # No default permissions
permissions: read-all permissions: {}
jobs: jobs:
analysis: analysis:
name: Scorecards analysis name: Scorecards analysis
runs-on: ubuntu-latest runs-on: ubuntu-24.04
permissions: permissions:
# Needed to upload the results to code-scanning dashboard. # Needed to upload the results to code-scanning dashboard.
security-events: write security-events: write
@ -26,7 +26,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@ -38,7 +38,7 @@ jobs:
# Upload the results as artifacts (optional). # Upload the results as artifacts (optional).
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@ -46,6 +46,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b
with: with:
sarif_file: results.sarif sarif_file: results.sarif

9
.gitignore vendored
View File

@ -8,9 +8,18 @@ result
*.pending-snap *.pending-snap
*.snap* *.snap*
!cli/tests/cli-reference@.md.snap !cli/tests/cli-reference@.md.snap
# Per user insta settings.
# See https://insta.rs/docs/settings/#tool-config-file for details.
.config/insta.yaml
# mkdocs
/.venv
/.python-version
# Editor specific ignores # Editor specific ignores
.idea .idea
.vscode
.zed
# Generated by setting `JJ_TRACE` environment variable. # Generated by setting `JJ_TRACE` environment variable.
jj-trace-*.json jj-trace-*.json

File diff suppressed because it is too large Load Diff

2631
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +1,53 @@
cargo-features = [] cargo-features = []
[workspace] [workspace]
resolver = "2" resolver = "3"
members = ["cli", "lib", "lib/gen-protos", "lib/proc-macros", "lib/testutils"] members = ["cli", "lib", "lib/gen-protos", "lib/proc-macros", "lib/testutils"]
[workspace.package] [workspace.package]
version = "0.23.0" version = "0.28.2"
license = "Apache-2.0" license = "Apache-2.0"
rust-version = "1.76" # NOTE: remember to update CI, contributing.md, changelog.md, install-and-setup.md, and flake.nix rust-version = "1.84" # NOTE: remember to update CI, contributing.md, changelog.md, and install-and-setup.md
edition = "2021" edition = "2021"
readme = "README.md" readme = "README.md"
homepage = "https://github.com/martinvonz/jj" homepage = "https://github.com/jj-vcs/jj"
repository = "https://github.com/martinvonz/jj" repository = "https://github.com/jj-vcs/jj"
documentation = "https://martinvonz.github.io/jj/" documentation = "https://jj-vcs.github.io/jj/"
categories = ["version-control", "development-tools"] categories = ["version-control", "development-tools"]
keywords = ["VCS", "DVCS", "SCM", "Git", "Mercurial"] keywords = ["VCS", "DVCS", "SCM", "Git", "Mercurial"]
[workspace.dependencies] [workspace.dependencies]
anyhow = "1.0.93"
assert_cmd = "2.0.8" assert_cmd = "2.0.8"
assert_matches = "1.5.0" assert_matches = "1.5.0"
async-trait = "0.1.83" async-trait = "0.1.88"
backoff = "0.4.0"
blake2 = "0.10.6" blake2 = "0.10.6"
bstr = "1.10.0" bstr = "1.11.3"
clap = { version = "4.5.20", features = [ clap = { version = "4.5.37", features = [
"derive", "derive",
"deprecated", "deprecated",
"wrap_help", "wrap_help",
"string", "string",
] } ] }
clap_complete = "4.5.37" clap_complete = { version = "4.5.48", features = ["unstable-dynamic"] }
clap_complete_nushell = "4.5.4" clap_complete_nushell = "4.5.5"
clap-markdown = "0.1.4" # Update clap-markdown manually since test_generate_md_cli_help snapshot
clap_mangen = "0.2.10" # will need regenerating.
chrono = { version = "0.4.38", default-features = false, features = [ clap-markdown = "=0.1.5"
clap_mangen = "0.2.25"
chrono = { version = "0.4.41", default-features = false, features = [
"std", "std",
"clock", "clock",
] } ] }
chrono-english = { version = "0.1.7" }
clru = "0.6.2" clru = "0.6.2"
config = { version = "0.13.4", default-features = false, features = ["toml"] }
criterion = "0.5.1" criterion = "0.5.1"
crossterm = { version = "0.27", default-features = false } crossterm = { version = "0.28", default-features = false, features = ["windows"] }
datatest-stable = "0.3.2"
digest = "0.10.7" digest = "0.10.7"
dirs = "5.0.1"
dunce = "1.0.5" dunce = "1.0.5"
either = "1.13.0" etcetera = "0.10.0"
esl01-renderdag = "0.3.0" either = "1.15.0"
futures = "0.3.31" futures = "0.3.31"
git2 = { version = "0.19.0", features = [ git2 = { version = "0.20.1", features = [
# Do *not* disable this feature even if you'd like dynamic linking. Instead, # Do *not* disable this feature even if you'd like dynamic linking. Instead,
# set the environment variable `LIBGIT2_NO_VENDOR=1` if dynamic linking must # set the environment variable `LIBGIT2_NO_VENDOR=1` if dynamic linking must
# be used (this will override the Cargo feature), and allow static linking # be used (this will override the Cargo feature), and allow static linking
@ -59,79 +57,82 @@ git2 = { version = "0.19.0", features = [
# https://github.com/rust-lang/git2-rs/commit/3cef4119f # https://github.com/rust-lang/git2-rs/commit/3cef4119f
"vendored-libgit2" "vendored-libgit2"
] } ] }
gix = { version = "0.66.0", default-features = false, features = [ gix = { version = "0.71.0", default-features = false, features = [
"attributes",
"blob-diff",
"index", "index",
"max-performance-safe", "max-performance-safe",
"blob-diff", "zlib-rs",
] } ] }
gix-filter = "0.13.0" glob = "0.3.2"
glob = "0.3.1" hashbrown = { version = "0.15.3", default-features = false, features = ["inline-more"] }
hashbrown = { version = "0.15.1", default-features = false, features = ["inline-more"] }
hex = "0.4.3" hex = "0.4.3"
ignore = "0.4.23" ignore = "0.4.23"
indexmap = "2.6.0" indexmap = { version = "2.9.0", features = ["serde"] }
indoc = "2.0.4" indoc = "2.0.6"
insta = { version = "1.41.1", features = ["filters"] } insta = { version = "1.43.1", features = ["filters"] }
itertools = "0.13.0" interim = { version = "0.2.1", features = ["chrono_0_4"] }
libc = { version = "0.2.161" } itertools = "0.14.0"
libc = { version = "0.2.172" }
maplit = "1.0.2" maplit = "1.0.2"
minus = { version = "5.6.1", features = ["dynamic_output", "search"] }
num_cpus = "1.16.0" num_cpus = "1.16.0"
once_cell = "1.20.2" once_cell = "1.21.3"
pest = "2.7.14" os_pipe = "1.2.1"
pest_derive = "2.7.14" pest = "2.8.0"
pollster = "0.3.0" pest_derive = "2.8.0"
pollster = "0.4.0"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
proc-macro2 = "1.0.89" proc-macro2 = "1.0.95"
prost = "0.12.6" prost = "0.13.5"
prost-build = "0.12.6" prost-build = "0.13.5"
quote = "1.0.36" quote = "1.0.40"
rand = "0.8.5" rand = "0.8.5"
rand_chacha = "0.3.1" rand_chacha = "0.3.1"
rayon = "1.10.0" rayon = "1.10.0"
ref-cast = "1.0.23" ref-cast = "1.0.24"
regex = "1.11.1" regex = "1.11.1"
rpassword = "7.3.1" rpassword = "7.4.0"
rustix = { version = "0.38.39", features = ["fs"] } rustix = { version = "1.0.7", features = ["fs"] }
same-file = "1.0.6" same-file = "1.0.6"
scm-record = "0.4.0" sapling-renderdag = "0.1.0"
sapling-streampager = "0.11.0"
scm-record = "0.8.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.132" serde_json = "1.0.140"
slab = "0.4.9" slab = "0.4.9"
smallvec = { version = "1.13.2", features = [ smallvec = { version = "1.14.0", features = [
"const_generics", "const_generics",
"const_new", "const_new",
"union", "union",
] } ] }
strsim = "0.11.1" strsim = "0.11.1"
syn = "2.0.87" syn = "2.0.101"
tempfile = "3.13.0" tempfile = "3.19.1"
test-case = "3.3.1" test-case = "3.3.1"
textwrap = "0.16.1" textwrap = "0.16.2"
thiserror = "1.0.68" thiserror = "2.0.12"
timeago = { version = "0.4.2", default-features = false } timeago = { version = "0.4.2", default-features = false }
tokio = { version = "1.41.0" } tokio = { version = "1.44.2" }
toml_edit = { version = "0.19.15", features = ["serde"] } toml_edit = { version = "0.22.26", features = ["serde"] }
tracing = "0.1.40" tracing = "0.1.41"
tracing-chrome = "0.7.2" tracing-chrome = "0.7.2"
tracing-subscriber = { version = "0.3.18", default-features = false, features = [ tracing-subscriber = { version = "0.3.19", default-features = false, features = [
"std", "std",
"ansi", "ansi",
"env-filter", "env-filter",
"fmt", "fmt",
] } ] }
unicode-width = "0.1.14" unicode-width = "0.2.0"
version_check = "0.9.5" version_check = "0.9.5"
watchman_client = { version = "0.9.0" } watchman_client = { version = "0.9.0" }
whoami = "1.5.2" whoami = "1.6.0"
winreg = "0.52" winreg = "0.52"
zstd = "0.12.4"
# put all inter-workspace libraries, i.e. those that use 'path = ...' here in # put all inter-workspace libraries, i.e. those that use 'path = ...' here in
# their own (alphabetically sorted) block # their own (alphabetically sorted) block
jj-lib = { path = "lib", version = "0.23.0" } jj-lib = { path = "lib", version = "0.28.2", default-features = false }
jj-lib-proc-macros = { path = "lib/proc-macros", version = "0.23.0" } jj-lib-proc-macros = { path = "lib/proc-macros", version = "0.28.2" }
testutils = { path = "lib/testutils" } testutils = { path = "lib/testutils" }
[workspace.lints.clippy] [workspace.lints.clippy]
@ -141,7 +142,9 @@ implicit_clone = "warn"
needless_for_each = "warn" needless_for_each = "warn"
semicolon_if_nothing_returned = "warn" semicolon_if_nothing_returned = "warn"
uninlined_format_args = "warn" uninlined_format_args = "warn"
unused_trait_names = "warn"
useless_conversion = "warn"
# Insta suggests compiling these packages in opt mode for faster testing. # Insta suggests compiling these packages in opt mode for faster testing.
# See https://docs.rs/insta/latest/insta/#optional-faster-runs. # See https://docs.rs/insta/latest/insta/#optional-faster-runs.
[profile.dev.package] [profile.dev.package]

144
GOVERNANCE.md Normal file
View File

@ -0,0 +1,144 @@
# Jujutsu Governance
## Overview
Jujutsu is an open source project, led, maintained and designed for a worldwide
community. Anyone who is interested can join, contribute, and participate in the
decision-making process. This document is intended to help you understand how
you can do that.
## Project roles
We greatly appreciate everyone's contributions, and Jujutsu has benefited
greatly from people who shared a single idea, change, or a suggestion, without
ever becoming a regular contributor. We also want everybody to feel welcome to
share their suggestions for the project (as long as you follow the Community
Guidelines).
There are two special roles for participants in the Jujutsu projects:
Maintainers and Contributors.
The role of the Maintainer is formally defined. These are the people empowered
to collectively make final decisions about most aspects of the project. They are
expected to take community's input seriously and to aim for the benefit of the
entire community.
The role of a Contributor is less formal. In situations where opinions become
numerous or contentious, it is acceptable for the maintainers to assign more
weight to the voices of the more established Contributors.
### Maintainers
**Maintainers** are the people who contribute, review, guide, and collectively
make decisions about the direction and scope of the project (see:
[Decision Making](#decision-making)). Maintainers are elected by a
[voting process](#adding-and-removing-maintainers).
A typical Maintainer is not only someone who has made "large" contributions, but
someone who has shown they are continuously committed to the project and its
community. Some expected responsibilities of maintainers include (but are not
exclusively limited to):
- Displaying a high level of commitment to the project and its community, and
being a role model for others.
- Writing patches &mdash; a lot of patches, especially "glue code" or "grunt
work" or general "housekeeping"; fixing bugs, ensuring documentation is always
high quality, consistent UX design, improving processes, making judgments on
dependencies, handling security vulnerabilities, and so on and so forth.
- Reviewing code submitted by others &mdash; with an eye to maintainability,
performance, code quality, and "style" (fitting in with the project).
- Participating in design discussions, especially with regards to architecture
or long-term vision.
- Ensuring the community remains a warm and welcoming place, to new and veteran
members alike.
This is not an exhaustive list, nor is it intended that every Maintainer does
each and every one of these individual tasks to equal amounts. Rather this is
only a guideline for what Maintainers are expected to conceptually do.
In short, Maintainers are the outwardly visible stewards of the project.
#### Current list of Maintainers
The current list of Maintainers:
- Austin Seipp (@thoughtpolice)
- Ilya Grigoriev (@ilyagr)
- Martin von Zweigbergk (@martinvonz)
- Waleed Khan (@arxanas)
- Yuya Nishihara (@yuja)
### Contributors
We consider contributors to be active participants in the project and community
who are _not_ maintainers. These are people who might:
- Help users by answering questions
- Participating in lively and respectful discussions across various channels
- Submit high-quality bug reports, reproduce reported bugs, and verifying fixes
- Submit patches or pull requests
- Provide reviews and input on others' pull requests
- Help with testing and quality assurance
- Submit feedback about planned features, use cases, or bugs
We essentially define them as **people who actively participate in the
project**. Examples of things that would _not_ make you a contributor are:
- Submitting a single bug report and never returning
- Writing blog posts or other evangelism
- Using the software in production
- Forking the project and maintaining your own version
- Writing a third-party tool or add-on
While these are all generally quite valuable, we don't consider these ongoing
contributions to the codebase or project itself, and on their own do not
constitute "active participation".
## Processes
For the purposes of making decisions across the project, the following processes
are defined.
### Decision-Making
The person proposing a decision to be made (i.e. technical, project direction,
etc.) can offer a proposal, along with a 2-to-4 week deadline for discussion.
During this time, Maintainers may participate with a vote of:
A) Support B) Reject C) Abstain
Each Maintainer gets one vote. The total number of "participating votes" is the
number of Maintainer votes which are not Abstain. The proposal is accepted when
more than half of the participating votes are Support.
In the event that a decision is reached before the proposed timeline, said
proposal can move on and be accepted immediately. In the event no consensus is
reached, a proposal may be re-submitted later on.
This document itself is subject to the Decision-Making process by the existing
set of Maintainers.
### Adding and Removing Maintainers
An active Contributor may, at any given time, nominate themselves or another
Contributor to become a Maintainer. This process is purely optional and no
Contributor is expected to do so; however, self-nomination is encouraged for
active participants. A vote and discussion by the existing Maintainers will be
used to decide the outcome.
Note that Contributors should demonstrate a high standard of continuous
participation to become a Maintainer; the upper limit on the number of
Maintainers is practically bounded, and so rejection should be considered as a
real possibility. As the scope of the project changes, this limit may increase,
but it is fundamentally fluid. (If you are unsure, you are free to privately ask
existing Maintainers before self-nominating if there is room.)
A Maintainer may, at any time, cede their responsibility and step down without a
vote.
A Maintainer can be removed by other Maintainers, subject to a vote of at-least
a 2/3rds majority from the existing Maintainer group (excluding the vote of the
Maintainer in question). This can be due to lack of participation or conduct
violations, among other things. Note that Maintainers are subject to a higher
set of behavioral and communicative standards than average contributor or
participant.

View File

@ -2,22 +2,25 @@
# Jujutsu—a version control system # Jujutsu—a version control system
![](https://img.shields.io/github/v/release/martinvonz/jj) <p><img title="jj logo" src="docs/images/jj-logo.svg" width="320" height="320"></p>
![](https://img.shields.io/github/release-date/martinvonz/jj)
[![Release](https://img.shields.io/github/v/release/martinvonz/jj)](https://github.com/jj-vcs/jj/releases)
[![Release date](https://img.shields.io/github/release-date/martinvonz/jj)](https://github.com/jj-vcs/jj/releases)
<br/> <br/>
![](https://img.shields.io/github/license/martinvonz/jj) [![License](https://img.shields.io/github/license/martinvonz/jj)](https://github.com/jj-vcs/jj/blob/main/LICENSE)
![](https://github.com/martinvonz/jj/workflows/build/badge.svg)
[![Discord](https://img.shields.io/discord/968932220549103686.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/dkmfj3aGQN) [![Discord](https://img.shields.io/discord/968932220549103686.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/dkmfj3aGQN)
[![IRC](https://img.shields.io/badge/irc-%23jujutsu-blue.svg)](https://web.libera.chat/?channel=#jujutsu)
**[Homepage] &nbsp;&nbsp;&bull;&nbsp;&nbsp;** **[Homepage] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Installation] &nbsp;&nbsp;&bull;&nbsp;&nbsp;** **[Installation] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Getting Started] &nbsp;&nbsp;&bull;&nbsp;&nbsp;** **[Getting Started] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Development Roadmap]** **[Development Roadmap] &nbsp;&nbsp;&bull;&nbsp;&nbsp;**
**[Contributing](#contributing)**
[Homepage]: https://martinvonz.github.io/jj [Homepage]: https://jj-vcs.github.io/jj
[Installation]: https://martinvonz.github.io/jj/latest/install-and-setup [Installation]: https://jj-vcs.github.io/jj/latest/install-and-setup
[Getting Started]: https://martinvonz.github.io/jj/latest/tutorial [Getting Started]: https://jj-vcs.github.io/jj/latest/tutorial
[Development Roadmap]: https://martinvonz.github.io/jj/latest/roadmap [Development Roadmap]: https://jj-vcs.github.io/jj/latest/roadmap
</div> </div>
@ -67,10 +70,10 @@ systems into a single tool. Some of those sources of inspiration include:
theory of patches, as opposed to snapshots), the effect is that many forms of theory of patches, as opposed to snapshots), the effect is that many forms of
conflict resolution can be performed and propagated automatically. conflict resolution can be performed and propagated automatically.
[perf]: https://github.com/martinvonz/jj/discussions/49 [perf]: https://github.com/jj-vcs/jj/discussions/49
[revset]: https://martinvonz.github.io/jj/latest/revsets/ [revset]: https://jj-vcs.github.io/jj/latest/revsets/
[no-index]: https://martinvonz.github.io/jj/latest/git-comparison/#the-index [no-index]: https://jj-vcs.github.io/jj/latest/git-comparison/#the-index
[conflicts]: https://martinvonz.github.io/jj/latest/conflicts/ [conflicts]: https://jj-vcs.github.io/jj/latest/conflicts/
And it adds several innovative, useful features of its own: And it adds several innovative, useful features of its own:
@ -115,9 +118,9 @@ And it adds several innovative, useful features of its own:
_should_ happen is that it will expose conflicts between the local and remote _should_ happen is that it will expose conflicts between the local and remote
state, leaving you to resolve them. state, leaving you to resolve them.
[wcc]: https://martinvonz.github.io/jj/latest/working-copy/ [wcc]: https://jj-vcs.github.io/jj/latest/working-copy/
[undo-history]: https://en.wikipedia.org/wiki/Undo#History [undo-history]: https://en.wikipedia.org/wiki/Undo#History
[conc-safety]: https://martinvonz.github.io/jj/latest/technical/concurrency/ [conc-safety]: https://jj-vcs.github.io/jj/latest/technical/concurrency/
The command-line tool is called `jj` for now because it's easy to type and easy The command-line tool is called `jj` for now because it's easy to type and easy
to replace (rare in English). The project is called "Jujutsu" because it matches to replace (rare in English). The project is called "Jujutsu" because it matches
@ -125,12 +128,24 @@ to replace (rare in English). The project is called "Jujutsu" because it matches
Jujutsu is relatively young, with lots of work to still be done. If you have any Jujutsu is relatively young, with lots of work to still be done. If you have any
questions, or want to talk about future plans, please join us on Discord questions, or want to talk about future plans, please join us on Discord
[![Discord](https://img.shields.io/discord/968932220549103686.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/dkmfj3aGQN) [![Discord](https://img.shields.io/discord/968932220549103686.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/dkmfj3aGQN),
or start a [GitHub Discussion](https://github.com/martinvonz/jj/discussions); the start a [GitHub Discussion](https://github.com/jj-vcs/jj/discussions), or
developers monitor both channels. send an IRC message to [`#jujutsu` on Libera
Chat](https://web.libera.chat/?channel=#jujutsu). The developers monitor all of
these channels[^bridge].
[^bridge]: To be more precise, the `#jujutsu` Libera IRC channel is bridged to
one of the channels on jj's Discord. Some of the developers stay on Discord and
use the bridge to follow IRC.
### News and Updates 📣 ### News and Updates 📣
- **December 2024**: The `jj` Repository has moved to the `jj-vcs` GitHub
organisation.
- **November 2024**: Version 0.24 is released which adds `jj file annotate`,
which is equivalent to `git blame` or `hg annotate`.
- **September 2024**: Martin gave a [presentation about Jujutsu][merge-vid-2024] at
Git Merge 2024.
- **Feb 2024**: Version 0.14 is released, which deprecates ["jj checkout" and "jj merge"](CHANGELOG.md#0140---2024-02-07), - **Feb 2024**: Version 0.14 is released, which deprecates ["jj checkout" and "jj merge"](CHANGELOG.md#0140---2024-02-07),
as well as `jj init --git`, which is now just called `jj git init`. as well as `jj init --git`, which is now just called `jj git init`.
- **Oct 2023**: Version 0.10.0 is released! Now includes a bundled merge and - **Oct 2023**: Version 0.10.0 is released! Now includes a bundled merge and
@ -158,7 +173,8 @@ The wiki also contains a more extensive list of [media references][wiki-media].
[lwn]: https://lwn.net/Articles/958468/ [lwn]: https://lwn.net/Articles/958468/
[merge-talk]: https://www.youtube.com/watch?v=bx_LGilOuE4 [merge-talk]: https://www.youtube.com/watch?v=bx_LGilOuE4
[merge-slides]: https://docs.google.com/presentation/d/1F8j9_UOOSGUN9MvHxPZX_L4bQ9NMcYOp1isn17kTC_M/view [merge-slides]: https://docs.google.com/presentation/d/1F8j9_UOOSGUN9MvHxPZX_L4bQ9NMcYOp1isn17kTC_M/view
[wiki-media]: https://github.com/martinvonz/jj/wiki/Media [merge-vid-2024]: https://www.youtube.com/watch?v=LV0JzI8IcCY
[wiki-media]: https://github.com/jj-vcs/jj/wiki/Media
## Getting started ## Getting started
@ -169,23 +185,25 @@ The wiki also contains a more extensive list of [media references][wiki-media].
> it unusable for your particular use. > it unusable for your particular use.
Follow the [installation Follow the [installation
instructions](https://martinvonz.github.io/jj/latest/install-and-setup) to instructions](https://jj-vcs.github.io/jj/latest/install-and-setup) to
obtain and configure `jj`. obtain and configure `jj`.
The best way to get started is probably to go through [the The best way to get started is probably to go through [the
tutorial](https://martinvonz.github.io/jj/latest/tutorial). Also see the [Git tutorial](https://jj-vcs.github.io/jj/latest/tutorial). Also see the [Git
comparison](https://martinvonz.github.io/jj/latest/git-comparison), which comparison](https://jj-vcs.github.io/jj/latest/git-comparison), which
includes a table of `jj` vs. `git` commands. includes a table of `jj` vs. `git` commands.
As you become more familiar with Jujutsu, the following resources may be helpful: As you become more familiar with Jujutsu, the following resources may be helpful:
- The [FAQ](https://martinvonz.github.io/jj/latest/FAQ). - The [FAQ](https://jj-vcs.github.io/jj/latest/FAQ).
- The [Glossary](https://martinvonz.github.io/jj/latest/glossary). - The [Glossary](https://jj-vcs.github.io/jj/latest/glossary).
- The `jj help` command (e.g. `jj help rebase`). - The `jj help` command (e.g. `jj help rebase`).
- The `jj help -k <keyword>` command (e.g. `jj help -k config`). Use `jj help --help`
to see what keywords are available.
If you are using a **prerelease** version of `jj`, you would want to consult If you are using a **prerelease** version of `jj`, you would want to consult
[the docs for the prerelease (main branch) [the docs for the prerelease (main branch)
version](https://martinvonz.github.io/jj/prerelease/). You can also get there version](https://jj-vcs.github.io/jj/prerelease/). You can also get there
from the docs for the latest release by using the website's version switcher. The version switcher is visible in from the docs for the latest release by using the website's version switcher. The version switcher is visible in
the header of the website when you scroll to the top of any page. the header of the website when you scroll to the top of any page.
@ -194,16 +212,11 @@ the header of the website when you scroll to the top of any page.
### Compatible with Git ### Compatible with Git
Jujutsu is designed so that the underlying data and storage model is abstract. Jujutsu is designed so that the underlying data and storage model is abstract.
Today, it features two [backends]—one of them uses a Git repository for storage, Today, only the Git backend is production-ready. The Git backend uses the
while the other is a native storage backend[^native-backend]. The Git backend [libgit2](https://libgit2.org/) C library and the
uses the [libgit2](https://libgit2.org/) C library and the
[gitoxide](https://github.com/Byron/gitoxide) Rust library. [gitoxide](https://github.com/Byron/gitoxide) Rust library.
[backends]: https://martinvonz.github.io/jj/latest/glossary#backend [backends]: https://jj-vcs.github.io/jj/latest/glossary#backend
[^native-backend]: At this time, there's practically no reason to use the native
backend. The backend exists mainly to make sure that it's possible to eventually
add functionality that cannot easily be added to the Git backend.
The Git backend is fully featured and maintained, and allows you to use Jujutsu The Git backend is fully featured and maintained, and allows you to use Jujutsu
with any Git remote. The commits you create will look like regular Git commits. with any Git remote. The commits you create will look like regular Git commits.
@ -215,7 +228,7 @@ Here is how you can explore a GitHub repository with `jj`.
<img src="demos/git_compat.png" /> <img src="demos/git_compat.png" />
You can even have a ["co-located" local You can even have a ["co-located" local
repository](https://martinvonz.github.io/jj/latest/git-compatibility#co-located-jujutsugit-repos) repository](https://jj-vcs.github.io/jj/latest/git-compatibility#co-located-jujutsugit-repos)
where you can use both `jj` and `git` commands interchangeably. where you can use both `jj` and `git` commands interchangeably.
### The working copy is automatically committed ### The working copy is automatically committed
@ -256,7 +269,7 @@ necessarily have to be the most recent operation).
### Conflicts can be recorded in commits ### Conflicts can be recorded in commits
If an operation results in If an operation results in
[conflicts](https://martinvonz.github.io/jj/latest/glossary#conflict), [conflicts](https://jj-vcs.github.io/jj/latest/glossary#conflict),
information about those conflicts will be recorded in the commit(s). The information about those conflicts will be recorded in the commit(s). The
operation will succeed. You can then resolve the conflicts later. One operation will succeed. You can then resolve the conflicts later. One
consequence of this design is that there's no need to continue interrupted consequence of this design is that there's no need to continue interrupted
@ -289,8 +302,8 @@ commit to any other commit using `jj squash -i --from X --into Y`.
## Status ## Status
The tool is fairly feature-complete, but some important features like (the The tool is fairly feature-complete, but some important features like support
equivalent of) `git blame` are not yet supported. There for Git submodules are not yet completed. There
are also several performance bugs. It's likely that workflows and setups are also several performance bugs. It's likely that workflows and setups
different from what the core developers use are not well supported, e.g. there different from what the core developers use are not well supported, e.g. there
is no native support for email-based workflows. is no native support for email-based workflows.
@ -309,7 +322,7 @@ scripts if requested.
## Related work ## Related work
There are several tools trying to solve similar problems as Jujutsu. See There are several tools trying to solve similar problems as Jujutsu. See
[related work](https://martinvonz.github.io/jj/latest/related-work) for details. [related work](https://jj-vcs.github.io/jj/latest/related-work) for details.
## Contributing ## Contributing
@ -318,7 +331,7 @@ don't be shy. Please ask if you want a pointer on something you can help with,
and hopefully we can all figure something out. and hopefully we can all figure something out.
We do have [a few policies and We do have [a few policies and
suggestions](https://martinvonz.github.io/jj/prerelease/contributing/) suggestions](https://jj-vcs.github.io/jj/prerelease/contributing/)
for contributors. The broad TL;DR: for contributors. The broad TL;DR:
- Bug reports are very welcome! - Bug reports are very welcome!
@ -338,4 +351,7 @@ That said, **this is not a Google product**.
## License ## License
Jujutsu is available as Open Source Software, under the Apache 2.0 license. See Jujutsu is available as Open Source Software, under the Apache 2.0 license. See
[LICENSE](./LICENSE) for details about copyright and redistribution. [`LICENSE`](./LICENSE) for details about copyright and redistribution.
The `jj` logo was contributed by J. Jennings and is licensed under a Creative
Commons License, see [`docs/images/LICENSE`](docs/images/LICENSE).

View File

@ -1,7 +1,10 @@
To report a security issue, please To report a security issue, please use the "Report a vulnerability" button on
email Jujutsu VCS Security at <jj-security@googlegroups.com> GitHub's Security tab for `jj`'s main repo, under
with a description of the issue, the steps you took to create the issue, [Advisories](https://github.com/jj-vcs/jj/security/advisories).
affected versions, and, if known, mitigations for the issue. Our vulnerability
management team will respond within 3 working days of your email. If the issue Our vulnerability management team will respond within 3 working days of your
is confirmed as a vulnerability, we will open a Security Advisory. This project report. If the issue is confirmed as a vulnerability, we will open a Security
follows a 90 day disclosure timeline. Advisory. This project follows a 90 day disclosure timeline.
Feel free to email Jujutsu VCS Security at <jj-security@googlegroups.com> if you
have questions.

View File

@ -23,7 +23,7 @@ include = [
"/tests/", "/tests/",
"!*.pending-snap", "!*.pending-snap",
"!*.snap*", "!*.snap*",
"/tests/cli-reference@.md.snap" "/tests/cli-reference@.md.snap",
] ]
[[bin]] [[bin]]
@ -48,6 +48,10 @@ required-features = ["test-fakes"]
[[test]] [[test]]
name = "runner" name = "runner"
[[test]]
name = "datatest_runner"
harness = false
[dependencies] [dependencies]
bstr = { workspace = true } bstr = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
@ -56,30 +60,32 @@ clap-markdown = { workspace = true }
clap_complete = { workspace = true } clap_complete = { workspace = true }
clap_complete_nushell = { workspace = true } clap_complete_nushell = { workspace = true }
clap_mangen = { workspace = true } clap_mangen = { workspace = true }
config = { workspace = true }
criterion = { workspace = true, optional = true } criterion = { workspace = true, optional = true }
crossterm = { workspace = true } crossterm = { workspace = true }
dirs = { workspace = true }
dunce = { workspace = true } dunce = { workspace = true }
esl01-renderdag = { workspace = true } etcetera = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
git2 = { workspace = true } git2 = { workspace = true, optional = true }
gix = { workspace = true } gix = { workspace = true, optional = true }
glob = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
indoc = { workspace = true } indoc = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
jj-lib = { workspace = true } jj-lib = { workspace = true }
maplit = { workspace = true } maplit = { workspace = true }
minus = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
os_pipe = { workspace = true }
pest = { workspace = true } pest = { workspace = true }
pest_derive = { workspace = true } pest_derive = { workspace = true }
pollster = { workspace = true } pollster = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
rpassword = { workspace = true } rpassword = { workspace = true }
sapling-renderdag = { workspace = true }
sapling-streampager = { workspace = true }
scm-record = { workspace = true } scm-record = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true }
slab = { workspace = true } slab = { workspace = true }
strsim = { workspace = true } strsim = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
@ -91,15 +97,16 @@ tracing = { workspace = true }
tracing-chrome = { workspace = true } tracing-chrome = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }
whoami = { workspace = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = { workspace = true } libc = { workspace = true }
[dev-dependencies] [dev-dependencies]
anyhow = { workspace = true }
assert_cmd = { workspace = true } assert_cmd = { workspace = true }
assert_matches = { workspace = true } assert_matches = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
datatest-stable = { workspace = true }
insta = { workspace = true } insta = { workspace = true }
test-case = { workspace = true } test-case = { workspace = true }
testutils = { workspace = true } testutils = { workspace = true }
@ -107,9 +114,10 @@ testutils = { workspace = true }
jj-cli = { path = ".", features = ["test-fakes"], default-features = false } jj-cli = { path = ".", features = ["test-fakes"], default-features = false }
[features] [features]
default = ["watchman"] default = ["watchman", "git", "git2"]
bench = ["dep:criterion"] bench = ["dep:criterion"]
packaging = [] git = ["jj-lib/git", "dep:gix"]
git2 = ["git", "jj-lib/git2", "dep:git2"]
test-fakes = ["jj-lib/testing"] test-fakes = ["jj-lib/testing"]
vendored-openssl = ["git2/vendored-openssl", "jj-lib/vendored-openssl"] vendored-openssl = ["git2/vendored-openssl", "jj-lib/vendored-openssl"]
watchman = ["jj-lib/watchman"] watchman = ["jj-lib/watchman"]

View File

@ -73,13 +73,13 @@ fn run_custom_command(
match command { match command {
CustomCommand::InitJit => { CustomCommand::InitJit => {
let wc_path = command_helper.cwd(); let wc_path = command_helper.cwd();
let settings = command_helper.settings_for_new_workspace(wc_path)?;
// Initialize a workspace with the custom backend // Initialize a workspace with the custom backend
Workspace::init_with_backend( Workspace::init_with_backend(
command_helper.settings(), &settings,
wc_path, wc_path,
&|settings, store_path| Ok(Box::new(JitBackend::init(settings, store_path)?)), &|settings, store_path| Ok(Box::new(JitBackend::init(settings, store_path)?)),
Signer::from_settings(command_helper.settings()) Signer::from_settings(&settings).map_err(WorkspaceInitError::SignInit)?,
.map_err(WorkspaceInitError::SignInit)?,
)?; )?;
Ok(()) Ok(())
} }

View File

@ -45,7 +45,7 @@ fn run_custom_command(
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let new_commit = tx let new_commit = tx
.repo_mut() .repo_mut()
.rewrite_commit(command_helper.settings(), &commit) .rewrite_commit(&commit)
.set_description("Frobnicated!") .set_description("Frobnicated!")
.write()?; .write()?;
tx.finish(ui, "frobnicate")?; tx.finish(ui, "frobnicate")?;

View File

@ -15,30 +15,31 @@
use std::any::Any; use std::any::Any;
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools; use itertools::Itertools as _;
use jj_cli::cli_util::CliRunner; use jj_cli::cli_util::CliRunner;
use jj_cli::commit_templater::CommitTemplateBuildFnTable; use jj_cli::commit_templater::CommitTemplateBuildFnTable;
use jj_cli::commit_templater::CommitTemplateLanguage;
use jj_cli::commit_templater::CommitTemplateLanguageExtension; use jj_cli::commit_templater::CommitTemplateLanguageExtension;
use jj_cli::template_builder::TemplateLanguage; use jj_cli::commit_templater::CommitTemplatePropertyKind;
use jj_cli::template_builder::CoreTemplatePropertyVar as _;
use jj_cli::template_parser; use jj_cli::template_parser;
use jj_cli::template_parser::TemplateParseError; use jj_cli::template_parser::TemplateParseError;
use jj_cli::templater::TemplatePropertyExt as _; use jj_cli::templater::TemplatePropertyExt as _;
use jj_lib::backend::CommitId; use jj_lib::backend::CommitId;
use jj_lib::commit::Commit; use jj_lib::commit::Commit;
use jj_lib::extensions_map::ExtensionsMap; use jj_lib::extensions_map::ExtensionsMap;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use jj_lib::repo::Repo; use jj_lib::repo::Repo;
use jj_lib::revset::FunctionCallNode; use jj_lib::revset::FunctionCallNode;
use jj_lib::revset::LoweringContext;
use jj_lib::revset::PartialSymbolResolver; use jj_lib::revset::PartialSymbolResolver;
use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetDiagnostics;
use jj_lib::revset::RevsetExpression; use jj_lib::revset::RevsetExpression;
use jj_lib::revset::RevsetFilterExtension; use jj_lib::revset::RevsetFilterExtension;
use jj_lib::revset::RevsetFilterPredicate; use jj_lib::revset::RevsetFilterPredicate;
use jj_lib::revset::RevsetParseContext;
use jj_lib::revset::RevsetParseError; use jj_lib::revset::RevsetParseError;
use jj_lib::revset::RevsetResolutionError; use jj_lib::revset::RevsetResolutionError;
use jj_lib::revset::SymbolResolverExtension; use jj_lib::revset::SymbolResolverExtension;
use jj_lib::revset::UserRevsetExpression;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
struct HexCounter; struct HexCounter;
@ -72,7 +73,7 @@ impl MostDigitsInId {
fn count(&self, repo: &dyn Repo) -> i64 { fn count(&self, repo: &dyn Repo) -> i64 {
*self.count.get_or_init(|| { *self.count.get_or_init(|| {
RevsetExpression::all() RevsetExpression::all()
.evaluate_programmatic(repo) .evaluate(repo)
.unwrap() .unwrap()
.iter() .iter()
.map(Result::unwrap) .map(Result::unwrap)
@ -100,7 +101,7 @@ impl PartialSymbolResolver for TheDigitestResolver {
Ok(Some( Ok(Some(
RevsetExpression::all() RevsetExpression::all()
.evaluate_programmatic(repo) .evaluate(repo)
.map_err(|err| RevsetResolutionError::Other(err.into()))? .map_err(|err| RevsetResolutionError::Other(err.into()))?
.iter() .iter()
.map(Result::unwrap) .map(Result::unwrap)
@ -120,7 +121,7 @@ impl SymbolResolverExtension for TheDigitest {
impl CommitTemplateLanguageExtension for HexCounter { impl CommitTemplateLanguageExtension for HexCounter {
fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo> { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo> {
type L<'repo> = CommitTemplateLanguage<'repo>; type P<'repo> = CommitTemplatePropertyKind<'repo>;
let mut table = CommitTemplateBuildFnTable::empty(); let mut table = CommitTemplateBuildFnTable::empty();
table.commit_methods.insert( table.commit_methods.insert(
"has_most_digits", "has_most_digits",
@ -130,18 +131,17 @@ impl CommitTemplateLanguageExtension for HexCounter {
.cache_extension::<MostDigitsInId>() .cache_extension::<MostDigitsInId>()
.unwrap() .unwrap()
.count(language.repo()); .count(language.repo());
Ok(L::wrap_boolean(property.map(move |commit| { let out_property =
num_digits_in_id(commit.id()) == most_digits property.map(move |commit| num_digits_in_id(commit.id()) == most_digits);
}))) Ok(P::wrap_boolean(out_property.into_dyn()))
}, },
); );
table.commit_methods.insert( table.commit_methods.insert(
"num_digits_in_id", "num_digits_in_id",
|_language, _diagnostics, _build_context, property, call| { |_language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?; call.expect_no_arguments()?;
Ok(L::wrap_integer( let out_property = property.map(|commit| num_digits_in_id(commit.id()));
property.map(|commit| num_digits_in_id(commit.id())), Ok(P::wrap_integer(out_property.into_dyn()))
))
}, },
); );
table.commit_methods.insert( table.commit_methods.insert(
@ -160,9 +160,8 @@ impl CommitTemplateLanguageExtension for HexCounter {
} }
})?; })?;
Ok(L::wrap_integer( let out_property = property.map(move |commit| num_char_in_id(commit, char_arg));
property.map(move |commit| num_char_in_id(commit, char_arg)), Ok(P::wrap_integer(out_property.into_dyn()))
))
}, },
); );
@ -190,8 +189,8 @@ impl RevsetFilterExtension for EvenDigitsFilter {
fn even_digits( fn even_digits(
_diagnostics: &mut RevsetDiagnostics, _diagnostics: &mut RevsetDiagnostics,
function: &FunctionCallNode, function: &FunctionCallNode,
_context: &RevsetParseContext, _context: &LoweringContext,
) -> Result<Rc<RevsetExpression>, RevsetParseError> { ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
function.expect_no_arguments()?; function.expect_no_arguments()?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::Extension( Ok(RevsetExpression::filter(RevsetFilterPredicate::Extension(
Rc::new(EvenDigitsFilter), Rc::new(EvenDigitsFilter),

View File

@ -14,14 +14,14 @@
use jj_cli::cli_util::CliRunner; use jj_cli::cli_util::CliRunner;
use jj_cli::operation_templater::OperationTemplateBuildFnTable; use jj_cli::operation_templater::OperationTemplateBuildFnTable;
use jj_cli::operation_templater::OperationTemplateLanguage;
use jj_cli::operation_templater::OperationTemplateLanguageExtension; use jj_cli::operation_templater::OperationTemplateLanguageExtension;
use jj_cli::template_builder::TemplateLanguage; use jj_cli::operation_templater::OperationTemplatePropertyKind;
use jj_cli::template_builder::CoreTemplatePropertyVar as _;
use jj_cli::template_parser; use jj_cli::template_parser;
use jj_cli::template_parser::TemplateParseError; use jj_cli::template_parser::TemplateParseError;
use jj_cli::templater::TemplatePropertyExt as _; use jj_cli::templater::TemplatePropertyExt as _;
use jj_lib::extensions_map::ExtensionsMap; use jj_lib::extensions_map::ExtensionsMap;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use jj_lib::op_store::OperationId; use jj_lib::op_store::OperationId;
use jj_lib::operation::Operation; use jj_lib::operation::Operation;
@ -49,15 +49,14 @@ fn num_char_in_id(operation: Operation, ch_match: char) -> i64 {
impl OperationTemplateLanguageExtension for HexCounter { impl OperationTemplateLanguageExtension for HexCounter {
fn build_fn_table(&self) -> OperationTemplateBuildFnTable { fn build_fn_table(&self) -> OperationTemplateBuildFnTable {
type L = OperationTemplateLanguage; type P = OperationTemplatePropertyKind;
let mut table = OperationTemplateBuildFnTable::empty(); let mut table = OperationTemplateBuildFnTable::empty();
table.operation_methods.insert( table.operation_methods.insert(
"num_digits_in_id", "num_digits_in_id",
|_language, _diagnostics, _build_context, property, call| { |_language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?; call.expect_no_arguments()?;
Ok(L::wrap_integer( let out_property = property.map(|operation| num_digits_in_id(operation.id()));
property.map(|operation| num_digits_in_id(operation.id())), Ok(P::wrap_integer(out_property.into_dyn()))
))
}, },
); );
table.operation_methods.insert( table.operation_methods.insert(
@ -76,9 +75,9 @@ impl OperationTemplateLanguageExtension for HexCounter {
} }
})?; })?;
Ok(L::wrap_integer( let out_property =
property.map(move |operation| num_char_in_id(operation, char_arg)), property.map(move |operation| num_char_in_id(operation, char_arg));
)) Ok(P::wrap_integer(out_property.into_dyn()))
}, },
); );

View File

@ -17,7 +17,7 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use itertools::Itertools; use itertools::Itertools as _;
use jj_cli::cli_util::CliRunner; use jj_cli::cli_util::CliRunner;
use jj_cli::cli_util::CommandHelper; use jj_cli::cli_util::CommandHelper;
use jj_cli::command_error::CommandError; use jj_cli::command_error::CommandError;
@ -28,18 +28,21 @@ use jj_lib::commit::Commit;
use jj_lib::git_backend::GitBackend; use jj_lib::git_backend::GitBackend;
use jj_lib::local_working_copy::LocalWorkingCopy; use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::op_store::OperationId; use jj_lib::op_store::OperationId;
use jj_lib::op_store::WorkspaceId; use jj_lib::ref_name::WorkspaceName;
use jj_lib::ref_name::WorkspaceNameBuf;
use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::ReadonlyRepo;
use jj_lib::repo_path::RepoPathBuf; use jj_lib::repo_path::RepoPathBuf;
use jj_lib::settings::UserSettings; use jj_lib::settings::UserSettings;
use jj_lib::signing::Signer; use jj_lib::signing::Signer;
use jj_lib::store::Store; use jj_lib::store::Store;
use jj_lib::working_copy::CheckoutError; use jj_lib::working_copy::CheckoutError;
use jj_lib::working_copy::CheckoutOptions;
use jj_lib::working_copy::CheckoutStats; use jj_lib::working_copy::CheckoutStats;
use jj_lib::working_copy::LockedWorkingCopy; use jj_lib::working_copy::LockedWorkingCopy;
use jj_lib::working_copy::ResetError; use jj_lib::working_copy::ResetError;
use jj_lib::working_copy::SnapshotError; use jj_lib::working_copy::SnapshotError;
use jj_lib::working_copy::SnapshotOptions; use jj_lib::working_copy::SnapshotOptions;
use jj_lib::working_copy::SnapshotStats;
use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopy;
use jj_lib::working_copy::WorkingCopyFactory; use jj_lib::working_copy::WorkingCopyFactory;
use jj_lib::working_copy::WorkingCopyStateError; use jj_lib::working_copy::WorkingCopyStateError;
@ -61,23 +64,23 @@ fn run_custom_command(
match command { match command {
CustomCommand::InitConflicts => { CustomCommand::InitConflicts => {
let wc_path = command_helper.cwd(); let wc_path = command_helper.cwd();
let settings = command_helper.settings_for_new_workspace(wc_path)?;
let backend_initializer = |settings: &UserSettings, store_path: &Path| { let backend_initializer = |settings: &UserSettings, store_path: &Path| {
let backend: Box<dyn Backend> = let backend: Box<dyn Backend> =
Box::new(GitBackend::init_internal(settings, store_path)?); Box::new(GitBackend::init_internal(settings, store_path)?);
Ok(backend) Ok(backend)
}; };
Workspace::init_with_factories( Workspace::init_with_factories(
command_helper.settings(), &settings,
wc_path, wc_path,
&backend_initializer, &backend_initializer,
Signer::from_settings(command_helper.settings()) Signer::from_settings(&settings).map_err(WorkspaceInitError::SignInit)?,
.map_err(WorkspaceInitError::SignInit)?,
&ReadonlyRepo::default_op_store_initializer(), &ReadonlyRepo::default_op_store_initializer(),
&ReadonlyRepo::default_op_heads_store_initializer(), &ReadonlyRepo::default_op_heads_store_initializer(),
&ReadonlyRepo::default_index_store_initializer(), &ReadonlyRepo::default_index_store_initializer(),
&ReadonlyRepo::default_submodule_store_initializer(), &ReadonlyRepo::default_submodule_store_initializer(),
&ConflictsWorkingCopyFactory {}, &ConflictsWorkingCopyFactory {},
WorkspaceId::default(), WorkspaceName::DEFAULT.to_owned(),
)?; )?;
Ok(()) Ok(())
} }
@ -118,14 +121,14 @@ impl ConflictsWorkingCopy {
working_copy_path: PathBuf, working_copy_path: PathBuf,
state_path: PathBuf, state_path: PathBuf,
operation_id: OperationId, operation_id: OperationId,
workspace_id: WorkspaceId, workspace_name: WorkspaceNameBuf,
) -> Result<Self, WorkingCopyStateError> { ) -> Result<Self, WorkingCopyStateError> {
let inner = LocalWorkingCopy::init( let inner = LocalWorkingCopy::init(
store, store,
working_copy_path.clone(), working_copy_path.clone(),
state_path, state_path,
operation_id, operation_id,
workspace_id, workspace_name,
)?; )?;
Ok(ConflictsWorkingCopy { Ok(ConflictsWorkingCopy {
inner: Box::new(inner), inner: Box::new(inner),
@ -151,8 +154,8 @@ impl WorkingCopy for ConflictsWorkingCopy {
Self::name() Self::name()
} }
fn workspace_id(&self) -> &WorkspaceId { fn workspace_name(&self) -> &WorkspaceName {
self.inner.workspace_id() self.inner.workspace_name()
} }
fn operation_id(&self) -> &OperationId { fn operation_id(&self) -> &OperationId {
@ -185,14 +188,14 @@ impl WorkingCopyFactory for ConflictsWorkingCopyFactory {
working_copy_path: PathBuf, working_copy_path: PathBuf,
state_path: PathBuf, state_path: PathBuf,
operation_id: OperationId, operation_id: OperationId,
workspace_id: WorkspaceId, workspace_name: WorkspaceNameBuf,
) -> Result<Box<dyn WorkingCopy>, WorkingCopyStateError> { ) -> Result<Box<dyn WorkingCopy>, WorkingCopyStateError> {
Ok(Box::new(ConflictsWorkingCopy::init( Ok(Box::new(ConflictsWorkingCopy::init(
store, store,
working_copy_path, working_copy_path,
state_path, state_path,
operation_id, operation_id,
workspace_id, workspace_name,
)?)) )?))
} }
@ -232,26 +235,37 @@ impl LockedWorkingCopy for LockedConflictsWorkingCopy {
self.inner.old_tree_id() self.inner.old_tree_id()
} }
fn snapshot(&mut self, options: &SnapshotOptions) -> Result<MergedTreeId, SnapshotError> { fn snapshot(
&mut self,
options: &SnapshotOptions,
) -> Result<(MergedTreeId, SnapshotStats), SnapshotError> {
let options = SnapshotOptions { let options = SnapshotOptions {
base_ignores: options.base_ignores.chain("", "/.conflicts".as_bytes())?, base_ignores: options.base_ignores.chain(
"",
Path::new(""),
"/.conflicts".as_bytes(),
)?,
..options.clone() ..options.clone()
}; };
self.inner.snapshot(&options) self.inner.snapshot(&options)
} }
fn check_out(&mut self, commit: &Commit) -> Result<CheckoutStats, CheckoutError> { fn check_out(
&mut self,
commit: &Commit,
options: &CheckoutOptions,
) -> Result<CheckoutStats, CheckoutError> {
let conflicts = commit let conflicts = commit
.tree()? .tree()?
.conflicts() .conflicts()
.map(|(path, _value)| format!("{}\n", path.as_internal_file_string())) .map(|(path, _value)| format!("{}\n", path.as_internal_file_string()))
.join(""); .join("");
std::fs::write(self.wc_path.join(".conflicts"), conflicts).unwrap(); std::fs::write(self.wc_path.join(".conflicts"), conflicts).unwrap();
self.inner.check_out(commit) self.inner.check_out(commit, options)
} }
fn rename_workspace(&mut self, new_workspace_id: WorkspaceId) { fn rename_workspace(&mut self, new_name: WorkspaceNameBuf) {
self.inner.rename_workspace(new_workspace_id); self.inner.rename_workspace(new_name);
} }
fn reset(&mut self, commit: &Commit) -> Result<(), ResetError> { fn reset(&mut self, commit: &Commit) -> Result<(), ResetError> {
@ -269,8 +283,9 @@ impl LockedWorkingCopy for LockedConflictsWorkingCopy {
fn set_sparse_patterns( fn set_sparse_patterns(
&mut self, &mut self,
new_sparse_patterns: Vec<RepoPathBuf>, new_sparse_patterns: Vec<RepoPathBuf>,
options: &CheckoutOptions,
) -> Result<CheckoutStats, CheckoutError> { ) -> Result<CheckoutStats, CheckoutError> {
self.inner.set_sparse_patterns(new_sparse_patterns) self.inner.set_sparse_patterns(new_sparse_patterns, options)
} }
fn finish( fn finish(

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
use std::error; use std::error;
use std::error::Error as _;
use std::io; use std::io;
use std::io::Write as _; use std::io::Write as _;
use std::iter; use std::iter;
@ -21,17 +22,20 @@ use std::str;
use std::sync::Arc; use std::sync::Arc;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::absorb::AbsorbError;
use jj_lib::backend::BackendError; use jj_lib::backend::BackendError;
use jj_lib::config::ConfigFileSaveError;
use jj_lib::config::ConfigGetError;
use jj_lib::config::ConfigLoadError;
use jj_lib::config::ConfigMigrateError;
use jj_lib::dsl_util::Diagnostics; use jj_lib::dsl_util::Diagnostics;
use jj_lib::fileset::FilePatternParseError; use jj_lib::fileset::FilePatternParseError;
use jj_lib::fileset::FilesetParseError; use jj_lib::fileset::FilesetParseError;
use jj_lib::fileset::FilesetParseErrorKind; use jj_lib::fileset::FilesetParseErrorKind;
use jj_lib::git::GitConfigParseError; use jj_lib::fix::FixError;
use jj_lib::git::GitExportError;
use jj_lib::git::GitImportError;
use jj_lib::git::GitRemoteManagementError;
use jj_lib::gitignore::GitIgnoreError; use jj_lib::gitignore::GitIgnoreError;
use jj_lib::op_heads_store::OpHeadResolutionError; use jj_lib::op_heads_store::OpHeadResolutionError;
use jj_lib::op_heads_store::OpHeadsStoreError;
use jj_lib::op_store::OpStoreError; use jj_lib::op_store::OpStoreError;
use jj_lib::op_walk::OpsetEvaluationError; use jj_lib::op_walk::OpsetEvaluationError;
use jj_lib::op_walk::OpsetResolutionError; use jj_lib::op_walk::OpsetResolutionError;
@ -41,13 +45,16 @@ use jj_lib::repo::RepoLoaderError;
use jj_lib::repo::RewriteRootCommit; use jj_lib::repo::RewriteRootCommit;
use jj_lib::repo_path::RepoPathBuf; use jj_lib::repo_path::RepoPathBuf;
use jj_lib::repo_path::UiPathParseError; use jj_lib::repo_path::UiPathParseError;
use jj_lib::revset;
use jj_lib::revset::RevsetEvaluationError; use jj_lib::revset::RevsetEvaluationError;
use jj_lib::revset::RevsetParseError; use jj_lib::revset::RevsetParseError;
use jj_lib::revset::RevsetParseErrorKind; use jj_lib::revset::RevsetParseErrorKind;
use jj_lib::revset::RevsetResolutionError; use jj_lib::revset::RevsetResolutionError;
use jj_lib::signing::SignInitError;
use jj_lib::str_util::StringPatternParseError; use jj_lib::str_util::StringPatternParseError;
use jj_lib::trailer::TrailerParseError;
use jj_lib::transaction::TransactionCommitError;
use jj_lib::view::RenameWorkspaceError; use jj_lib::view::RenameWorkspaceError;
use jj_lib::working_copy::RecoverWorkspaceError;
use jj_lib::working_copy::ResetError; use jj_lib::working_copy::ResetError;
use jj_lib::working_copy::SnapshotError; use jj_lib::working_copy::SnapshotError;
use jj_lib::working_copy::WorkingCopyStateError; use jj_lib::working_copy::WorkingCopyStateError;
@ -56,12 +63,16 @@ use thiserror::Error;
use crate::cli_util::short_operation_hash; use crate::cli_util::short_operation_hash;
use crate::description_util::ParseBulkEditMessageError; use crate::description_util::ParseBulkEditMessageError;
use crate::description_util::TempTextEditError;
use crate::description_util::TextEditError;
use crate::diff_util::DiffRenderError; use crate::diff_util::DiffRenderError;
use crate::formatter::FormatRecorder; use crate::formatter::FormatRecorder;
use crate::formatter::Formatter; use crate::formatter::Formatter;
use crate::merge_tools::ConflictResolveError; use crate::merge_tools::ConflictResolveError;
use crate::merge_tools::DiffEditError; use crate::merge_tools::DiffEditError;
use crate::merge_tools::MergeToolConfigError; use crate::merge_tools::MergeToolConfigError;
use crate::merge_tools::MergeToolPartialResolutionError;
use crate::revset_util::BookmarkNameParseError;
use crate::revset_util::UserRevsetEvaluationError; use crate::revset_util::UserRevsetEvaluationError;
use crate::template_parser::TemplateParseError; use crate::template_parser::TemplateParseError;
use crate::template_parser::TemplateParseErrorKind; use crate::template_parser::TemplateParseErrorKind;
@ -196,6 +207,13 @@ pub fn cli_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> Command
CommandError::new(CommandErrorKind::Cli, err) CommandError::new(CommandErrorKind::Cli, err)
} }
pub fn cli_error_with_message(
message: impl Into<String>,
source: impl Into<Box<dyn error::Error + Send + Sync>>,
) -> CommandError {
CommandError::with_message(CommandErrorKind::Cli, message, source)
}
pub fn internal_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError { pub fn internal_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {
CommandError::new(CommandErrorKind::Internal, err) CommandError::new(CommandErrorKind::Internal, err)
} }
@ -211,10 +229,7 @@ fn format_similarity_hint<S: AsRef<str>>(candidates: &[S]) -> Option<String> {
match candidates { match candidates {
[] => None, [] => None,
names => { names => {
let quoted_names = names let quoted_names = names.iter().map(|s| format!("`{}`", s.as_ref())).join(", ");
.iter()
.map(|s| format!(r#""{}""#, s.as_ref()))
.join(", ");
Some(format!("Did you mean {quoted_names}?")) Some(format!("Did you mean {quoted_names}?"))
} }
} }
@ -236,15 +251,44 @@ impl From<jj_lib::file_util::PathError> for CommandError {
} }
} }
impl From<config::ConfigError> for CommandError { impl From<ConfigFileSaveError> for CommandError {
fn from(err: config::ConfigError) -> Self { fn from(err: ConfigFileSaveError) -> Self {
config_error(err) user_error(err)
} }
} }
impl From<crate::config::ConfigError> for CommandError { impl From<ConfigGetError> for CommandError {
fn from(err: crate::config::ConfigError) -> Self { fn from(err: ConfigGetError) -> Self {
config_error(err) let hint = config_get_error_hint(&err);
let mut cmd_err = config_error(err);
cmd_err.extend_hints(hint);
cmd_err
}
}
impl From<ConfigLoadError> for CommandError {
fn from(err: ConfigLoadError) -> Self {
let hint = match &err {
ConfigLoadError::Read(_) => None,
ConfigLoadError::Parse { source_path, .. } => source_path
.as_ref()
.map(|path| format!("Check the config file: {}", path.display())),
};
let mut cmd_err = config_error(err);
cmd_err.extend_hints(hint);
cmd_err
}
}
impl From<ConfigMigrateError> for CommandError {
fn from(err: ConfigMigrateError) -> Self {
let hint = err
.source_path
.as_ref()
.map(|path| format!("Check the config file: {}", path.display()));
let mut cmd_err = config_error(err);
cmd_err.extend_hints(hint);
cmd_err
} }
} }
@ -281,6 +325,12 @@ impl From<BackendError> for CommandError {
} }
} }
impl From<OpHeadsStoreError> for CommandError {
fn from(err: OpHeadsStoreError) -> Self {
internal_error_with_message("Unexpected error from operation heads store", err)
}
}
impl From<WorkspaceInitError> for CommandError { impl From<WorkspaceInitError> for CommandError {
fn from(err: WorkspaceInitError) -> Self { fn from(err: WorkspaceInitError) -> Self {
match err { match err {
@ -296,14 +346,17 @@ impl From<WorkspaceInitError> for CommandError {
WorkspaceInitError::Path(err) => { WorkspaceInitError::Path(err) => {
internal_error_with_message("Failed to access the repository", err) internal_error_with_message("Failed to access the repository", err)
} }
WorkspaceInitError::OpHeadsStore(err) => {
user_error_with_message("Failed to record initial operation", err)
}
WorkspaceInitError::Backend(err) => { WorkspaceInitError::Backend(err) => {
user_error_with_message("Failed to access the repository", err) user_error_with_message("Failed to access the repository", err)
} }
WorkspaceInitError::WorkingCopyState(err) => { WorkspaceInitError::WorkingCopyState(err) => {
internal_error_with_message("Failed to access the repository", err) internal_error_with_message("Failed to access the repository", err)
} }
WorkspaceInitError::SignInit(err @ SignInitError::UnknownBackend(_)) => user_error(err), WorkspaceInitError::SignInit(err) => user_error(err),
WorkspaceInitError::SignInit(err) => internal_error(err), WorkspaceInitError::TransactionCommit(err) => err.into(),
} }
} }
} }
@ -328,6 +381,7 @@ impl From<OpsetEvaluationError> for CommandError {
cmd_err cmd_err
} }
OpsetEvaluationError::OpHeadResolution(err) => err.into(), OpsetEvaluationError::OpHeadResolution(err) => err.into(),
OpsetEvaluationError::OpHeadsStore(err) => err.into(),
OpsetEvaluationError::OpStore(err) => err.into(), OpsetEvaluationError::OpStore(err) => err.into(),
} }
} }
@ -335,45 +389,7 @@ impl From<OpsetEvaluationError> for CommandError {
impl From<SnapshotError> for CommandError { impl From<SnapshotError> for CommandError {
fn from(err: SnapshotError) -> Self { fn from(err: SnapshotError) -> Self {
match err { internal_error_with_message("Failed to snapshot the working copy", err)
SnapshotError::NewFileTooLarge {
path,
size,
max_size,
} => {
// if the size difference is < 1KiB, then show exact bytes.
// otherwise, show in human-readable form; this avoids weird cases
// where a file is 400 bytes too large but the error says something
// like '1.0MiB, maximum size allowed is ~1.0MiB'
let size_diff = size.0 - max_size.0;
let err_str = if size_diff <= 1024 {
format!(
"it is {} bytes too large; the maximum size allowed is {} bytes ({}).",
size_diff, max_size.0, max_size,
)
} else {
format!("it is {size}; the maximum size allowed is ~{max_size}.")
};
user_error(format!(
"Failed to snapshot the working copy\nThe file '{}' is too large to be \
snapshotted: {}",
path.display(),
err_str,
))
.hinted(format!(
"This is to prevent large files from being added on accident. You can fix \
this error by:
- Adding the file to `.gitignore`
- Run `jj config set --repo snapshot.max-new-file-size {}`
This will increase the maximum file size allowed for new files, in this repository only.
- Run `jj --config-toml 'snapshot.max-new-file-size={}' st`
This will increase the maximum file size allowed for new files, for this command only.",
size.0, size.0
))
}
err => internal_error_with_message("Failed to snapshot the working copy", err),
}
} }
} }
@ -395,6 +411,12 @@ impl From<ResetError> for CommandError {
} }
} }
impl From<TransactionCommitError> for CommandError {
fn from(err: TransactionCommitError) -> Self {
internal_error(err)
}
}
impl From<DiffEditError> for CommandError { impl From<DiffEditError> for CommandError {
fn from(err: DiffEditError) -> Self { fn from(err: DiffEditError) -> Self {
user_error_with_message("Failed to edit diff", err) user_error_with_message("Failed to edit diff", err)
@ -415,7 +437,27 @@ impl From<DiffRenderError> for CommandError {
impl From<ConflictResolveError> for CommandError { impl From<ConflictResolveError> for CommandError {
fn from(err: ConflictResolveError) -> Self { fn from(err: ConflictResolveError) -> Self {
user_error_with_message("Failed to resolve conflicts", err) match err {
ConflictResolveError::Backend(err) => err.into(),
ConflictResolveError::Io(err) => err.into(),
_ => {
let hint = match &err {
ConflictResolveError::ExecutableConflict { .. } => {
Some("Use `jj file chmod` to update the executable bit.".to_owned())
}
_ => None,
};
let mut cmd_err = user_error_with_message("Failed to resolve conflicts", err);
cmd_err.extend_hints(hint);
cmd_err
}
}
}
}
impl From<MergeToolPartialResolutionError> for CommandError {
fn from(err: MergeToolPartialResolutionError) -> Self {
user_error(err)
} }
} }
@ -438,46 +480,157 @@ impl From<MergeToolConfigError> for CommandError {
} }
} }
impl From<git2::Error> for CommandError { impl From<TextEditError> for CommandError {
fn from(err: git2::Error) -> Self { fn from(err: TextEditError) -> Self {
user_error_with_message("Git operation failed", err) user_error(err)
} }
} }
impl From<GitImportError> for CommandError { impl From<TempTextEditError> for CommandError {
fn from(err: GitImportError) -> Self { fn from(err: TempTextEditError) -> Self {
let hint = match &err { let hint = err.path.as_ref().map(|path| {
GitImportError::MissingHeadTarget { .. } let name = err.name.as_deref().unwrap_or("file");
| GitImportError::MissingRefAncestor { .. } => Some( format!("Edited {name} is left in {path}", path = path.display())
"\ });
Is this Git repository a partial clone (cloned with the --filter argument)? let mut cmd_err = user_error(err);
jj currently does not support partial clones. To use jj with this repository, try re-cloning with \
the full repository contents."
.to_string(),
),
GitImportError::RemoteReservedForLocalGitRepo => {
Some("Run `jj git remote rename` to give different name.".to_string())
}
GitImportError::InternalBackend(_) => None,
GitImportError::InternalGitError(_) => None,
GitImportError::UnexpectedBackend => None,
};
let mut cmd_err =
user_error_with_message("Failed to import refs from underlying Git repo", err);
cmd_err.extend_hints(hint); cmd_err.extend_hints(hint);
cmd_err cmd_err
} }
} }
impl From<GitExportError> for CommandError { impl From<TrailerParseError> for CommandError {
fn from(err: GitExportError) -> Self { fn from(err: TrailerParseError) -> Self {
internal_error_with_message("Failed to export refs to underlying Git repo", err) user_error(err)
} }
} }
impl From<GitRemoteManagementError> for CommandError { #[cfg(feature = "git")]
fn from(err: GitRemoteManagementError) -> Self { mod git {
user_error(err) use jj_lib::git::GitExportError;
use jj_lib::git::GitFetchError;
use jj_lib::git::GitFetchPrepareError;
use jj_lib::git::GitImportError;
use jj_lib::git::GitPushError;
use jj_lib::git::GitRemoteManagementError;
use jj_lib::git::GitResetHeadError;
use jj_lib::git::UnexpectedGitBackendError;
use super::*;
impl From<GitImportError> for CommandError {
fn from(err: GitImportError) -> Self {
let hint = match &err {
GitImportError::MissingHeadTarget { .. }
| GitImportError::MissingRefAncestor { .. } => Some(
"\
Is this Git repository a partial clone (cloned with the --filter argument)?
jj currently does not support partial clones. To use jj with this repository, try re-cloning with \
the full repository contents."
.to_string(),
),
GitImportError::Backend(_) => None,
GitImportError::Git(_) => None,
GitImportError::UnexpectedBackend(_) => None,
};
let mut cmd_err =
user_error_with_message("Failed to import refs from underlying Git repo", err);
cmd_err.extend_hints(hint);
cmd_err
}
}
impl From<GitExportError> for CommandError {
fn from(err: GitExportError) -> Self {
user_error_with_message("Failed to export refs to underlying Git repo", err)
}
}
impl From<GitFetchError> for CommandError {
fn from(err: GitFetchError) -> Self {
if let GitFetchError::InvalidBranchPattern(pattern) = &err {
if pattern.as_exact().is_some_and(|s| s.contains('*')) {
return user_error_with_hint(
"Branch names may not include `*`.",
"Prefix the pattern with `glob:` to expand `*` as a glob",
);
}
}
match err {
GitFetchError::NoSuchRemote(_) => user_error(err),
GitFetchError::RemoteName(_) => user_error_with_hint(
err,
"Run `jj git remote rename` to give a different name.",
),
GitFetchError::InvalidBranchPattern(_) => user_error(err),
#[cfg(feature = "git2")]
GitFetchError::Git2(err) => map_git2_error(err),
GitFetchError::Subprocess(_) => user_error(err),
}
}
}
impl From<GitFetchPrepareError> for CommandError {
fn from(err: GitFetchPrepareError) -> Self {
match err {
#[cfg(feature = "git2")]
GitFetchPrepareError::Git2(err) => map_git2_error(err),
GitFetchPrepareError::UnexpectedBackend(_) => user_error(err),
}
}
}
impl From<GitPushError> for CommandError {
fn from(err: GitPushError) -> Self {
match err {
GitPushError::NoSuchRemote(_) => user_error(err),
GitPushError::RemoteName(_) => user_error_with_hint(
err,
"Run `jj git remote rename` to give a different name.",
),
#[cfg(feature = "git2")]
GitPushError::Git2(err) => map_git2_error(err),
GitPushError::Subprocess(_) => user_error(err),
GitPushError::UnexpectedBackend(_) => user_error(err),
}
}
}
impl From<GitRemoteManagementError> for CommandError {
fn from(err: GitRemoteManagementError) -> Self {
user_error(err)
}
}
impl From<GitResetHeadError> for CommandError {
fn from(err: GitResetHeadError) -> Self {
user_error_with_message("Failed to reset Git HEAD state", err)
}
}
impl From<UnexpectedGitBackendError> for CommandError {
fn from(err: UnexpectedGitBackendError) -> Self {
user_error(err)
}
}
#[cfg(feature = "git2")]
fn map_git2_error(err: git2::Error) -> CommandError {
if err.class() == git2::ErrorClass::Ssh {
let hint = if err.code() == git2::ErrorCode::Certificate
&& std::env::var_os("HOME").is_none()
{
"The HOME environment variable is not set, and might be required for Git to \
successfully load certificates. Try setting it to the path of a directory that \
contains a `.ssh` directory."
} else {
"Jujutsu uses libssh2, which doesn't respect ~/.ssh/config. Does `ssh -F \
/dev/null` to the host work?"
};
user_error_with_hint(err, hint)
} else {
user_error(err)
}
} }
} }
@ -497,6 +650,18 @@ impl From<FilesetParseError> for CommandError {
} }
} }
impl From<RecoverWorkspaceError> for CommandError {
fn from(err: RecoverWorkspaceError) -> Self {
match err {
RecoverWorkspaceError::Backend(err) => err.into(),
RecoverWorkspaceError::Reset(err) => err.into(),
RecoverWorkspaceError::RewriteRootCommit(err) => err.into(),
RecoverWorkspaceError::TransactionCommit(err) => err.into(),
err @ RecoverWorkspaceError::WorkspaceMissingWorkingCopy(_) => user_error(err),
}
}
}
impl From<RevsetParseError> for CommandError { impl From<RevsetParseError> for CommandError {
fn from(err: RevsetParseError) -> Self { fn from(err: RevsetParseError) -> Self {
let hint = revset_parse_error_hint(&err); let hint = revset_parse_error_hint(&err);
@ -550,12 +715,6 @@ impl From<clap::Error> for CommandError {
} }
} }
impl From<GitConfigParseError> for CommandError {
fn from(err: GitConfigParseError) -> Self {
internal_error_with_message("Failed to parse Git config", err)
}
}
impl From<WorkingCopyStateError> for CommandError { impl From<WorkingCopyStateError> for CommandError {
fn from(err: WorkingCopyStateError) -> Self { fn from(err: WorkingCopyStateError) -> Self {
internal_error_with_message("Failed to access working copy state", err) internal_error_with_message("Failed to access working copy state", err)
@ -574,9 +733,36 @@ impl From<ParseBulkEditMessageError> for CommandError {
} }
} }
impl From<AbsorbError> for CommandError {
fn from(err: AbsorbError) -> Self {
match err {
AbsorbError::Backend(err) => err.into(),
AbsorbError::RevsetEvaluation(err) => err.into(),
}
}
}
impl From<FixError> for CommandError {
fn from(err: FixError) -> Self {
match err {
FixError::Backend(err) => err.into(),
FixError::RevsetEvaluation(err) => err.into(),
FixError::IO(err) => err.into(),
FixError::FixContent(err) => internal_error_with_message(
"An error occurred while attempting to fix file content",
err,
),
}
}
}
fn find_source_parse_error_hint(err: &dyn error::Error) -> Option<String> { fn find_source_parse_error_hint(err: &dyn error::Error) -> Option<String> {
let source = err.source()?; let source = err.source()?;
if let Some(source) = source.downcast_ref() { if let Some(source) = source.downcast_ref() {
bookmark_name_parse_error_hint(source)
} else if let Some(source) = source.downcast_ref() {
config_get_error_hint(source)
} else if let Some(source) = source.downcast_ref() {
file_pattern_parse_error_hint(source) file_pattern_parse_error_hint(source)
} else if let Some(source) = source.downcast_ref() { } else if let Some(source) = source.downcast_ref() {
fileset_parse_error_hint(source) fileset_parse_error_hint(source)
@ -595,9 +781,35 @@ fn find_source_parse_error_hint(err: &dyn error::Error) -> Option<String> {
} }
} }
fn bookmark_name_parse_error_hint(err: &BookmarkNameParseError) -> Option<String> {
use revset::ExpressionKind;
match revset::parse_program(&err.input).map(|node| node.kind) {
Ok(ExpressionKind::RemoteSymbol(symbol)) => Some(format!(
"Looks like remote bookmark. Run `jj bookmark track {symbol}` to track it."
)),
_ => Some(
"See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how \
to quote symbols."
.into(),
),
}
}
fn config_get_error_hint(err: &ConfigGetError) -> Option<String> {
match &err {
ConfigGetError::NotFound { .. } => None,
ConfigGetError::Type { source_path, .. } => source_path
.as_ref()
.map(|path| format!("Check the config file: {}", path.display())),
}
}
fn file_pattern_parse_error_hint(err: &FilePatternParseError) -> Option<String> { fn file_pattern_parse_error_hint(err: &FilePatternParseError) -> Option<String> {
match err { match err {
FilePatternParseError::InvalidKind(_) => None, FilePatternParseError::InvalidKind(_) => Some(String::from(
"See https://jj-vcs.github.io/jj/latest/filesets/#file-patterns or `jj help -k \
filesets` for valid prefixes.",
)),
// Suggest root:"<path>" if input can be parsed as repo-relative path // Suggest root:"<path>" if input can be parsed as repo-relative path
FilePatternParseError::UiPath(UiPathParseError::Fs(e)) => { FilePatternParseError::UiPath(UiPathParseError::Fs(e)) => {
RepoPathBuf::from_relative_path(&e.input).ok().map(|path| { RepoPathBuf::from_relative_path(&e.input).ok().map(|path| {
@ -612,8 +824,8 @@ fn file_pattern_parse_error_hint(err: &FilePatternParseError) -> Option<String>
fn fileset_parse_error_hint(err: &FilesetParseError) -> Option<String> { fn fileset_parse_error_hint(err: &FilesetParseError) -> Option<String> {
match err.kind() { match err.kind() {
FilesetParseErrorKind::SyntaxError => Some(String::from( FilesetParseErrorKind::SyntaxError => Some(String::from(
"See https://martinvonz.github.io/jj/latest/filesets/ for filesets syntax, or for how \ "See https://jj-vcs.github.io/jj/latest/filesets/ or use `jj help -k filesets` for \
to match file paths.", filesets syntax and how to match file paths.",
)), )),
FilesetParseErrorKind::NoSuchFunction { FilesetParseErrorKind::NoSuchFunction {
name: _, name: _,
@ -645,6 +857,11 @@ fn revset_parse_error_hint(err: &RevsetParseError) -> Option<String> {
// Only for the bottom error, which is usually the root cause // Only for the bottom error, which is usually the root cause
let bottom_err = iter::successors(Some(err), |e| e.origin()).last().unwrap(); let bottom_err = iter::successors(Some(err), |e| e.origin()).last().unwrap();
match bottom_err.kind() { match bottom_err.kind() {
RevsetParseErrorKind::SyntaxError => Some(
"See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for \
revsets syntax and how to quote symbols."
.into(),
),
RevsetParseErrorKind::NotPrefixOperator { RevsetParseErrorKind::NotPrefixOperator {
op: _, op: _,
similar_op, similar_op,
@ -659,7 +876,7 @@ fn revset_parse_error_hint(err: &RevsetParseError) -> Option<String> {
op: _, op: _,
similar_op, similar_op,
description, description,
} => Some(format!("Did you mean '{similar_op}' for {description}?")), } => Some(format!("Did you mean `{similar_op}` for {description}?")),
RevsetParseErrorKind::NoSuchFunction { RevsetParseErrorKind::NoSuchFunction {
name: _, name: _,
candidates, candidates,
@ -680,16 +897,18 @@ fn revset_resolution_error_hint(err: &RevsetResolutionError) -> Option<String> {
| RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. }
| RevsetResolutionError::AmbiguousCommitIdPrefix(_) | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
| RevsetResolutionError::AmbiguousChangeIdPrefix(_) | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
| RevsetResolutionError::StoreError(_) | RevsetResolutionError::Backend(_)
| RevsetResolutionError::Other(_) => None, | RevsetResolutionError::Other(_) => None,
} }
} }
fn string_pattern_parse_error_hint(err: &StringPatternParseError) -> Option<String> { fn string_pattern_parse_error_hint(err: &StringPatternParseError) -> Option<String> {
match err { match err {
StringPatternParseError::InvalidKind(_) => { StringPatternParseError::InvalidKind(_) => Some(
Some("Try prefixing with one of `exact:`, `glob:`, `regex:`, or `substring:`".into()) "Try prefixing with one of `exact:`, `glob:`, `regex:`, `substring:`, or one of these \
} with `-i` suffix added (e.g. `glob-i:`) for case-insensitive matching"
.into(),
),
StringPatternParseError::GlobPattern(_) | StringPatternParseError::Regex(_) => None, StringPatternParseError::GlobPattern(_) | StringPatternParseError::Regex(_) => None,
} }
} }
@ -734,7 +953,8 @@ fn try_handle_command_result(
print_error(ui, "Config error: ", err, hints)?; print_error(ui, "Config error: ", err, hints)?;
writeln!( writeln!(
ui.stderr_formatter().labeled("hint"), ui.stderr_formatter().labeled("hint"),
"For help, see https://martinvonz.github.io/jj/latest/config/." "For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k \
config`."
)?; )?;
Ok(ExitCode::from(1)) Ok(ExitCode::from(1))
} }
@ -769,7 +989,8 @@ fn print_error(
Ok(()) Ok(())
} }
fn print_error_sources(ui: &Ui, source: Option<&dyn error::Error>) -> io::Result<()> { /// Prints error sources one by one from the given `source` inclusive.
pub fn print_error_sources(ui: &Ui, source: Option<&dyn error::Error>) -> io::Result<()> {
let Some(err) = source else { let Some(err) = source else {
return Ok(()); return Ok(());
}; };
@ -780,7 +1001,7 @@ fn print_error_sources(ui: &Ui, source: Option<&dyn error::Error>) -> io::Result
writeln!(formatter, "{err}")?; writeln!(formatter, "{err}")?;
} else { } else {
writeln!(formatter.labeled("heading"), "Caused by:")?; writeln!(formatter.labeled("heading"), "Caused by:")?;
for (i, err) in iter::successors(Some(err), |err| err.source()).enumerate() { for (i, err) in iter::successors(Some(err), |&err| err.source()).enumerate() {
write!(formatter.labeled("heading"), "{}: ", i + 1)?; write!(formatter.labeled("heading"), "{}: ", i + 1)?;
writeln!(formatter, "{err}")?; writeln!(formatter, "{err}")?;
} }
@ -834,6 +1055,8 @@ fn handle_clap_error(ui: &mut Ui, err: &clap::Error, hints: &[ErrorHint]) -> io:
_ => {} _ => {}
} }
write!(ui.stderr(), "{clap_str}")?; write!(ui.stderr(), "{clap_str}")?;
// Skip the first source error, which should be printed inline.
print_error_sources(ui, err.source().and_then(|err| err.source()))?;
print_error_hints(ui, hints)?; print_error_hints(ui, hints)?;
Ok(ExitCode::from(2)) Ok(ExitCode::from(2))
} }
@ -846,7 +1069,7 @@ pub fn print_parse_diagnostics<T: error::Error>(
) -> io::Result<()> { ) -> io::Result<()> {
for diag in diagnostics { for diag in diagnostics {
writeln!(ui.warning_default(), "{context_message}")?; writeln!(ui.warning_default(), "{context_message}")?;
for err in iter::successors(Some(diag as &dyn error::Error), |err| err.source()) { for err in iter::successors(Some(diag as &dyn error::Error), |&err| err.source()) {
writeln!(ui.stderr(), "{err}")?; writeln!(ui.stderr(), "{err}")?;
} }
// If we add support for multiple error diagnostics, we might have to do // If we add support for multiple error diagnostics, we might have to do

View File

@ -12,16 +12,26 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::collections::HashMap;
use std::collections::HashSet;
use std::io::Write as _;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::commit::CommitIteratorExt; use jj_lib::backend::CommitId;
use jj_lib::object_id::ObjectId; use jj_lib::commit::CommitIteratorExt as _;
use jj_lib::object_id::ObjectId as _;
use jj_lib::refs::diff_named_ref_targets;
use jj_lib::repo::Repo as _;
use jj_lib::rewrite::RewriteRefsOptions;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::has_tracked_remote_bookmarks;
use crate::cli_util::print_updated_commits;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Abandon a revision /// Abandon a revision
@ -34,15 +44,27 @@ use crate::ui::Ui;
/// commit. This is true in general; it is not specific to this command. /// commit. This is true in general; it is not specific to this command.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct AbandonArgs { pub(crate) struct AbandonArgs {
/// The revision(s) to abandon /// The revision(s) to abandon (default: @)
#[arg(default_value = "@")] #[arg(
revisions: Vec<RevisionArg>, value_name = "REVSETS",
/// Do not print every abandoned commit on a separate line add = ArgValueCompleter::new(complete::revset_expression_mutable),
#[arg(long, short)] )]
revisions_pos: Vec<RevisionArg>,
#[arg(
short = 'r',
hide = true,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revisions_opt: Vec<RevisionArg>,
// TODO: Remove in jj 0.34+
#[arg(long, short, hide = true)]
summary: bool, summary: bool,
/// Ignored (but lets you pass `-r` for consistency with other commands) /// Do not delete bookmarks pointing to the revisions to abandon
#[arg(short = 'r', hide = true, action = clap::ArgAction::Count)] ///
unused_revision: u8, /// Bookmarks will be moved to the parent revisions instead.
#[arg(long)]
retain_bookmarks: bool,
/// Do not modify the content of the children of the abandoned commits /// Do not modify the content of the children of the abandoned commits
#[arg(long)] #[arg(long)]
restore_descendants: bool, restore_descendants: bool,
@ -54,54 +76,84 @@ pub(crate) fn cmd_abandon(
command: &CommandHelper, command: &CommandHelper,
args: &AbandonArgs, args: &AbandonArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
if args.summary {
writeln!(ui.warning_default(), "--summary is no longer supported.")?;
}
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let to_abandon: Vec<_> = workspace_command let to_abandon: Vec<_> = if !args.revisions_pos.is_empty() || !args.revisions_opt.is_empty() {
.parse_union_revsets(ui, &args.revisions)? workspace_command
.evaluate_to_commits()? .parse_union_revsets(ui, &[&*args.revisions_pos, &*args.revisions_opt].concat())?
.try_collect()?; } else {
workspace_command.parse_revset(ui, &RevisionArg::AT)?
}
.evaluate_to_commits()?
.try_collect()?;
if to_abandon.is_empty() { if to_abandon.is_empty() {
writeln!(ui.status(), "No revisions to abandon.")?; writeln!(ui.status(), "No revisions to abandon.")?;
return Ok(()); return Ok(());
} }
workspace_command.check_rewritable(to_abandon.iter().ids())?; let to_abandon_set: HashSet<&CommitId> = to_abandon.iter().ids().collect();
workspace_command.check_rewritable(to_abandon_set.iter().copied())?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
for commit in &to_abandon { let options = RewriteRefsOptions {
tx.repo_mut().record_abandoned_commit(commit.id().clone()); delete_abandoned_bookmarks: !args.retain_bookmarks,
}
let (num_rebased, extra_msg) = if args.restore_descendants {
(
tx.repo_mut().reparent_descendants(command.settings())?,
" (while preserving their content)",
)
} else {
(tx.repo_mut().rebase_descendants(command.settings())?, "")
}; };
let mut num_rebased = 0;
tx.repo_mut().transform_descendants_with_options(
to_abandon_set.iter().copied().cloned().collect(),
&HashMap::new(),
&options,
|rewriter| {
if to_abandon_set.contains(rewriter.old_commit().id()) {
rewriter.abandon();
} else if args.restore_descendants {
rewriter.reparent().write()?;
num_rebased += 1;
} else {
rewriter.rebase()?.write()?;
num_rebased += 1;
}
Ok(())
},
)?;
let deleted_bookmarks = diff_named_ref_targets(
tx.base_repo().view().local_bookmarks(),
tx.repo().view().local_bookmarks(),
)
.filter(|(_, (_old, new))| new.is_absent())
.map(|(name, _)| name.to_owned())
.collect_vec();
if let Some(mut formatter) = ui.status_formatter() { if let Some(mut formatter) = ui.status_formatter() {
if to_abandon.len() == 1 { writeln!(formatter, "Abandoned {} commits:", to_abandon.len())?;
write!(formatter, "Abandoned commit ")?; print_updated_commits(
tx.base_workspace_helper() formatter.as_mut(),
.write_commit_summary(formatter.as_mut(), &to_abandon[0])?; &tx.base_workspace_helper().commit_summary_template(),
writeln!(ui.status())?; &to_abandon,
} else if !args.summary { )?;
let template = tx.base_workspace_helper().commit_summary_template(); if !deleted_bookmarks.is_empty() {
writeln!(formatter, "Abandoned the following commits:")?;
for commit in &to_abandon {
write!(formatter, " ")?;
template.format(commit, formatter.as_mut())?;
writeln!(formatter)?;
}
} else {
writeln!(formatter, "Abandoned {} commits.", &to_abandon.len())?;
}
if num_rebased > 0 {
writeln!( writeln!(
formatter, formatter,
"Rebased {num_rebased} descendant commits{extra_msg} onto parents of abandoned \ "Deleted bookmarks: {}",
commits", deleted_bookmarks.iter().map(|n| n.as_symbol()).join(", ")
)?; )?;
} }
if num_rebased > 0 {
if args.restore_descendants {
writeln!(
formatter,
"Rebased {num_rebased} descendant commits (while preserving their content) \
onto parents of abandoned commits",
)?;
} else {
writeln!(
formatter,
"Rebased {num_rebased} descendant commits onto parents of abandoned commits",
)?;
}
}
} }
let transaction_description = if to_abandon.len() == 1 { let transaction_description = if to_abandon.len() == 1 {
format!("abandon commit {}", to_abandon[0].id().hex()) format!("abandon commit {}", to_abandon[0].id().hex())
@ -113,5 +165,20 @@ pub(crate) fn cmd_abandon(
) )
}; };
tx.finish(ui, transaction_description)?; tx.finish(ui, transaction_description)?;
#[cfg(feature = "git")]
if jj_lib::git::get_git_backend(workspace_command.repo().store()).is_ok() {
let view = workspace_command.repo().view();
if deleted_bookmarks
.iter()
.any(|name| has_tracked_remote_bookmarks(view, name))
{
writeln!(
ui.hint_default(),
"Deleted bookmarks can be pushed by name or all at once with `jj git push \
--deleted`."
)?;
}
}
Ok(()) Ok(())
} }

142
cli/src/commands/absorb.rs Normal file
View File

@ -0,0 +1,142 @@
// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use clap_complete::ArgValueCompleter;
use jj_lib::absorb::absorb_hunks;
use jj_lib::absorb::split_hunks_to_trees;
use jj_lib::absorb::AbsorbSource;
use jj_lib::matchers::EverythingMatcher;
use pollster::FutureExt as _;
use tracing::instrument;
use crate::cli_util::print_updated_commits;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::command_error::CommandError;
use crate::complete;
use crate::diff_util::DiffFormat;
use crate::ui::Ui;
/// Move changes from a revision into the stack of mutable revisions
///
/// This command splits changes in the source revision and moves each change to
/// the closest mutable ancestor where the corresponding lines were modified
/// last. If the destination revision cannot be determined unambiguously, the
/// change will be left in the source revision.
///
/// The source revision will be abandoned if all changes are absorbed into the
/// destination revisions, and if the source revision has no description.
///
/// The modification made by `jj absorb` can be reviewed by `jj op show -p`.
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct AbsorbArgs {
/// Source revision to absorb from
#[arg(
long, short,
default_value = "@",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
from: RevisionArg,
/// Destination revisions to absorb into
///
/// Only ancestors of the source revision will be considered.
#[arg(
long, short = 't', visible_alias = "to",
default_value = "mutable()",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
into: Vec<RevisionArg>,
/// Move only changes to these paths (instead of all paths)
#[arg(value_name = "FILESETS", value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>,
}
#[instrument(skip_all)]
pub(crate) fn cmd_absorb(
ui: &mut Ui,
command: &CommandHelper,
args: &AbsorbArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let source_commit = workspace_command.resolve_single_rev(ui, &args.from)?;
let destinations = workspace_command
.parse_union_revsets(ui, &args.into)?
.resolve()?;
let matcher = workspace_command
.parse_file_patterns(ui, &args.paths)?
.to_matcher();
let repo = workspace_command.repo().as_ref();
let source = AbsorbSource::from_commit(repo, source_commit)?;
let selected_trees = split_hunks_to_trees(repo, &source, &destinations, &matcher).block_on()?;
let path_converter = workspace_command.path_converter();
for (path, reason) in selected_trees.skipped_paths {
let ui_path = path_converter.format_file_path(&path);
writeln!(ui.warning_default(), "Skipping {ui_path}: {reason}")?;
}
workspace_command.check_rewritable(selected_trees.target_commits.keys())?;
let mut tx = workspace_command.start_transaction();
let stats = absorb_hunks(tx.repo_mut(), &source, selected_trees.target_commits)?;
if let Some(mut formatter) = ui.status_formatter() {
if !stats.rewritten_destinations.is_empty() {
writeln!(
formatter,
"Absorbed changes into {} revisions:",
stats.rewritten_destinations.len()
)?;
print_updated_commits(
formatter.as_mut(),
&tx.commit_summary_template(),
stats.rewritten_destinations.iter().rev(),
)?;
}
if stats.num_rebased > 0 {
writeln!(
formatter,
"Rebased {} descendant commits.",
stats.num_rebased
)?;
}
}
tx.finish(
ui,
format!(
"absorb changes into {} commits",
stats.rewritten_destinations.len()
),
)?;
if let Some(mut formatter) = ui.status_formatter() {
if let Some(commit) = &stats.rewritten_source {
let repo = workspace_command.repo().as_ref();
if !commit.is_empty(repo)? {
writeln!(formatter, "Remaining changes:")?;
let diff_renderer = workspace_command.diff_renderer(vec![DiffFormat::Summary]);
let matcher = &EverythingMatcher; // also print excluded paths
let width = ui.term_width();
diff_renderer.show_patch(ui, formatter.as_mut(), commit, matcher, width)?;
}
}
}
Ok(())
}

View File

@ -12,26 +12,44 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use bstr::ByteVec as _;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use jj_lib::rewrite::merge_commit_trees; use jj_lib::rewrite::merge_commit_trees;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::formatter::PlainTextFormatter;
use crate::ui::Ui; use crate::ui::Ui;
/// Apply the reverse of a revision on top of another revision /// Apply the reverse of given revisions on top of another revision
///
/// The description of the new revisions can be customized with the
/// `templates.backout_description` config variable.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(hide = true)]
pub(crate) struct BackoutArgs { pub(crate) struct BackoutArgs {
/// The revision(s) to apply the reverse of /// The revision(s) to apply the reverse of
#[arg(long, short, default_value = "@")] #[arg(
long, short,
default_value = "@",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revisions: Vec<RevisionArg>, revisions: Vec<RevisionArg>,
/// The revision to apply the reverse changes on top of /// The revision to apply the reverse changes on top of
// TODO: It seems better to default this to `@-`. Maybe the working // TODO: It seems better to default this to `@-`. Maybe the working
// copy should be rebased on top? // copy should be rebased on top?
#[arg(long, short, default_value = "@")] #[arg(
long, short,
default_value = "@",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
destination: Vec<RevisionArg>, destination: Vec<RevisionArg>,
} }
@ -41,6 +59,15 @@ pub(crate) fn cmd_backout(
command: &CommandHelper, command: &CommandHelper,
args: &BackoutArgs, args: &BackoutArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
writeln!(
ui.warning_default(),
"`jj backout` is deprecated; use `jj revert` instead"
)?;
writeln!(
ui.warning_default(),
"`jj backout` will be removed in a future version, and this will be a hard error"
)?;
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let to_back_out: Vec<_> = workspace_command let to_back_out: Vec<_> = workspace_command
.parse_union_revsets(ui, &args.revisions)? .parse_union_revsets(ui, &args.revisions)?
@ -55,7 +82,6 @@ pub(crate) fn cmd_backout(
let destination = workspace_command.resolve_single_rev(ui, revision_str)?; let destination = workspace_command.resolve_single_rev(ui, revision_str)?;
parents.push(destination); parents.push(destination);
} }
let mut tx = workspace_command.start_transaction();
let transaction_description = if to_back_out.len() == 1 { let transaction_description = if to_back_out.len() == 1 {
format!("back out commit {}", to_back_out[0].id().hex()) format!("back out commit {}", to_back_out[0].id().hex())
} else { } else {
@ -65,25 +91,38 @@ pub(crate) fn cmd_backout(
to_back_out.len() - 1 to_back_out.len() - 1
) )
}; };
let commits_to_back_out_with_new_commit_descriptions = {
let template_text = command
.settings()
.get_string("templates.backout_description")?;
let template = workspace_command.parse_commit_template(ui, &template_text)?;
to_back_out
.into_iter()
.map(|commit| {
let mut output = Vec::new();
template
.format(&commit, &mut PlainTextFormatter::new(&mut output))
.expect("write() to vec backed formatter should never fail");
// Template output is usually UTF-8, but it can contain file content.
let commit_description = output.into_string_lossy();
(commit, commit_description)
})
.collect_vec()
};
let mut tx = workspace_command.start_transaction();
let mut new_base_tree = merge_commit_trees(tx.repo(), &parents)?; let mut new_base_tree = merge_commit_trees(tx.repo(), &parents)?;
for commit_to_back_out in to_back_out {
let commit_to_back_out_subject = commit_to_back_out for (commit_to_back_out, new_commit_description) in
.description() commits_to_back_out_with_new_commit_descriptions
.lines() {
.next()
.unwrap_or_default();
let new_commit_description = format!(
"Back out \"{}\"\n\nThis backs out commit {}.\n",
commit_to_back_out_subject,
&commit_to_back_out.id().hex()
);
let old_base_tree = commit_to_back_out.parent_tree(tx.repo())?; let old_base_tree = commit_to_back_out.parent_tree(tx.repo())?;
let old_tree = commit_to_back_out.tree()?; let old_tree = commit_to_back_out.tree()?;
let new_tree = new_base_tree.merge(&old_tree, &old_base_tree)?; let new_tree = new_base_tree.merge(&old_tree, &old_base_tree)?;
let new_parent_ids = parents.iter().map(|commit| commit.id().clone()).collect(); let new_parent_ids = parents.iter().map(|commit| commit.id().clone()).collect();
let new_commit = tx let new_commit = tx
.repo_mut() .repo_mut()
.new_commit(command.settings(), new_parent_ids, new_tree.id()) .new_commit(new_parent_ids, new_tree.id())
.set_description(new_commit_description) .set_description(new_commit_description)
.write()?; .write()?;
parents = vec![new_commit]; parents = vec![new_commit];

View File

@ -19,10 +19,9 @@ use criterion::measurement::Measurement;
use criterion::BatchSize; use criterion::BatchSize;
use criterion::BenchmarkGroup; use criterion::BenchmarkGroup;
use criterion::BenchmarkId; use criterion::BenchmarkId;
use jj_lib::revset;
use jj_lib::revset::DefaultSymbolResolver; use jj_lib::revset::DefaultSymbolResolver;
use jj_lib::revset::RevsetExpression;
use jj_lib::revset::SymbolResolverExtension; use jj_lib::revset::SymbolResolverExtension;
use jj_lib::revset::UserRevsetExpression;
use super::new_criterion; use super::new_criterion;
use super::CriterionArgs; use super::CriterionArgs;
@ -80,14 +79,13 @@ fn bench_revset<M: Measurement>(
revset: &RevisionArg, revset: &RevisionArg,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
writeln!(ui.status(), "----------Testing revset: {revset}----------")?; writeln!(ui.status(), "----------Testing revset: {revset}----------")?;
let expression = revset::optimize( let expression = workspace_command
workspace_command .parse_revset(ui, revset)?
.parse_revset(ui, revset)? .expression()
.expression() .clone();
.clone(),
);
// Time both evaluation and iteration. // Time both evaluation and iteration.
let routine = |workspace_command: &WorkspaceCommandHelper, expression: Rc<RevsetExpression>| { let routine = |workspace_command: &WorkspaceCommandHelper,
expression: Rc<UserRevsetExpression>| {
// Evaluate the expression without parsing/evaluating short-prefixes. // Evaluate the expression without parsing/evaluating short-prefixes.
let repo = workspace_command.repo().as_ref(); let repo = workspace_command.repo().as_ref();
let symbol_resolver = let symbol_resolver =

View File

@ -12,30 +12,42 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap::builder::NonEmptyStringValueParser; use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::object_id::ObjectId as _; use jj_lib::object_id::ObjectId as _;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
use jj_lib::ref_name::RefNameBuf;
use super::has_tracked_remote_bookmarks; use crate::cli_util::has_tracked_remote_bookmarks;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error_with_hint; use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::revset_util;
use crate::ui::Ui; use crate::ui::Ui;
/// Create a new bookmark /// Create a new bookmark
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkCreateArgs { pub struct BookmarkCreateArgs {
// TODO(#5374): Make required in jj 0.32+
/// The bookmark's target revision /// The bookmark's target revision
// //
// The `--to` alias exists for making it easier for the user to switch // The `--to` alias exists for making it easier for the user to switch
// between `bookmark create`, `bookmark move`, and `bookmark set`. // between `bookmark create`, `bookmark move`, and `bookmark set`. Currently target revision
#[arg(long, short, visible_alias = "to")] // defaults to the working copy if not specified, but in the near future it will be required to
// explicitly specify it.
#[arg(
long, short,
visible_alias = "to",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revision: Option<RevisionArg>, revision: Option<RevisionArg>,
/// The bookmarks to create /// The bookmarks to create
#[arg(required = true, value_parser = NonEmptyStringValueParser::new())] #[arg(required = true, value_parser = revset_util::parse_bookmark_name)]
names: Vec<String>, names: Vec<RefNameBuf>,
} }
pub fn cmd_bookmark_create( pub fn cmd_bookmark_create(
@ -44,6 +56,13 @@ pub fn cmd_bookmark_create(
args: &BookmarkCreateArgs, args: &BookmarkCreateArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
if args.revision.is_none() {
writeln!(
ui.warning_default(),
"Target revision was not specified, defaulting to the working copy (-r@). In the near \
future it will be required to explicitly specify target revision."
)?;
}
let target_commit = workspace_command let target_commit = workspace_command
.resolve_single_rev(ui, args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; .resolve_single_rev(ui, args.revision.as_ref().unwrap_or(&RevisionArg::AT))?;
let view = workspace_command.repo().view(); let view = workspace_command.repo().view();
@ -51,27 +70,29 @@ pub fn cmd_bookmark_create(
for name in bookmark_names { for name in bookmark_names {
if view.get_local_bookmark(name).is_present() { if view.get_local_bookmark(name).is_present() {
return Err(user_error_with_hint( return Err(user_error_with_hint(
format!("Bookmark already exists: {name}"), format!("Bookmark already exists: {name}", name = name.as_symbol()),
"Use `jj bookmark set` to update it.", "Use `jj bookmark set` to update it.",
)); ));
} }
if has_tracked_remote_bookmarks(view, name) { if has_tracked_remote_bookmarks(view, name) {
return Err(user_error_with_hint( return Err(user_error_with_hint(
format!("Tracked remote bookmarks exist for deleted bookmark: {name}"), format!(
"Tracked remote bookmarks exist for deleted bookmark: {name}",
name = name.as_symbol()
),
format!( format!(
"Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark \ "Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark \
untrack 'glob:{name}@*'` to disassociate them." untrack 'glob:{name}@*'` to disassociate them.",
name = name.as_symbol()
), ),
)); ));
} }
} }
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
for bookmark_name in bookmark_names { for name in bookmark_names {
tx.repo_mut().set_local_bookmark_target( tx.repo_mut()
bookmark_name, .set_local_bookmark_target(name, RefTarget::normal(target_commit.id().clone()));
RefTarget::normal(target_commit.id().clone()),
);
} }
if let Some(mut formatter) = ui.status_formatter() { if let Some(mut formatter) = ui.status_formatter() {
@ -83,15 +104,11 @@ pub fn cmd_bookmark_create(
tx.write_commit_summary(formatter.as_mut(), &target_commit)?; tx.write_commit_summary(formatter.as_mut(), &target_commit)?;
writeln!(formatter)?; writeln!(formatter)?;
} }
if bookmark_names.len() > 1 && args.revision.is_none() {
writeln!(ui.hint_default(), "Use -r to specify the target revision.")?;
}
tx.finish( tx.finish(
ui, ui,
format!( format!(
"create bookmark {names} pointing to commit {id}", "create bookmark {names} pointing to commit {id}",
names = bookmark_names.join(", "), names = bookmark_names.iter().map(|n| n.as_symbol()).join(", "),
id = target_commit.id().hex() id = target_commit.id().hex()
), ),
)?; )?;

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
use jj_lib::str_util::StringPattern; use jj_lib::str_util::StringPattern;
@ -19,18 +20,33 @@ use jj_lib::str_util::StringPattern;
use super::find_local_bookmarks; use super::find_local_bookmarks;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Delete an existing bookmark and propagate the deletion to remotes on the /// Delete an existing bookmark and propagate the deletion to remotes on the
/// next push /// next push
///
/// Revisions referred to by the deleted bookmarks are not abandoned. To delete
/// revisions as well as bookmarks, use `jj abandon`. For example, `jj abandon
/// main..<bookmark>` will abandon revisions belonging to the `<bookmark>`
/// branch (relative to the `main` branch.)
///
/// If you don't want the deletion of the local bookmark to propagate to any
/// tracked remote bookmarks, use `jj bookmark forget` instead.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkDeleteArgs { pub struct BookmarkDeleteArgs {
/// The bookmarks to delete /// The bookmarks to delete
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// select bookmarks by wildcard pattern. For details, see /// select bookmarks by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. ///
#[arg(required = true, value_parser = StringPattern::parse)] /// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg(
required = true,
value_parser = StringPattern::parse,
add = ArgValueCandidates::new(complete::local_bookmarks),
)]
names: Vec<StringPattern>, names: Vec<StringPattern>,
} }
@ -56,7 +72,10 @@ pub fn cmd_bookmark_delete(
ui, ui,
format!( format!(
"delete bookmark {}", "delete bookmark {}",
matched_bookmarks.iter().map(|(name, _)| name).join(", ") matched_bookmarks
.iter()
.map(|(name, _)| name.as_symbol())
.join(", ")
), ),
)?; )?;
Ok(()) Ok(())

View File

@ -12,31 +12,49 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::op_store::BookmarkTarget; use jj_lib::op_store::BookmarkTarget;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
use jj_lib::op_store::RemoteRef; use jj_lib::op_store::RemoteRef;
use jj_lib::ref_name::RefName;
use jj_lib::str_util::StringPattern; use jj_lib::str_util::StringPattern;
use jj_lib::view::View; use jj_lib::view::View;
use super::find_bookmarks_with; use super::find_bookmarks_with;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Forget everything about a bookmark, including its local and remote /// Forget a bookmark without marking it as a deletion to be pushed
/// targets
/// ///
/// A forgotten bookmark will not impact remotes on future pushes. It will be /// If a local bookmark is forgotten, any corresponding remote bookmarks will
/// recreated on future pulls if it still exists in the remote. /// become untracked to ensure that the forgotten bookmark will not impact
/// remotes on future pushes.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkForgetArgs { pub struct BookmarkForgetArgs {
/// When forgetting a local bookmark, also forget any corresponding remote
/// bookmarks
///
/// A forgotten remote bookmark will not impact remotes on future pushes. It
/// will be recreated on future fetches if it still exists on the remote. If
/// there is a corresponding Git-tracking remote bookmark, it will also be
/// forgotten.
#[arg(long)]
include_remotes: bool,
/// The bookmarks to forget /// The bookmarks to forget
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// select bookmarks by wildcard pattern. For details, see /// select bookmarks by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. ///
#[arg(required = true, value_parser = StringPattern::parse)] /// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg(
required = true,
value_parser = StringPattern::parse,
add = ArgValueCandidates::new(complete::bookmarks),
)]
names: Vec<StringPattern>, names: Vec<StringPattern>,
} }
@ -49,32 +67,50 @@ pub fn cmd_bookmark_forget(
let repo = workspace_command.repo().clone(); let repo = workspace_command.repo().clone();
let matched_bookmarks = find_forgettable_bookmarks(repo.view(), &args.names)?; let matched_bookmarks = find_forgettable_bookmarks(repo.view(), &args.names)?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let mut forgotten_remote: usize = 0;
for (name, bookmark_target) in &matched_bookmarks { for (name, bookmark_target) in &matched_bookmarks {
tx.repo_mut() tx.repo_mut()
.set_local_bookmark_target(name, RefTarget::absent()); .set_local_bookmark_target(name, RefTarget::absent());
for (remote_name, _) in &bookmark_target.remote_refs { for (remote, _) in &bookmark_target.remote_refs {
tx.repo_mut() let symbol = name.to_remote_symbol(remote);
.set_remote_bookmark(name, remote_name, RemoteRef::absent()); // If `--include-remotes` is specified, we forget the corresponding remote
// bookmarks instead of untracking them
if args.include_remotes {
tx.repo_mut()
.set_remote_bookmark(symbol, RemoteRef::absent());
forgotten_remote += 1;
continue;
}
// Git-tracking remote bookmarks cannot be untracked currently, so skip them
if jj_lib::git::is_special_git_remote(symbol.remote) {
continue;
}
tx.repo_mut().untrack_remote_bookmark(symbol);
} }
} }
writeln!(ui.status(), "Forgot {} bookmarks.", matched_bookmarks.len())?; writeln!(
tx.finish( ui.status(),
ui, "Forgot {} local bookmarks.",
format!( matched_bookmarks.len()
"forget bookmark {}",
matched_bookmarks.iter().map(|(name, _)| name).join(", ")
),
)?; )?;
if forgotten_remote != 0 {
writeln!(ui.status(), "Forgot {forgotten_remote} remote bookmarks.")?;
}
let forgotten_bookmarks = matched_bookmarks
.iter()
.map(|(name, _)| name.as_symbol())
.join(", ");
tx.finish(ui, format!("forget bookmark {forgotten_bookmarks}"))?;
Ok(()) Ok(())
} }
fn find_forgettable_bookmarks<'a>( fn find_forgettable_bookmarks<'a>(
view: &'a View, view: &'a View,
name_patterns: &[StringPattern], name_patterns: &[StringPattern],
) -> Result<Vec<(&'a str, BookmarkTarget<'a>)>, CommandError> { ) -> Result<Vec<(&'a RefName, BookmarkTarget<'a>)>, CommandError> {
find_bookmarks_with(name_patterns, |pattern| { find_bookmarks_with(name_patterns, |pattern| {
view.bookmarks() view.bookmarks()
.filter(|(name, _)| pattern.matches(name)) .filter(|(name, _)| pattern.matches(name.as_str()))
.map(Ok) .map(Ok)
}) })
} }

View File

@ -12,18 +12,29 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::cmp;
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::rc::Rc;
use std::sync::Arc;
use itertools::Itertools; use clap::ValueEnum;
use jj_lib::git; use clap_complete::ArgValueCandidates;
use itertools::Itertools as _;
use jj_lib::backend;
use jj_lib::backend::CommitId;
use jj_lib::config::ConfigValue;
use jj_lib::ref_name::RefName;
use jj_lib::repo::Repo as _;
use jj_lib::revset::RevsetExpression; use jj_lib::revset::RevsetExpression;
use jj_lib::str_util::StringPattern; use jj_lib::str_util::StringPattern;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitRef;
use crate::commit_templater::RefName; use crate::commit_templater::CommitTemplatePropertyKind;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// List bookmarks and their targets /// List bookmarks and their targets
@ -34,8 +45,10 @@ use crate::ui::Ui;
/// revisions are preceded by a "-" and new target revisions are preceded by a /// revisions are preceded by a "-" and new target revisions are preceded by a
/// "+". /// "+".
/// ///
/// For information about bookmarks, see /// See [`jj help -k bookmarks`] for more information.
/// https://martinvonz.github.io/jj/latest/bookmarks/. ///
/// [`jj help -k bookmarks`]:
/// https://jj-vcs.github.io/jj/latest/bookmarks
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkListArgs { pub struct BookmarkListArgs {
/// Show all tracking and non-tracking remote bookmarks including the ones /// Show all tracking and non-tracking remote bookmarks including the ones
@ -50,13 +63,16 @@ pub struct BookmarkListArgs {
/// bookmarks shown (can be repeated.) /// bookmarks shown (can be repeated.)
/// ///
/// By default, the specified remote name matches exactly. Use `glob:` /// By default, the specified remote name matches exactly. Use `glob:`
/// prefix to select remotes by wildcard pattern. For details, see /// prefix to select remotes by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. ///
/// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg( #[arg(
long = "remote", long = "remote",
value_name = "REMOTE", value_name = "REMOTE",
conflicts_with_all = ["all_remotes"], conflicts_with_all = ["all_remotes"],
value_parser = StringPattern::parse, value_parser = StringPattern::parse,
add = ArgValueCandidates::new(complete::git_remotes),
)] )]
remotes: Option<Vec<StringPattern>>, remotes: Option<Vec<StringPattern>>,
@ -72,25 +88,43 @@ pub struct BookmarkListArgs {
/// Show bookmarks whose local name matches /// Show bookmarks whose local name matches
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// select bookmarks by wildcard pattern. For details, see /// select bookmarks by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. ///
#[arg(value_parser = StringPattern::parse)] /// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg(value_parser = StringPattern::parse, add = ArgValueCandidates::new(complete::bookmarks))]
names: Option<Vec<StringPattern>>, names: Option<Vec<StringPattern>>,
/// Show bookmarks whose local targets are in the given revisions /// Show bookmarks whose local targets are in the given revisions
/// ///
/// Note that `-r deleted_bookmark` will not work since `deleted_bookmark` /// Note that `-r deleted_bookmark` will not work since `deleted_bookmark`
/// wouldn't have a local target. /// wouldn't have a local target.
#[arg(long, short)] #[arg(long, short, value_name = "REVSETS")]
revisions: Option<Vec<RevisionArg>>, revisions: Option<Vec<RevisionArg>>,
/// Render each bookmark using the given template /// Render each bookmark using the given template
/// ///
/// All 0-argument methods of the `RefName` type are available as keywords. /// All 0-argument methods of the [`CommitRef` type] are available as
/// keywords in the template expression. See [`jj help -k templates`]
/// for more information.
/// ///
/// For the syntax, see https://martinvonz.github.io/jj/latest/templates/ /// [`CommitRef` type]:
#[arg(long, short = 'T')] /// https://jj-vcs.github.io/jj/latest/templates/#commitref-type
///
/// [`jj help -k templates`]:
/// https://jj-vcs.github.io/jj/latest/templates/
#[arg(long, short = 'T', add = ArgValueCandidates::new(complete::template_aliases))]
template: Option<String>, template: Option<String>,
/// Sort bookmarks based on the given key (or multiple keys)
///
/// Suffix the key with `-` to sort in descending order of the value (e.g.
/// `--sort name-`). Note that when using multiple keys, the first key is
/// the most significant.
///
/// This defaults to the `ui.bookmark-list-sort-keys` setting.
#[arg(long, value_name = "SORT_KEY", value_enum, value_delimiter = ',')]
sort: Vec<SortKey>,
} }
pub fn cmd_bookmark_list( pub fn cmd_bookmark_list(
@ -104,11 +138,15 @@ pub fn cmd_bookmark_list(
// Like cmd_git_push(), names and revisions are OR-ed. // Like cmd_git_push(), names and revisions are OR-ed.
let bookmark_names_to_list = if args.names.is_some() || args.revisions.is_some() { let bookmark_names_to_list = if args.names.is_some() || args.revisions.is_some() {
let mut bookmark_names: HashSet<&str> = HashSet::new(); let mut bookmark_names: HashSet<&RefName> = HashSet::new();
if let Some(patterns) = &args.names { if let Some(patterns) = &args.names {
bookmark_names.extend( bookmark_names.extend(
view.bookmarks() view.bookmarks()
.filter(|&(name, _)| patterns.iter().any(|pattern| pattern.matches(name))) .filter(|(name, _)| {
patterns
.iter()
.any(|pattern| pattern.matches(name.as_str()))
})
.map(|(name, _)| name), .map(|(name, _)| name),
); );
} }
@ -137,91 +175,712 @@ pub fn cmd_bookmark_list(
let language = workspace_command.commit_template_language(); let language = workspace_command.commit_template_language();
let text = match &args.template { let text = match &args.template {
Some(value) => value.to_owned(), Some(value) => value.to_owned(),
None => command.settings().config().get("templates.bookmark_list")?, None => workspace_command
.settings()
.get("templates.bookmark_list")?,
}; };
workspace_command workspace_command
.parse_template(ui, &language, &text, CommitTemplateLanguage::wrap_ref_name)? .parse_template(
ui,
&language,
&text,
CommitTemplatePropertyKind::wrap_commit_ref,
)?
.labeled("bookmark_list") .labeled("bookmark_list")
}; };
ui.request_pager(); let mut bookmark_list_items: Vec<RefListItem> = Vec::new();
let mut formatter = ui.stdout_formatter();
let mut found_deleted_local_bookmark = false;
let mut found_deleted_tracking_local_bookmark = false;
let bookmarks_to_list = view.bookmarks().filter(|(name, target)| { let bookmarks_to_list = view.bookmarks().filter(|(name, target)| {
bookmark_names_to_list bookmark_names_to_list
.as_ref() .as_ref()
.map_or(true, |bookmark_names| bookmark_names.contains(name)) .is_none_or(|bookmark_names| bookmark_names.contains(name))
&& (!args.conflicted || target.local_target.has_conflict()) && (!args.conflicted || target.local_target.has_conflict())
}); });
for (name, bookmark_target) in bookmarks_to_list { for (name, bookmark_target) in bookmarks_to_list {
let local_target = bookmark_target.local_target; let local_target = bookmark_target.local_target;
let remote_refs = bookmark_target.remote_refs; let remote_refs = bookmark_target.remote_refs;
let (mut tracking_remote_refs, untracked_remote_refs) = remote_refs let (mut tracked_remote_refs, untracked_remote_refs) = remote_refs
.iter() .iter()
.copied() .copied()
.filter(|&(remote_name, _)| { .filter(|(remote_name, _)| {
args.remotes.as_ref().map_or(true, |patterns| { args.remotes.as_ref().is_none_or(|patterns| {
patterns.iter().any(|pattern| pattern.matches(remote_name)) patterns
.iter()
.any(|pattern| pattern.matches(remote_name.as_str()))
}) })
}) })
.partition::<Vec<_>, _>(|&(_, remote_ref)| remote_ref.is_tracking()); .partition::<Vec<_>, _>(|&(_, remote_ref)| remote_ref.is_tracked());
if args.tracked { if args.tracked {
tracking_remote_refs tracked_remote_refs.retain(|&(remote, _)| !jj_lib::git::is_special_git_remote(remote));
.retain(|&(remote, _)| remote != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO);
} else if !args.all_remotes && args.remotes.is_none() { } else if !args.all_remotes && args.remotes.is_none() {
tracking_remote_refs.retain(|&(_, remote_ref)| remote_ref.target != *local_target); tracked_remote_refs.retain(|&(_, remote_ref)| remote_ref.target != *local_target);
} }
let include_local_only = !args.tracked && args.remotes.is_none(); let include_local_only = !args.tracked && args.remotes.is_none();
if include_local_only && local_target.is_present() || !tracking_remote_refs.is_empty() { if include_local_only && local_target.is_present() || !tracked_remote_refs.is_empty() {
let ref_name = RefName::local( let primary = CommitRef::local(
name, name,
local_target.clone(), local_target.clone(),
remote_refs.iter().map(|&(_, remote_ref)| remote_ref), remote_refs.iter().map(|&(_, remote_ref)| remote_ref),
); );
template.format(&ref_name, formatter.as_mut())?; let tracked = tracked_remote_refs
}
for &(remote, remote_ref) in &tracking_remote_refs {
let ref_name = RefName::remote(name, remote, remote_ref.clone(), local_target);
template.format(&ref_name, formatter.as_mut())?;
}
if local_target.is_absent() && !tracking_remote_refs.is_empty() {
found_deleted_local_bookmark = true;
found_deleted_tracking_local_bookmark |= tracking_remote_refs
.iter() .iter()
.any(|&(remote, _)| remote != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO); .map(|&(remote, remote_ref)| {
CommitRef::remote(name, remote, remote_ref.clone(), local_target)
})
.collect();
bookmark_list_items.push(RefListItem { primary, tracked });
} }
if !args.tracked && (args.all_remotes || args.remotes.is_some()) { if !args.tracked && (args.all_remotes || args.remotes.is_some()) {
for &(remote, remote_ref) in &untracked_remote_refs { bookmark_list_items.extend(untracked_remote_refs.iter().map(
let ref_name = RefName::remote_only(name, remote, remote_ref.target.clone()); |&(remote, remote_ref)| RefListItem {
template.format(&ref_name, formatter.as_mut())?; primary: CommitRef::remote_only(name, remote, remote_ref.target.clone()),
} tracked: vec![],
},
));
} }
} }
let sort_keys = if args.sort.is_empty() {
workspace_command
.settings()
.get_value_with("ui.bookmark-list-sort-keys", parse_sort_keys)?
} else {
args.sort.clone()
};
let store = repo.store();
let mut commits: HashMap<CommitId, Arc<backend::Commit>> = HashMap::new();
if sort_keys.iter().any(|key| key.is_commit_dependant()) {
commits = bookmark_list_items
.iter()
.filter_map(|item| item.primary.target().added_ids().next())
.cloned()
.map(|commit_id| {
store
.get_commit(&commit_id)
.map(|commit| (commit_id, commit.store_commit().clone()))
})
.try_collect()?;
}
sort(&mut bookmark_list_items, &sort_keys, &commits);
ui.request_pager();
let mut formatter = ui.stdout_formatter();
bookmark_list_items
.iter()
.flat_map(|item| itertools::chain([&item.primary], &item.tracked))
.try_for_each(|commit_ref| template.format(commit_ref, formatter.as_mut()))?;
drop(formatter); drop(formatter);
// Print only one of these hints. It's not important to mention unexported #[cfg(feature = "git")]
// bookmarks, but user might wonder why deleted bookmarks are still listed. if jj_lib::git::get_git_backend(repo.store()).is_ok() {
if found_deleted_tracking_local_bookmark { // Print only one of these hints. It's not important to mention unexported
writeln!( // bookmarks, but user might wonder why deleted bookmarks are still listed.
ui.hint_default(), let deleted_tracking = bookmark_list_items
"Bookmarks marked as deleted will be *deleted permanently* on the remote on the next \ .iter()
`jj git push`. Use `jj bookmark forget` to prevent this." .filter(|item| item.primary.is_local() && item.primary.is_absent())
)?; .map(|item| {
} else if found_deleted_local_bookmark { item.tracked.iter().any(|r| {
writeln!( let remote = r.remote_name().expect("tracked ref should be remote");
ui.hint_default(), !jj_lib::git::is_special_git_remote(remote.as_ref())
"Bookmarks marked as deleted will be deleted from the underlying Git repo on the next \ })
`jj git export`." })
)?; .max();
match deleted_tracking {
Some(true) => {
writeln!(
ui.hint_default(),
"Bookmarks marked as deleted can be *deleted permanently* on the remote by \
running `jj git push --deleted`. Use `jj bookmark forget` if you don't want \
that."
)?;
}
Some(false) => {
writeln!(
ui.hint_default(),
"Bookmarks marked as deleted will be deleted from the underlying Git repo on \
the next `jj git export`."
)?;
}
None => {}
}
} }
Ok(()) Ok(())
} }
#[derive(Clone, Debug)]
struct RefListItem {
/// Local bookmark or untracked remote bookmark.
primary: Rc<CommitRef>,
/// Remote bookmarks tracked by the primary (or local) bookmark.
tracked: Vec<Rc<CommitRef>>,
}
/// Sort key for the `--sort` argument option.
#[derive(Copy, Clone, PartialEq, Debug, ValueEnum)]
enum SortKey {
Name,
#[value(name = "name-")]
NameDesc,
AuthorName,
#[value(name = "author-name-")]
AuthorNameDesc,
AuthorEmail,
#[value(name = "author-email-")]
AuthorEmailDesc,
AuthorDate,
#[value(name = "author-date-")]
AuthorDateDesc,
CommitterName,
#[value(name = "committer-name-")]
CommitterNameDesc,
CommitterEmail,
#[value(name = "committer-email-")]
CommitterEmailDesc,
CommitterDate,
#[value(name = "committer-date-")]
CommitterDateDesc,
}
impl SortKey {
fn is_commit_dependant(&self) -> bool {
match self {
SortKey::Name | SortKey::NameDesc => false,
SortKey::AuthorName
| SortKey::AuthorNameDesc
| SortKey::AuthorEmail
| SortKey::AuthorEmailDesc
| SortKey::AuthorDate
| SortKey::AuthorDateDesc
| SortKey::CommitterName
| SortKey::CommitterNameDesc
| SortKey::CommitterEmail
| SortKey::CommitterEmailDesc
| SortKey::CommitterDate
| SortKey::CommitterDateDesc => true,
}
}
}
fn parse_sort_keys(value: ConfigValue) -> Result<Vec<SortKey>, String> {
if let Some(array) = value.as_array() {
array
.iter()
.map(|item| {
item.as_str()
.ok_or("Expected sort key as a string".to_owned())
.and_then(|key| SortKey::from_str(key, false))
})
.try_collect()
} else {
Err("Expected an array of sort keys as strings".to_owned())
}
}
fn sort(
bookmark_items: &mut [RefListItem],
sort_keys: &[SortKey],
commits: &HashMap<CommitId, Arc<backend::Commit>>,
) {
let to_commit = |item: &RefListItem| {
let id = item.primary.target().added_ids().next()?;
commits.get(id)
};
// Multi-pass sorting, the first key is most significant.
// Skip first iteration if sort key is `Name`, since bookmarks are already
// sorted by name.
for sort_key in sort_keys
.iter()
.rev()
.skip_while(|key| *key == &SortKey::Name)
{
match sort_key {
SortKey::Name => {
bookmark_items.sort_by_key(|item| {
(
item.primary.name().to_owned(),
item.primary.remote_name().map(|name| name.to_owned()),
)
});
}
SortKey::NameDesc => {
bookmark_items.sort_by_key(|item| {
cmp::Reverse((
item.primary.name().to_owned(),
item.primary.remote_name().map(|name| name.to_owned()),
))
});
}
SortKey::AuthorName => bookmark_items
.sort_by_key(|item| to_commit(item).map(|commit| commit.author.name.as_str())),
SortKey::AuthorNameDesc => bookmark_items.sort_by_key(|item| {
cmp::Reverse(to_commit(item).map(|commit| commit.author.name.as_str()))
}),
SortKey::AuthorEmail => bookmark_items
.sort_by_key(|item| to_commit(item).map(|commit| commit.author.email.as_str())),
SortKey::AuthorEmailDesc => bookmark_items.sort_by_key(|item| {
cmp::Reverse(to_commit(item).map(|commit| commit.author.email.as_str()))
}),
SortKey::AuthorDate => bookmark_items
.sort_by_key(|item| to_commit(item).map(|commit| commit.author.timestamp)),
SortKey::AuthorDateDesc => bookmark_items.sort_by_key(|item| {
cmp::Reverse(to_commit(item).map(|commit| commit.author.timestamp))
}),
SortKey::CommitterName => bookmark_items
.sort_by_key(|item| to_commit(item).map(|commit| commit.committer.name.as_str())),
SortKey::CommitterNameDesc => bookmark_items.sort_by_key(|item| {
cmp::Reverse(to_commit(item).map(|commit| commit.committer.name.as_str()))
}),
SortKey::CommitterEmail => bookmark_items
.sort_by_key(|item| to_commit(item).map(|commit| commit.committer.email.as_str())),
SortKey::CommitterEmailDesc => bookmark_items.sort_by_key(|item| {
cmp::Reverse(to_commit(item).map(|commit| commit.committer.email.as_str()))
}),
SortKey::CommitterDate => bookmark_items
.sort_by_key(|item| to_commit(item).map(|commit| commit.committer.timestamp)),
SortKey::CommitterDateDesc => bookmark_items.sort_by_key(|item| {
cmp::Reverse(to_commit(item).map(|commit| commit.committer.timestamp))
}),
}
}
}
#[cfg(test)]
mod tests {
use jj_lib::backend::ChangeId;
use jj_lib::backend::MergedTreeId;
use jj_lib::backend::MillisSinceEpoch;
use jj_lib::backend::Signature;
use jj_lib::backend::Timestamp;
use jj_lib::backend::TreeId;
use jj_lib::op_store::RefTarget;
use super::*;
fn make_backend_commit(author: Signature, committer: Signature) -> Arc<backend::Commit> {
Arc::new(backend::Commit {
parents: vec![],
predecessors: vec![],
root_tree: MergedTreeId::Legacy(TreeId::new(vec![])),
change_id: ChangeId::new(vec![]),
description: String::new(),
author,
committer,
secure_sig: None,
})
}
fn make_default_signature() -> Signature {
Signature {
name: "Test User".to_owned(),
email: "test.user@g.com".to_owned(),
timestamp: Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
},
}
}
fn commit_id_generator() -> impl FnMut() -> CommitId {
let mut iter = (1_u128..).map(|n| CommitId::new(n.to_le_bytes().into()));
move || iter.next().unwrap()
}
fn commit_ts_generator() -> impl FnMut() -> Timestamp {
// iter starts as 1, 1, 2, ... for test purposes
let mut iter = Some(1_i64).into_iter().chain(1_i64..).map(|ms| Timestamp {
timestamp: MillisSinceEpoch(ms),
tz_offset: 0,
});
move || iter.next().unwrap()
}
// Helper function to prepare test data, sort and prepare snapshot with relevant
// information.
fn prepare_data_sort_and_snapshot(sort_keys: &[SortKey]) -> String {
let mut new_commit_id = commit_id_generator();
let mut new_timestamp = commit_ts_generator();
let names = ["bob", "alice", "eve", "bob", "bob"];
let emails = [
"bob@g.com",
"alice@g.com",
"eve@g.com",
"bob@g.com",
"bob@g.com",
];
let bookmark_names = ["feature", "bug-fix", "chore", "bug-fix", "feature"];
let remote_names = [None, Some("upstream"), None, Some("origin"), Some("origin")];
let deleted = [false, false, false, false, true];
let mut bookmark_items: Vec<RefListItem> = Vec::new();
let mut commits: HashMap<CommitId, Arc<backend::Commit>> = HashMap::new();
for (&name, &email, bookmark_name, remote_name, &is_deleted) in
itertools::izip!(&names, &emails, &bookmark_names, &remote_names, &deleted)
{
let commit_id = new_commit_id();
let mut b_name = "foo";
let mut author = make_default_signature();
let mut committer = make_default_signature();
if sort_keys.contains(&SortKey::Name) || sort_keys.contains(&SortKey::NameDesc) {
b_name = bookmark_name;
}
if sort_keys.contains(&SortKey::AuthorName)
|| sort_keys.contains(&SortKey::AuthorNameDesc)
{
author.name = String::from(name);
}
if sort_keys.contains(&SortKey::AuthorEmail)
|| sort_keys.contains(&SortKey::AuthorEmailDesc)
{
author.email = String::from(email);
}
if sort_keys.contains(&SortKey::AuthorDate)
|| sort_keys.contains(&SortKey::AuthorDateDesc)
{
author.timestamp = new_timestamp();
}
if sort_keys.contains(&SortKey::CommitterName)
|| sort_keys.contains(&SortKey::CommitterNameDesc)
{
committer.name = String::from(name);
}
if sort_keys.contains(&SortKey::CommitterEmail)
|| sort_keys.contains(&SortKey::CommitterEmailDesc)
{
committer.email = String::from(email);
}
if sort_keys.contains(&SortKey::CommitterDate)
|| sort_keys.contains(&SortKey::CommitterDateDesc)
{
committer.timestamp = new_timestamp();
}
if let Some(remote_name) = remote_name {
if is_deleted {
bookmark_items.push(RefListItem {
primary: CommitRef::remote_only(b_name, *remote_name, RefTarget::absent()),
tracked: vec![CommitRef::local_only(
b_name,
RefTarget::normal(commit_id.clone()),
)],
});
} else {
bookmark_items.push(RefListItem {
primary: CommitRef::remote_only(
b_name,
*remote_name,
RefTarget::normal(commit_id.clone()),
),
tracked: vec![],
});
}
} else {
bookmark_items.push(RefListItem {
primary: CommitRef::local_only(b_name, RefTarget::normal(commit_id.clone())),
tracked: vec![],
});
}
commits.insert(commit_id, make_backend_commit(author, committer));
}
// The sort function has an assumption that refs are sorted by name.
// Here we support this assumption.
bookmark_items.sort_by_key(|item| {
(
item.primary.name().to_owned(),
item.primary.remote_name().map(|name| name.to_owned()),
)
});
sort_and_snapshot(&mut bookmark_items, sort_keys, &commits)
}
// Helper function to sort refs and prepare snapshot with relevant information.
fn sort_and_snapshot(
items: &mut [RefListItem],
sort_keys: &[SortKey],
commits: &HashMap<CommitId, Arc<backend::Commit>>,
) -> String {
sort(items, sort_keys, commits);
let to_commit = |item: &RefListItem| {
let id = item.primary.target().added_ids().next()?;
commits.get(id)
};
macro_rules! row_format {
($($args:tt)*) => {
format!("{:<20}{:<16}{:<17}{:<14}{:<16}{:<17}{}", $($args)*)
}
}
let header = row_format!(
"Name",
"AuthorName",
"AuthorEmail",
"AuthorDate",
"CommitterName",
"CommitterEmail",
"CommitterDate"
);
let rows: Vec<String> = items
.iter()
.map(|item| {
let name = [Some(item.primary.name()), item.primary.remote_name()]
.iter()
.flatten()
.join("@");
let commit = to_commit(item);
let author_name = commit
.map(|c| c.author.name.clone())
.unwrap_or_else(|| String::from("-"));
let author_email = commit
.map(|c| c.author.email.clone())
.unwrap_or_else(|| String::from("-"));
let author_date = commit
.map(|c| c.author.timestamp.timestamp.0.to_string())
.unwrap_or_else(|| String::from("-"));
let committer_name = commit
.map(|c| c.committer.name.clone())
.unwrap_or_else(|| String::from("-"));
let committer_email = commit
.map(|c| c.committer.email.clone())
.unwrap_or_else(|| String::from("-"));
let committer_date = commit
.map(|c| c.committer.timestamp.timestamp.0.to_string())
.unwrap_or_else(|| String::from("-"));
row_format!(
name,
author_name,
author_email,
author_date,
committer_name,
committer_email,
committer_date
)
})
.collect();
let mut result = vec![header];
result.extend(rows);
result.join("\n")
}
#[test]
fn test_sort_by_name() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::Name]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
bug-fix@origin Test User test.user@g.com 0 Test User test.user@g.com 0
bug-fix@upstream Test User test.user@g.com 0 Test User test.user@g.com 0
chore Test User test.user@g.com 0 Test User test.user@g.com 0
feature Test User test.user@g.com 0 Test User test.user@g.com 0
feature@origin - - - - - -
");
}
#[test]
fn test_sort_by_name_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::NameDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
feature@origin - - - - - -
feature Test User test.user@g.com 0 Test User test.user@g.com 0
chore Test User test.user@g.com 0 Test User test.user@g.com 0
bug-fix@upstream Test User test.user@g.com 0 Test User test.user@g.com 0
bug-fix@origin Test User test.user@g.com 0 Test User test.user@g.com 0
");
}
#[test]
fn test_sort_by_author_name() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorName]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin - - - - - -
foo@upstream alice test.user@g.com 0 Test User test.user@g.com 0
foo bob test.user@g.com 0 Test User test.user@g.com 0
foo@origin bob test.user@g.com 0 Test User test.user@g.com 0
foo eve test.user@g.com 0 Test User test.user@g.com 0
");
}
#[test]
fn test_sort_by_author_name_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorNameDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo eve test.user@g.com 0 Test User test.user@g.com 0
foo bob test.user@g.com 0 Test User test.user@g.com 0
foo@origin bob test.user@g.com 0 Test User test.user@g.com 0
foo@upstream alice test.user@g.com 0 Test User test.user@g.com 0
foo@origin - - - - - -
");
}
#[test]
fn test_sort_by_author_email() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorEmail]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin - - - - - -
foo@upstream Test User alice@g.com 0 Test User test.user@g.com 0
foo Test User bob@g.com 0 Test User test.user@g.com 0
foo@origin Test User bob@g.com 0 Test User test.user@g.com 0
foo Test User eve@g.com 0 Test User test.user@g.com 0
");
}
#[test]
fn test_sort_by_author_email_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorEmailDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo Test User eve@g.com 0 Test User test.user@g.com 0
foo Test User bob@g.com 0 Test User test.user@g.com 0
foo@origin Test User bob@g.com 0 Test User test.user@g.com 0
foo@upstream Test User alice@g.com 0 Test User test.user@g.com 0
foo@origin - - - - - -
");
}
#[test]
fn test_sort_by_author_date() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorDate]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin - - - - - -
foo Test User test.user@g.com 1 Test User test.user@g.com 0
foo@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
foo Test User test.user@g.com 2 Test User test.user@g.com 0
foo@origin Test User test.user@g.com 3 Test User test.user@g.com 0
");
}
#[test]
fn test_sort_by_author_date_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorDateDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin Test User test.user@g.com 3 Test User test.user@g.com 0
foo Test User test.user@g.com 2 Test User test.user@g.com 0
foo Test User test.user@g.com 1 Test User test.user@g.com 0
foo@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
foo@origin - - - - - -
");
}
#[test]
fn test_sort_by_committer_name() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterName]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin - - - - - -
foo@upstream Test User test.user@g.com 0 alice test.user@g.com 0
foo Test User test.user@g.com 0 bob test.user@g.com 0
foo@origin Test User test.user@g.com 0 bob test.user@g.com 0
foo Test User test.user@g.com 0 eve test.user@g.com 0
");
}
#[test]
fn test_sort_by_committer_name_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterNameDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo Test User test.user@g.com 0 eve test.user@g.com 0
foo Test User test.user@g.com 0 bob test.user@g.com 0
foo@origin Test User test.user@g.com 0 bob test.user@g.com 0
foo@upstream Test User test.user@g.com 0 alice test.user@g.com 0
foo@origin - - - - - -
");
}
#[test]
fn test_sort_by_committer_email() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterEmail]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin - - - - - -
foo@upstream Test User test.user@g.com 0 Test User alice@g.com 0
foo Test User test.user@g.com 0 Test User bob@g.com 0
foo@origin Test User test.user@g.com 0 Test User bob@g.com 0
foo Test User test.user@g.com 0 Test User eve@g.com 0
");
}
#[test]
fn test_sort_by_committer_email_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterEmailDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo Test User test.user@g.com 0 Test User eve@g.com 0
foo Test User test.user@g.com 0 Test User bob@g.com 0
foo@origin Test User test.user@g.com 0 Test User bob@g.com 0
foo@upstream Test User test.user@g.com 0 Test User alice@g.com 0
foo@origin - - - - - -
");
}
#[test]
fn test_sort_by_committer_date() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterDate]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin - - - - - -
foo Test User test.user@g.com 0 Test User test.user@g.com 1
foo@upstream Test User test.user@g.com 0 Test User test.user@g.com 1
foo Test User test.user@g.com 0 Test User test.user@g.com 2
foo@origin Test User test.user@g.com 0 Test User test.user@g.com 3
");
}
#[test]
fn test_sort_by_committer_date_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterDateDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
foo@origin Test User test.user@g.com 0 Test User test.user@g.com 3
foo Test User test.user@g.com 0 Test User test.user@g.com 2
foo Test User test.user@g.com 0 Test User test.user@g.com 1
foo@upstream Test User test.user@g.com 0 Test User test.user@g.com 1
foo@origin - - - - - -
");
}
#[test]
fn test_sort_by_author_date_desc_and_name() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::AuthorDateDesc, SortKey::Name]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
bug-fix@origin Test User test.user@g.com 3 Test User test.user@g.com 0
chore Test User test.user@g.com 2 Test User test.user@g.com 0
bug-fix@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
feature Test User test.user@g.com 1 Test User test.user@g.com 0
feature@origin - - - - - -
");
}
#[test]
fn test_sort_by_committer_name_and_name_desc() {
insta::assert_snapshot!(
prepare_data_sort_and_snapshot(&[SortKey::CommitterName, SortKey::NameDesc]), @r"
Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
feature@origin - - - - - -
bug-fix@upstream Test User test.user@g.com 0 alice test.user@g.com 0
feature Test User test.user@g.com 0 bob test.user@g.com 0
bug-fix@origin Test User test.user@g.com 0 bob test.user@g.com 0
chore Test User test.user@g.com 0 eve test.user@g.com 0
");
}
}

View File

@ -24,9 +24,10 @@ mod untrack;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::backend::CommitId; use jj_lib::backend::CommitId;
use jj_lib::git;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
use jj_lib::op_store::RemoteRef; use jj_lib::op_store::RemoteRef;
use jj_lib::ref_name::RefName;
use jj_lib::ref_name::RemoteRefSymbol;
use jj_lib::repo::Repo; use jj_lib::repo::Repo;
use jj_lib::str_util::StringPattern; use jj_lib::str_util::StringPattern;
use jj_lib::view::View; use jj_lib::view::View;
@ -50,16 +51,20 @@ use self::track::BookmarkTrackArgs;
use self::untrack::cmd_bookmark_untrack; use self::untrack::cmd_bookmark_untrack;
use self::untrack::BookmarkUntrackArgs; use self::untrack::BookmarkUntrackArgs;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RemoteBookmarkName;
use crate::cli_util::RemoteBookmarkNamePattern; use crate::cli_util::RemoteBookmarkNamePattern;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::ui::Ui; use crate::ui::Ui;
/// Manage bookmarks // Unlike most other aliases, `b` is defined in the config and can be overridden
// by the user.
/// Manage bookmarks [default alias: b]
/// ///
/// For information about bookmarks, see /// See [`jj help -k bookmarks`] for more information.
/// https://martinvonz.github.io/jj/latest/bookmarks. ///
/// [`jj help -k bookmarks`]:
/// https://jj-vcs.github.io/jj/latest/bookmarks
#[derive(clap::Subcommand, Clone, Debug)] #[derive(clap::Subcommand, Clone, Debug)]
pub enum BookmarkCommand { pub enum BookmarkCommand {
#[command(visible_alias("c"))] #[command(visible_alias("c"))]
@ -102,7 +107,7 @@ pub fn cmd_bookmark(
fn find_local_bookmarks<'a>( fn find_local_bookmarks<'a>(
view: &'a View, view: &'a View,
name_patterns: &[StringPattern], name_patterns: &[StringPattern],
) -> Result<Vec<(&'a str, &'a RefTarget)>, CommandError> { ) -> Result<Vec<(&'a RefName, &'a RefTarget)>, CommandError> {
find_bookmarks_with(name_patterns, |pattern| { find_bookmarks_with(name_patterns, |pattern| {
view.local_bookmarks_matching(pattern).map(Ok) view.local_bookmarks_matching(pattern).map(Ok)
}) })
@ -111,11 +116,11 @@ fn find_local_bookmarks<'a>(
fn find_bookmarks_with<'a, 'b, V, I>( fn find_bookmarks_with<'a, 'b, V, I>(
name_patterns: &'b [StringPattern], name_patterns: &'b [StringPattern],
mut find_matches: impl FnMut(&'b StringPattern) -> I, mut find_matches: impl FnMut(&'b StringPattern) -> I,
) -> Result<Vec<(&'a str, V)>, CommandError> ) -> Result<Vec<(&'a RefName, V)>, CommandError>
where where
I: Iterator<Item = Result<(&'a str, V), CommandError>>, I: Iterator<Item = Result<(&'a RefName, V), CommandError>>,
{ {
let mut matching_bookmarks: Vec<(&'a str, V)> = vec![]; let mut matching_bookmarks: Vec<(&'a RefName, V)> = vec![];
let mut unmatched_patterns = vec![]; let mut unmatched_patterns = vec![];
for pattern in name_patterns { for pattern in name_patterns {
let mut matches = find_matches(pattern).peekable(); let mut matches = find_matches(pattern).peekable();
@ -141,19 +146,12 @@ where
fn find_remote_bookmarks<'a>( fn find_remote_bookmarks<'a>(
view: &'a View, view: &'a View,
name_patterns: &[RemoteBookmarkNamePattern], name_patterns: &[RemoteBookmarkNamePattern],
) -> Result<Vec<(RemoteBookmarkName, &'a RemoteRef)>, CommandError> { ) -> Result<Vec<(RemoteRefSymbol<'a>, &'a RemoteRef)>, CommandError> {
let mut matching_bookmarks = vec![]; let mut matching_bookmarks = vec![];
let mut unmatched_patterns = vec![]; let mut unmatched_patterns = vec![];
for pattern in name_patterns { for pattern in name_patterns {
let mut matches = view let mut matches = view
.remote_bookmarks_matching(&pattern.bookmark, &pattern.remote) .remote_bookmarks_matching(&pattern.bookmark, &pattern.remote)
.map(|((bookmark, remote), remote_ref)| {
let name = RemoteBookmarkName {
bookmark: bookmark.to_owned(),
remote: remote.to_owned(),
};
(name, remote_ref)
})
.peekable(); .peekable();
if matches.peek().is_none() { if matches.peek().is_none() {
unmatched_patterns.push(pattern); unmatched_patterns.push(pattern);
@ -162,8 +160,8 @@ fn find_remote_bookmarks<'a>(
} }
match &unmatched_patterns[..] { match &unmatched_patterns[..] {
[] => { [] => {
matching_bookmarks.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); matching_bookmarks.sort_unstable_by(|(sym1, _), (sym2, _)| sym1.cmp(sym2));
matching_bookmarks.dedup_by(|(name1, _), (name2, _)| name1 == name2); matching_bookmarks.dedup_by(|(sym1, _), (sym2, _)| sym1 == sym2);
Ok(matching_bookmarks) Ok(matching_bookmarks)
} }
[pattern] if pattern.is_exact() => { [pattern] if pattern.is_exact() => {
@ -176,17 +174,6 @@ fn find_remote_bookmarks<'a>(
} }
} }
/// Whether or not the `bookmark` has any tracked remotes (i.e. is a tracking
/// local bookmark.)
fn has_tracked_remote_bookmarks(view: &View, bookmark: &str) -> bool {
view.remote_bookmarks_matching(
&StringPattern::exact(bookmark),
&StringPattern::everything(),
)
.filter(|&((_, remote_name), _)| remote_name != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO)
.any(|(_, remote_ref)| remote_ref.is_tracking())
}
fn is_fast_forward(repo: &dyn Repo, old_target: &RefTarget, new_target_id: &CommitId) -> bool { fn is_fast_forward(repo: &dyn Repo, old_target: &RefTarget, new_target_id: &CommitId) -> bool {
if old_target.is_present() { if old_target.is_present() {
// Strictly speaking, "all" old targets should be ancestors, but we allow // Strictly speaking, "all" old targets should be ancestors, but we allow

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::object_id::ObjectId as _; use jj_lib::object_id::ObjectId as _;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
@ -23,6 +25,7 @@ use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error_with_hint; use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Move existing bookmarks to target revision /// Move existing bookmarks to target revision
@ -41,17 +44,24 @@ use crate::ui::Ui;
#[command(group(clap::ArgGroup::new("source").multiple(true).required(true)))] #[command(group(clap::ArgGroup::new("source").multiple(true).required(true)))]
pub struct BookmarkMoveArgs { pub struct BookmarkMoveArgs {
/// Move bookmarks from the given revisions /// Move bookmarks from the given revisions
// We intentionally do not support the short `-f` for `--from` since it #[arg(
// could be confused with a shorthand for `--force`, and people might not long, short,
// realize they need `-B`/`--allow-backwards` instead. group = "source",
#[arg(long, group = "source", value_name = "REVISIONS")] value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
from: Vec<RevisionArg>, from: Vec<RevisionArg>,
// TODO(#5374): Make required in jj 0.32+
/// Move bookmarks to this revision /// Move bookmarks to this revision
// We intentionally do not support the short `-t` for `--to` since we don't // Currently this defaults to the working copy, but in the near
// support `-f` for `--from`. // future it will be required to explicitly specify it.
#[arg(long, default_value = "@", value_name = "REVISION")] #[arg(
to: RevisionArg, long, short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
to: Option<RevisionArg>,
/// Allow moving bookmarks backwards or sideways /// Allow moving bookmarks backwards or sideways
#[arg(long, short = 'B')] #[arg(long, short = 'B')]
@ -60,9 +70,15 @@ pub struct BookmarkMoveArgs {
/// Move bookmarks matching the given name patterns /// Move bookmarks matching the given name patterns
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// select bookmarks by wildcard pattern. For details, see /// select bookmarks by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns. ///
#[arg(group = "source", value_parser = StringPattern::parse)] /// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg(
group = "source",
value_parser = StringPattern::parse,
add = ArgValueCandidates::new(complete::local_bookmarks),
)]
names: Vec<StringPattern>, names: Vec<StringPattern>,
} }
@ -73,8 +89,15 @@ pub fn cmd_bookmark_move(
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo().clone(); let repo = workspace_command.repo().clone();
if args.to.is_none() {
let target_commit = workspace_command.resolve_single_rev(ui, &args.to)?; writeln!(
ui.warning_default(),
"Target revision was not specified, defaulting to the working copy (--to=@). In the \
near future it will be required to explicitly specify it."
)?;
}
let target_commit =
workspace_command.resolve_single_rev(ui, args.to.as_ref().unwrap_or(&RevisionArg::AT))?;
let matched_bookmarks = { let matched_bookmarks = {
let is_source_ref: Box<dyn Fn(&RefTarget) -> _> = if !args.from.is_empty() { let is_source_ref: Box<dyn Fn(&RefTarget) -> _> = if !args.from.is_empty() {
let is_source_commit = workspace_command let is_source_commit = workspace_command
@ -128,7 +151,10 @@ pub fn cmd_bookmark_move(
.find(|(_, old_target)| !is_fast_forward(repo.as_ref(), old_target, target_commit.id())) .find(|(_, old_target)| !is_fast_forward(repo.as_ref(), old_target, target_commit.id()))
{ {
return Err(user_error_with_hint( return Err(user_error_with_hint(
format!("Refusing to move bookmark backwards or sideways: {name}"), format!(
"Refusing to move bookmark backwards or sideways: {name}",
name = name.as_symbol()
),
"Use --allow-backwards to allow it.", "Use --allow-backwards to allow it.",
)); ));
} }
@ -156,7 +182,10 @@ pub fn cmd_bookmark_move(
ui, ui,
format!( format!(
"point bookmark {names} to commit {id}", "point bookmark {names} to commit {id}",
names = matched_bookmarks.iter().map(|(name, _)| name).join(", "), names = matched_bookmarks
.iter()
.map(|(name, _)| name.as_symbol())
.join(", "),
id = target_commit.id().hex() id = target_commit.id().hex()
), ),
)?; )?;

View File

@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
use jj_lib::ref_name::RefNameBuf;
use super::has_tracked_remote_bookmarks; use crate::cli_util::has_tracked_remote_bookmarks;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::revset_util;
use crate::ui::Ui; use crate::ui::Ui;
/// Rename `old` bookmark name to `new` bookmark name /// Rename `old` bookmark name to `new` bookmark name
@ -26,10 +30,15 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkRenameArgs { pub struct BookmarkRenameArgs {
/// The old name of the bookmark /// The old name of the bookmark
old: String, #[arg(
value_parser = revset_util::parse_bookmark_name,
add = ArgValueCandidates::new(complete::local_bookmarks),
)]
old: RefNameBuf,
/// The new name of the bookmark /// The new name of the bookmark
new: String, #[arg(value_parser = revset_util::parse_bookmark_name)]
new: RefNameBuf,
} }
pub fn cmd_bookmark_rename( pub fn cmd_bookmark_rename(
@ -42,13 +51,17 @@ pub fn cmd_bookmark_rename(
let old_bookmark = &args.old; let old_bookmark = &args.old;
let ref_target = view.get_local_bookmark(old_bookmark).clone(); let ref_target = view.get_local_bookmark(old_bookmark).clone();
if ref_target.is_absent() { if ref_target.is_absent() {
return Err(user_error(format!("No such bookmark: {old_bookmark}"))); return Err(user_error(format!(
"No such bookmark: {old_bookmark}",
old_bookmark = old_bookmark.as_symbol()
)));
} }
let new_bookmark = &args.new; let new_bookmark = &args.new;
if view.get_local_bookmark(new_bookmark).is_present() { if view.get_local_bookmark(new_bookmark).is_present() {
return Err(user_error(format!( return Err(user_error(format!(
"Bookmark already exists: {new_bookmark}" "Bookmark already exists: {new_bookmark}",
new_bookmark = new_bookmark.as_symbol()
))); )));
} }
@ -59,7 +72,11 @@ pub fn cmd_bookmark_rename(
.set_local_bookmark_target(old_bookmark, RefTarget::absent()); .set_local_bookmark_target(old_bookmark, RefTarget::absent());
tx.finish( tx.finish(
ui, ui,
format!("rename bookmark {old_bookmark} to {new_bookmark}"), format!(
"rename bookmark {old_bookmark} to {new_bookmark}",
old_bookmark = old_bookmark.as_symbol(),
new_bookmark = new_bookmark.as_symbol()
),
)?; )?;
let view = workspace_command.repo().view(); let view = workspace_command.repo().view();
@ -67,12 +84,15 @@ pub fn cmd_bookmark_rename(
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"Tracked remote bookmarks for bookmark {old_bookmark} were not renamed.", "Tracked remote bookmarks for bookmark {old_bookmark} were not renamed.",
old_bookmark = old_bookmark.as_symbol(),
)?; )?;
writeln!( writeln!(
ui.hint_default(), ui.hint_default(),
"To rename the bookmark on the remote, you can `jj git push --bookmark \ "To rename the bookmark on the remote, you can `jj git push --bookmark \
{old_bookmark}` first (to delete it on the remote), and then `jj git push --bookmark \ {old_bookmark}` first (to delete it on the remote), and then `jj git push --bookmark \
{new_bookmark}`. `jj git push --all` would also be sufficient." {new_bookmark}`. `jj git push --all` would also be sufficient.",
old_bookmark = old_bookmark.as_symbol(),
new_bookmark = new_bookmark.as_symbol()
)?; )?;
} }
if has_tracked_remote_bookmarks(view, new_bookmark) { if has_tracked_remote_bookmarks(view, new_bookmark) {
@ -81,11 +101,14 @@ pub fn cmd_bookmark_rename(
// allowed even if the original old bookmark had tracked remotes. // allowed even if the original old bookmark had tracked remotes.
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"Tracked remote bookmarks for bookmark {new_bookmark} exist." "Tracked remote bookmarks for bookmark {new_bookmark} exist.",
new_bookmark = new_bookmark.as_symbol()
)?; )?;
writeln!( writeln!(
ui.hint_default(), ui.hint_default(),
"Run `jj bookmark untrack 'glob:{new_bookmark}@*'` to disassociate them." "Run `jj bookmark untrack 'glob:{new_bookmark}@*'` to disassociate them.",
// TODO: use .as_symbol() if pattern parser is ported to revset
new_bookmark = new_bookmark.as_str()
)?; )?;
} }

View File

@ -12,23 +12,37 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap::builder::NonEmptyStringValueParser; use clap_complete::ArgValueCandidates;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::object_id::ObjectId as _; use jj_lib::object_id::ObjectId as _;
use jj_lib::op_store::RefTarget; use jj_lib::op_store::RefTarget;
use jj_lib::ref_name::RefNameBuf;
use super::has_tracked_remote_bookmarks;
use super::is_fast_forward; use super::is_fast_forward;
use crate::cli_util::has_tracked_remote_bookmarks;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error_with_hint; use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::revset_util;
use crate::ui::Ui; use crate::ui::Ui;
/// Create or update a bookmark to point to a certain commit /// Create or update a bookmark to point to a certain commit
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkSetArgs { pub struct BookmarkSetArgs {
// TODO(#5374): Make required in jj 0.32+
/// The bookmark's target revision /// The bookmark's target revision
#[arg(long, short, visible_alias = "to")] //
// Currently target revision defaults to the working copy if not specified, but in the near
// future it will be required to explicitly specify it.
#[arg(
long, short,
visible_alias = "to",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revision: Option<RevisionArg>, revision: Option<RevisionArg>,
/// Allow moving the bookmark backwards or sideways /// Allow moving the bookmark backwards or sideways
@ -36,8 +50,12 @@ pub struct BookmarkSetArgs {
allow_backwards: bool, allow_backwards: bool,
/// The bookmarks to update /// The bookmarks to update
#[arg(required = true, value_parser = NonEmptyStringValueParser::new())] #[arg(
names: Vec<String>, required = true,
value_parser = revset_util::parse_bookmark_name,
add = ArgValueCandidates::new(complete::local_bookmarks),
)]
names: Vec<RefNameBuf>,
} }
pub fn cmd_bookmark_set( pub fn cmd_bookmark_set(
@ -46,6 +64,13 @@ pub fn cmd_bookmark_set(
args: &BookmarkSetArgs, args: &BookmarkSetArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
if args.revision.is_none() {
writeln!(
ui.warning_default(),
"Target revision was not specified, defaulting to the working copy (--revision=@). In \
the near future it will be required to explicitly specify target revision."
)?;
}
let target_commit = workspace_command let target_commit = workspace_command
.resolve_single_rev(ui, args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; .resolve_single_rev(ui, args.revision.as_ref().unwrap_or(&RevisionArg::AT))?;
let repo = workspace_command.repo().as_ref(); let repo = workspace_command.repo().as_ref();
@ -63,7 +88,10 @@ pub fn cmd_bookmark_set(
} }
if !args.allow_backwards && !is_fast_forward(repo, old_target, target_commit.id()) { if !args.allow_backwards && !is_fast_forward(repo, old_target, target_commit.id()) {
return Err(user_error_with_hint( return Err(user_error_with_hint(
format!("Refusing to move bookmark backwards or sideways: {name}"), format!(
"Refusing to move bookmark backwards or sideways: {name}",
name = name.as_symbol()
),
"Use --allow-backwards to allow it.", "Use --allow-backwards to allow it.",
)); ));
} }
@ -95,19 +123,12 @@ pub fn cmd_bookmark_set(
if bookmark_names.len() > 1 && args.revision.is_none() { if bookmark_names.len() > 1 && args.revision.is_none() {
writeln!(ui.hint_default(), "Use -r to specify the target revision.")?; writeln!(ui.hint_default(), "Use -r to specify the target revision.")?;
} }
if new_bookmark_count > 0 {
// TODO: delete this hint in jj 0.25+
writeln!(
ui.hint_default(),
"Consider using `jj bookmark move` if your intention was to move existing bookmarks."
)?;
}
tx.finish( tx.finish(
ui, ui,
format!( format!(
"point bookmark {names} to commit {id}", "point bookmark {names} to commit {id}",
names = bookmark_names.join(", "), names = bookmark_names.iter().map(|n| n.as_symbol()).join(", "),
id = target_commit.id().hex() id = target_commit.id().hex()
), ),
)?; )?;

View File

@ -14,14 +14,16 @@
use std::collections::HashMap; use std::collections::HashMap;
use clap_complete::ArgValueCandidates;
use itertools::Itertools as _; use itertools::Itertools as _;
use super::find_remote_bookmarks; use super::find_remote_bookmarks;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RemoteBookmarkNamePattern; use crate::cli_util::RemoteBookmarkNamePattern;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitRef;
use crate::commit_templater::RefName; use crate::commit_templater::CommitTemplatePropertyKind;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Start tracking given remote bookmarks /// Start tracking given remote bookmarks
@ -34,11 +36,17 @@ pub struct BookmarkTrackArgs {
/// Remote bookmarks to track /// Remote bookmarks to track
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// select bookmarks by wildcard pattern. For details, see /// select bookmarks by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns.
/// ///
/// Examples: bookmark@remote, glob:main@*, glob:jjfan-*@upstream /// Examples: bookmark@remote, glob:main@*, glob:jjfan-*@upstream
#[arg(required = true, value_name = "BOOKMARK@REMOTE")] ///
/// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg(
required = true,
value_name = "BOOKMARK@REMOTE",
add = ArgValueCandidates::new(complete::untracked_bookmarks),
)]
names: Vec<RemoteBookmarkNamePattern>, names: Vec<RemoteBookmarkNamePattern>,
} }
@ -48,33 +56,32 @@ pub fn cmd_bookmark_track(
args: &BookmarkTrackArgs, args: &BookmarkTrackArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let view = workspace_command.repo().view(); let repo = workspace_command.repo().clone();
let mut names = Vec::new(); let mut symbols = Vec::new();
for (name, remote_ref) in find_remote_bookmarks(view, &args.names)? { for (symbol, remote_ref) in find_remote_bookmarks(repo.view(), &args.names)? {
if remote_ref.is_tracking() { if remote_ref.is_tracked() {
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"Remote bookmark already tracked: {name}" "Remote bookmark already tracked: {symbol}"
)?; )?;
} else { } else {
names.push(name); symbols.push(symbol);
} }
} }
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
for name in &names { for &symbol in &symbols {
tx.repo_mut() tx.repo_mut().track_remote_bookmark(symbol);
.track_remote_bookmark(&name.bookmark, &name.remote);
} }
if !names.is_empty() { if !symbols.is_empty() {
writeln!( writeln!(
ui.status(), ui.status(),
"Started tracking {} remote bookmarks.", "Started tracking {} remote bookmarks.",
names.len() symbols.len()
)?; )?;
} }
tx.finish( tx.finish(
ui, ui,
format!("track remote bookmark {}", names.iter().join(", ")), format!("track remote bookmark {}", symbols.iter().join(", ")),
)?; )?;
//show conflicted bookmarks if there are some //show conflicted bookmarks if there are some
@ -82,21 +89,25 @@ pub fn cmd_bookmark_track(
if let Some(mut formatter) = ui.status_formatter() { if let Some(mut formatter) = ui.status_formatter() {
let template = { let template = {
let language = workspace_command.commit_template_language(); let language = workspace_command.commit_template_language();
let text = command let text = workspace_command
.settings() .settings()
.config()
.get::<String>("templates.bookmark_list")?; .get::<String>("templates.bookmark_list")?;
workspace_command workspace_command
.parse_template(ui, &language, &text, CommitTemplateLanguage::wrap_ref_name)? .parse_template(
ui,
&language,
&text,
CommitTemplatePropertyKind::wrap_commit_ref,
)?
.labeled("bookmark_list") .labeled("bookmark_list")
}; };
let mut remote_per_bookmark: HashMap<&str, Vec<&str>> = HashMap::new(); let mut remote_per_bookmark: HashMap<_, Vec<_>> = HashMap::new();
for n in &names { for symbol in &symbols {
remote_per_bookmark remote_per_bookmark
.entry(&n.bookmark) .entry(symbol.name)
.or_default() .or_default()
.push(&n.remote); .push(symbol.remote);
} }
let bookmarks_to_list = let bookmarks_to_list =
workspace_command workspace_command
@ -109,18 +120,18 @@ pub fn cmd_bookmark_track(
for (name, bookmark_target) in bookmarks_to_list { for (name, bookmark_target) in bookmarks_to_list {
let local_target = bookmark_target.local_target; let local_target = bookmark_target.local_target;
let ref_name = RefName::local( let commit_ref = CommitRef::local(
name, name,
local_target.clone(), local_target.clone(),
bookmark_target.remote_refs.iter().map(|x| x.1), bookmark_target.remote_refs.iter().map(|x| x.1),
); );
template.format(&ref_name, formatter.as_mut())?; template.format(&commit_ref, formatter.as_mut())?;
for (remote_name, remote_ref) in bookmark_target.remote_refs { for (remote_name, remote_ref) in bookmark_target.remote_refs {
if remote_per_bookmark[name].contains(&remote_name) { if remote_per_bookmark[name].contains(&remote_name) {
let ref_name = let commit_ref =
RefName::remote(name, remote_name, remote_ref.clone(), local_target); CommitRef::remote(name, remote_name, remote_ref.clone(), local_target);
template.format(&ref_name, formatter.as_mut())?; template.format(&commit_ref, formatter.as_mut())?;
} }
} }
} }

View File

@ -12,29 +12,39 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use itertools::Itertools as _; use itertools::Itertools as _;
use jj_lib::git;
use super::find_remote_bookmarks; use super::find_remote_bookmarks;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RemoteBookmarkNamePattern; use crate::cli_util::RemoteBookmarkNamePattern;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Stop tracking given remote bookmarks /// Stop tracking given remote bookmarks
/// ///
/// A non-tracking remote bookmark is just a pointer to the last-fetched remote /// A non-tracking remote bookmark is just a pointer to the last-fetched remote
/// bookmark. It won't be imported as a local bookmark on future pulls. /// bookmark. It won't be imported as a local bookmark on future pulls.
///
/// If you want to forget a local bookmark while also untracking the
/// corresponding remote bookmarks, use `jj bookmark forget` instead.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct BookmarkUntrackArgs { pub struct BookmarkUntrackArgs {
/// Remote bookmarks to untrack /// Remote bookmarks to untrack
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// select bookmarks by wildcard pattern. For details, see /// select bookmarks by [wildcard pattern].
/// https://martinvonz.github.io/jj/latest/revsets/#string-patterns.
/// ///
/// Examples: bookmark@remote, glob:main@*, glob:jjfan-*@upstream /// Examples: bookmark@remote, glob:main@*, glob:jjfan-*@upstream
#[arg(required = true, value_name = "BOOKMARK@REMOTE")] ///
/// [wildcard pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets/#string-patterns
#[arg(
required = true,
value_name = "BOOKMARK@REMOTE",
add = ArgValueCandidates::new(complete::tracked_bookmarks)
)]
names: Vec<RemoteBookmarkNamePattern>, names: Vec<RemoteBookmarkNamePattern>,
} }
@ -44,40 +54,39 @@ pub fn cmd_bookmark_untrack(
args: &BookmarkUntrackArgs, args: &BookmarkUntrackArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let view = workspace_command.repo().view(); let repo = workspace_command.repo().clone();
let mut names = Vec::new(); let mut symbols = Vec::new();
for (name, remote_ref) in find_remote_bookmarks(view, &args.names)? { for (symbol, remote_ref) in find_remote_bookmarks(repo.view(), &args.names)? {
if name.remote == git::REMOTE_NAME_FOR_LOCAL_GIT_REPO { if jj_lib::git::is_special_git_remote(symbol.remote) {
// This restriction can be lifted if we want to support untracked @git // This restriction can be lifted if we want to support untracked @git
// bookmarks. // bookmarks.
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"Git-tracking bookmark cannot be untracked: {name}" "Git-tracking bookmark cannot be untracked: {symbol}"
)?; )?;
} else if !remote_ref.is_tracking() { } else if !remote_ref.is_tracked() {
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"Remote bookmark not tracked yet: {name}" "Remote bookmark not tracked yet: {symbol}"
)?; )?;
} else { } else {
names.push(name); symbols.push(symbol);
} }
} }
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
for name in &names { for &symbol in &symbols {
tx.repo_mut() tx.repo_mut().untrack_remote_bookmark(symbol);
.untrack_remote_bookmark(&name.bookmark, &name.remote);
} }
if !names.is_empty() { if !symbols.is_empty() {
writeln!( writeln!(
ui.status(), ui.status(),
"Stopped tracking {} remote bookmarks.", "Stopped tracking {} remote bookmarks.",
names.len() symbols.len()
)?; )?;
} }
tx.finish( tx.finish(
ui, ui,
format!("untrack remote bookmark {}", names.iter().join(", ")), format!("untrack remote bookmark {}", symbols.iter().join(", ")),
)?; )?;
Ok(()) Ok(())
} }

View File

@ -1,70 +0,0 @@
// Copyright 2020 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use jj_lib::object_id::ObjectId;
use tracing::instrument;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::command_error::CommandError;
use crate::description_util::join_message_paragraphs;
use crate::ui::Ui;
/// Create a new, empty change and edit it in the working copy (DEPRECATED, use
/// `jj new`)
///
/// For more information, see
/// https://martinvonz.github.io/jj/latest/working-copy/.
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct CheckoutArgs {
/// The revision to update to
revision: RevisionArg,
/// Ignored (but lets you pass `-r` for consistency with other commands)
#[arg(short = 'r', hide = true)]
unused_revision: bool,
/// The change description to use
#[arg(long = "message", short, value_name = "MESSAGE")]
message_paragraphs: Vec<String>,
}
#[instrument(skip_all)]
pub(crate) fn cmd_checkout(
ui: &mut Ui,
command: &CommandHelper,
args: &CheckoutArgs,
) -> Result<(), CommandError> {
writeln!(
ui.warning_default(),
"`jj checkout` is deprecated; use `jj new` instead, which is equivalent"
)?;
writeln!(
ui.warning_default(),
"`jj checkout` will be removed in a future version, and this will be a hard error"
)?;
let mut workspace_command = command.workspace_helper(ui)?;
let target = workspace_command.resolve_single_rev(ui, &args.revision)?;
let mut tx = workspace_command.start_transaction();
let commit_builder = tx
.repo_mut()
.new_commit(
command.settings(),
vec![target.id().clone()],
target.tree_id().clone(),
)
.set_description(join_message_paragraphs(&args.message_paragraphs));
let new_commit = commit_builder.write()?;
tx.edit(&new_commit).unwrap();
tx.finish(ui, format!("check out commit {}", target.id().hex()))?;
Ok(())
}

View File

@ -12,14 +12,17 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCompleter;
use jj_lib::backend::Signature; use jj_lib::backend::Signature;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::description_util::add_trailers;
use crate::description_util::description_template; use crate::description_util::description_template;
use crate::description_util::edit_description; use crate::description_util::edit_description;
use crate::description_util::join_message_paragraphs; use crate::description_util::join_message_paragraphs;
@ -28,7 +31,6 @@ use crate::ui::Ui;
/// Update the description and create a new change on top. /// Update the description and create a new change on top.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(visible_aliases=&["ci"])]
pub(crate) struct CommitArgs { pub(crate) struct CommitArgs {
/// Interactively choose which changes to include in the first commit /// Interactively choose which changes to include in the first commit
#[arg(short, long)] #[arg(short, long)]
@ -40,7 +42,11 @@ pub(crate) struct CommitArgs {
#[arg(long = "message", short, value_name = "MESSAGE")] #[arg(long = "message", short, value_name = "MESSAGE")]
message_paragraphs: Vec<String>, message_paragraphs: Vec<String>,
/// Put these paths in the first commit /// Put these paths in the first commit
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(
value_name = "FILESETS",
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::modified_files),
)]
paths: Vec<String>, paths: Vec<String>,
/// Reset the author to the configured user /// Reset the author to the configured user
/// ///
@ -82,6 +88,7 @@ pub(crate) fn cmd_commit(
let advanceable_bookmarks = workspace_command.get_advanceable_bookmarks(commit.parent_ids())?; let advanceable_bookmarks = workspace_command.get_advanceable_bookmarks(commit.parent_ids())?;
let diff_selector = let diff_selector =
workspace_command.diff_selector(ui, args.tool.as_deref(), args.interactive)?; workspace_command.diff_selector(ui, args.tool.as_deref(), args.interactive)?;
let text_editor = workspace_command.text_editor()?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let base_tree = commit.parent_tree(tx.repo())?; let base_tree = commit.parent_tree(tx.repo())?;
let format_instructions = || { let format_instructions = || {
@ -110,10 +117,7 @@ new working-copy commit.
)?; )?;
} }
let mut commit_builder = tx let mut commit_builder = tx.repo_mut().rewrite_commit(&commit).detach();
.repo_mut()
.rewrite_commit(command.settings(), &commit)
.detach();
commit_builder.set_tree_id(tree_id); commit_builder.set_tree_id(tree_id);
if args.reset_author { if args.reset_author {
commit_builder.set_author(commit_builder.committer().clone()); commit_builder.set_author(commit_builder.committer().clone());
@ -128,38 +132,38 @@ new working-copy commit.
} }
let description = if !args.message_paragraphs.is_empty() { let description = if !args.message_paragraphs.is_empty() {
join_message_paragraphs(&args.message_paragraphs) let mut description = join_message_paragraphs(&args.message_paragraphs);
} else { if !description.is_empty() {
if commit_builder.description().is_empty() { // The first trailer would become the first line of the description.
commit_builder.set_description(command.settings().default_description()); // Also, a commit with no description is treated in a special way in jujutsu: it
// can be discarded as soon as it's no longer the working copy. Adding a
// trailer to an empty description would break that logic.
commit_builder.set_description(description);
description = add_trailers(ui, &tx, &commit_builder)?;
} }
description
} else {
let description = add_trailers(ui, &tx, &commit_builder)?;
commit_builder.set_description(description);
let temp_commit = commit_builder.write_hidden()?; let temp_commit = commit_builder.write_hidden()?;
let template = description_template(ui, &tx, "", &temp_commit)?; let description = description_template(ui, &tx, "", &temp_commit)?;
edit_description( edit_description(&text_editor, &description)?
tx.base_workspace_helper().repo_path(),
&template,
command.settings(),
)?
}; };
commit_builder.set_description(description); commit_builder.set_description(description);
let new_commit = commit_builder.write(tx.repo_mut())?; let new_commit = commit_builder.write(tx.repo_mut())?;
let workspace_ids = tx.repo().view().workspaces_for_wc_commit_id(commit.id()); let workspace_names = tx.repo().view().workspaces_for_wc_commit_id(commit.id());
if !workspace_ids.is_empty() { if !workspace_names.is_empty() {
let new_wc_commit = tx let new_wc_commit = tx
.repo_mut() .repo_mut()
.new_commit( .new_commit(vec![new_commit.id().clone()], commit.tree_id().clone())
command.settings(),
vec![new_commit.id().clone()],
commit.tree_id().clone(),
)
.write()?; .write()?;
// Does nothing if there's no bookmarks to advance. // Does nothing if there's no bookmarks to advance.
tx.advance_bookmarks(advanceable_bookmarks, new_commit.id()); tx.advance_bookmarks(advanceable_bookmarks, new_commit.id());
for workspace_id in workspace_ids { for name in workspace_names {
tx.repo_mut().edit(workspace_id, &new_wc_commit).unwrap(); tx.repo_mut().edit(name, &new_wc_commit).unwrap();
} }
} }
tx.finish(ui, format!("commit {}", commit.id().hex()))?; tx.finish(ui, format!("commit {}", commit.id().hex()))?;

View File

@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use jj_lib::config::ConfigLayer;
use tracing::instrument; use tracing::instrument;
use super::ConfigLevelArgs; use super::ConfigLevelArgs;
use crate::cli_util::get_new_config_file_path;
use crate::cli_util::run_ui_editor;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::print_error_sources;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::ui::Ui; use crate::ui::Ui;
@ -33,10 +33,43 @@ pub struct ConfigEditArgs {
#[instrument(skip_all)] #[instrument(skip_all)]
pub fn cmd_config_edit( pub fn cmd_config_edit(
_ui: &mut Ui, ui: &mut Ui,
command: &CommandHelper, command: &CommandHelper,
args: &ConfigEditArgs, args: &ConfigEditArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.level.expect_source_kind(), command)?; let editor = command.text_editor()?;
run_ui_editor(command.settings(), &config_path) let file = args.level.edit_config_file(ui, command)?;
if !file.path().exists() {
file.save()?;
}
// Editing again and again until either of these conditions is met
// 1. The config is OK
// 2. The user restores previous one
loop {
editor.edit_file(file.path())?;
// Trying to load back config. If error, prompt to continue editing
if let Err(e) = ConfigLayer::load_from_file(file.layer().source, file.path().to_path_buf())
{
writeln!(
ui.warning_default(),
"An error has been found inside the config:"
)?;
print_error_sources(ui, Some(&e))?;
let continue_editing = ui.prompt_yes_no(
"Do you want to keep editing the file? If not, previous config will be restored.",
Some(true),
)?;
if !continue_editing {
// Saving back previous config
file.save()?;
break;
}
} else {
// config is OK
break;
}
}
Ok(())
} }

View File

@ -14,12 +14,14 @@
use std::io::Write as _; use std::io::Write as _;
use clap_complete::ArgValueCandidates;
use jj_lib::config::ConfigNamePathBuf;
use jj_lib::config::ConfigValue;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::config_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::config::ConfigNamePathBuf; use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Get the value of a given config option. /// Get the value of a given config option.
@ -34,7 +36,7 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)] #[command(verbatim_doc_comment)]
pub struct ConfigGetArgs { pub struct ConfigGetArgs {
#[arg(required = true)] #[arg(required = true, add = ArgValueCandidates::new(complete::leaf_config_keys))]
name: ConfigNamePathBuf, name: ConfigNamePathBuf,
} }
@ -44,34 +46,24 @@ pub fn cmd_config_get(
command: &CommandHelper, command: &CommandHelper,
args: &ConfigGetArgs, args: &ConfigGetArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let value = args let stringified = command
.name .settings()
.lookup_value(command.settings().config()) .get_value_with(&args.name, |value| match value {
.and_then(|value| value.into_string()) // Remove extra formatting from a string value
.map_err(|err| match err { ConfigValue::String(v) => Ok(v.into_value()),
config::ConfigError::Type { // Print other values in TOML syntax (but whitespace trimmed)
origin, ConfigValue::Integer(_)
unexpected, | ConfigValue::Float(_)
expected, | ConfigValue::Boolean(_)
key, | ConfigValue::Datetime(_) => Ok(value.decorated("", "").to_string()),
} => { // TODO: maybe okay to just print array or table in TOML syntax?
let expected = format!("a value convertible to {expected}"); ConfigValue::Array(_) => {
// Copied from `impl fmt::Display for ConfigError`. We can't use Err("Expected a value convertible to a string, but is an array")
// the `Display` impl directly because `expected` is required to }
// be a `'static str`. ConfigValue::InlineTable(_) => {
let mut buf = String::new(); Err("Expected a value convertible to a string, but is a table")
use std::fmt::Write;
write!(buf, "invalid type: {unexpected}, expected {expected}").unwrap();
if let Some(key) = key {
write!(buf, " for key `{key}`").unwrap();
}
if let Some(origin) = origin {
write!(buf, " in {origin}").unwrap();
}
config_error(buf)
} }
err => err.into(),
})?; })?;
writeln!(ui.stdout(), "{value}")?; writeln!(ui.stdout(), "{stringified}")?;
Ok(()) Ok(())
} }

View File

@ -12,25 +12,31 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use jj_lib::config::ConfigNamePathBuf;
use jj_lib::config::ConfigSource;
use jj_lib::settings::UserSettings;
use tracing::instrument; use tracing::instrument;
use super::ConfigLevelArgs; use super::ConfigLevelArgs;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::config::to_toml_value; use crate::complete;
use crate::config::resolved_config_values;
use crate::config::AnnotatedValue; use crate::config::AnnotatedValue;
use crate::config::ConfigNamePathBuf;
use crate::config::ConfigSource;
use crate::generic_templater::GenericTemplateLanguage; use crate::generic_templater::GenericTemplateLanguage;
use crate::template_builder::TemplateLanguage as _; use crate::generic_templater::GenericTemplatePropertyKind;
use crate::template_builder::CoreTemplatePropertyVar as _;
use crate::template_builder::TemplateLanguage;
use crate::templater::TemplatePropertyExt as _; use crate::templater::TemplatePropertyExt as _;
use crate::ui::Ui; use crate::ui::Ui;
/// List variables set in config file, along with their values. /// List variables set in config files, along with their values.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(mut_group("config_level", |g| g.required(false)))] #[command(mut_group("config_level", |g| g.required(false)))]
pub struct ConfigListArgs { pub struct ConfigListArgs {
/// An optional name of a specific config option to look up. /// An optional name of a specific config option to look up.
#[arg(add = ArgValueCandidates::new(complete::config_keys))]
pub name: Option<ConfigNamePathBuf>, pub name: Option<ConfigNamePathBuf>,
/// Whether to explicitly include built-in default values in the list. /// Whether to explicitly include built-in default values in the list.
#[arg(long, conflicts_with = "config_level")] #[arg(long, conflicts_with = "config_level")]
@ -40,17 +46,29 @@ pub struct ConfigListArgs {
pub include_overridden: bool, pub include_overridden: bool,
#[command(flatten)] #[command(flatten)]
pub level: ConfigLevelArgs, pub level: ConfigLevelArgs,
// TODO(#1047): Support --show-origin using LayeredConfigs.
/// Render each variable using the given template /// Render each variable using the given template
/// ///
/// The following keywords are defined: /// The following keywords are available in the template expression:
/// ///
/// * `name: String`: Config name. /// * `name: String`: Config name.
/// * `value: String`: Serialized value in TOML syntax. /// * `value: ConfigValue`: Value to be formatted in TOML syntax.
/// * `overridden: Boolean`: True if the value is shadowed by other. /// * `overridden: Boolean`: True if the value is shadowed by other.
/// * `source: String`: Source of the value.
/// * `path: String`: Path to the config file.
/// ///
/// For the syntax, see https://martinvonz.github.io/jj/latest/templates/ /// Can be overridden by the `templates.config_list` setting. To
#[arg(long, short = 'T', verbatim_doc_comment)] /// see a detailed config list, use the `builtin_config_list_detailed`
/// template.
///
/// See [`jj help -k templates`] for more information.
///
/// [`jj help -k templates`]:
/// https://jj-vcs.github.io/jj/latest/templates/
#[arg(
long, short = 'T',
verbatim_doc_comment,
add = ArgValueCandidates::new(complete::template_aliases)
)]
template: Option<String>, template: Option<String>,
} }
@ -61,45 +79,37 @@ pub fn cmd_config_list(
args: &ConfigListArgs, args: &ConfigListArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let template = { let template = {
let language = config_template_language(); let language = config_template_language(command.settings());
let text = match &args.template { let text = match &args.template {
Some(value) => value.to_owned(), Some(value) => value.to_owned(),
None => command None => command.settings().get_string("templates.config_list")?,
.settings()
.config()
.get_string("templates.config_list")?,
}; };
command command
.parse_template(ui, &language, &text, GenericTemplateLanguage::wrap_self)? .parse_template(ui, &language, &text, GenericTemplatePropertyKind::wrap_self)?
.labeled("config_list") .labeled("config_list")
}; };
ui.request_pager();
let mut formatter = ui.stdout_formatter();
let name_path = args.name.clone().unwrap_or_else(ConfigNamePathBuf::root); let name_path = args.name.clone().unwrap_or_else(ConfigNamePathBuf::root);
let mut wrote_values = false; let mut annotated_values = resolved_config_values(command.settings().config(), &name_path);
for annotated in command.resolved_config_values(&name_path)? { // The default layer could be excluded beforehand as layers[len..], but we
// Remove overridden values. // can't do the same for "annotated.source == target_source" in order for
if annotated.is_overridden && !args.include_overridden { // resolved_config_values() to mark values overridden by the upper layers.
continue; if let Some(target_source) = args.level.get_source_kind() {
} annotated_values.retain(|annotated| annotated.source == target_source);
} else if !args.include_defaults {
if let Some(target_source) = args.level.get_source_kind() { annotated_values.retain(|annotated| annotated.source != ConfigSource::Default);
if target_source != annotated.source {
continue;
}
}
// Skip built-ins if not included.
if !args.include_defaults && annotated.source == ConfigSource::Default {
continue;
}
template.format(&annotated, formatter.as_mut())?;
wrote_values = true;
} }
drop(formatter); if !args.include_overridden {
if !wrote_values { annotated_values.retain(|annotated| !annotated.is_overridden);
}
if !annotated_values.is_empty() {
ui.request_pager();
let mut formatter = ui.stdout_formatter();
for annotated in &annotated_values {
template.format(annotated, formatter.as_mut())?;
}
} else {
// Note to stderr explaining why output is empty. // Note to stderr explaining why output is empty.
if let Some(name) = &args.name { if let Some(name) = &args.name {
writeln!(ui.warning_default(), "No matching config key for {name}")?; writeln!(ui.warning_default(), "No matching config key for {name}")?;
@ -110,25 +120,39 @@ pub fn cmd_config_list(
Ok(()) Ok(())
} }
type ConfigTemplateLanguage = GenericTemplateLanguage<'static, AnnotatedValue>;
// AnnotatedValue will be cloned internally in the templater. If the cloning // AnnotatedValue will be cloned internally in the templater. If the cloning
// cost matters, wrap it with Rc. // cost matters, wrap it with Rc.
fn config_template_language() -> GenericTemplateLanguage<'static, AnnotatedValue> { fn config_template_language(settings: &UserSettings) -> ConfigTemplateLanguage {
type L = GenericTemplateLanguage<'static, AnnotatedValue>; type P = <ConfigTemplateLanguage as TemplateLanguage<'static>>::Property;
let mut language = L::new(); let mut language = ConfigTemplateLanguage::new(settings);
// "name" instead of "path" to avoid confusion with the source file path
language.add_keyword("name", |self_property| { language.add_keyword("name", |self_property| {
let out_property = self_property.map(|annotated| annotated.path.to_string()); let out_property = self_property.map(|annotated| annotated.name.to_string());
Ok(L::wrap_string(out_property)) Ok(P::wrap_string(out_property.into_dyn()))
}); });
language.add_keyword("value", |self_property| { language.add_keyword("value", |self_property| {
// TODO: would be nice if we can provide raw dynamically-typed value // .decorated("", "") to trim leading/trailing whitespace
let out_property = let out_property = self_property.map(|annotated| annotated.value.decorated("", ""));
self_property.and_then(|annotated| Ok(to_toml_value(&annotated.value)?.to_string())); Ok(P::wrap_config_value(out_property.into_dyn()))
Ok(L::wrap_string(out_property)) });
language.add_keyword("source", |self_property| {
let out_property = self_property.map(|annotated| annotated.source.to_string());
Ok(P::wrap_string(out_property.into_dyn()))
});
language.add_keyword("path", |self_property| {
let out_property = self_property.map(|annotated| {
// TODO: maybe add FilePath(PathBuf) template type?
annotated
.path
.as_ref()
.map_or_else(String::new, |path| path.to_string_lossy().into_owned())
});
Ok(P::wrap_string(out_property.into_dyn()))
}); });
language.add_keyword("overridden", |self_property| { language.add_keyword("overridden", |self_property| {
let out_property = self_property.map(|annotated| annotated.is_overridden); let out_property = self_property.map(|annotated| annotated.is_overridden);
Ok(L::wrap_boolean(out_property)) Ok(P::wrap_boolean(out_property.into_dyn()))
}); });
language language
} }

View File

@ -19,6 +19,11 @@ mod path;
mod set; mod set;
mod unset; mod unset;
use std::path::Path;
use itertools::Itertools as _;
use jj_lib::config::ConfigFile;
use jj_lib::config::ConfigSource;
use tracing::instrument; use tracing::instrument;
use self::edit::cmd_config_edit; use self::edit::cmd_config_edit;
@ -34,8 +39,9 @@ use self::set::ConfigSetArgs;
use self::unset::cmd_config_unset; use self::unset::cmd_config_unset;
use self::unset::ConfigUnsetArgs; use self::unset::ConfigUnsetArgs;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::config::ConfigSource; use crate::config::ConfigEnv;
use crate::ui::Ui; use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
@ -51,10 +57,6 @@ pub(crate) struct ConfigLevelArgs {
} }
impl ConfigLevelArgs { impl ConfigLevelArgs {
fn expect_source_kind(&self) -> ConfigSource {
self.get_source_kind().expect("No config_level provided")
}
fn get_source_kind(&self) -> Option<ConfigSource> { fn get_source_kind(&self) -> Option<ConfigSource> {
if self.user { if self.user {
Some(ConfigSource::User) Some(ConfigSource::User)
@ -64,6 +66,60 @@ impl ConfigLevelArgs {
None None
} }
} }
fn config_paths<'a>(&self, config_env: &'a ConfigEnv) -> Result<Vec<&'a Path>, CommandError> {
if self.user {
let paths = config_env.user_config_paths().collect_vec();
if paths.is_empty() {
return Err(user_error("No user config path found"));
}
Ok(paths)
} else if self.repo {
config_env
.repo_config_path()
.map(|p| vec![p])
.ok_or_else(|| user_error("No repo config path found"))
} else {
panic!("No config_level provided")
}
}
fn edit_config_file(
&self,
ui: &Ui,
command: &CommandHelper,
) -> Result<ConfigFile, CommandError> {
let config_env = command.config_env();
let config = command.raw_config();
let pick_one = |mut files: Vec<ConfigFile>, not_found_error: &str| {
if files.len() > 1 {
let mut choices = vec![];
let mut formatter = ui.stderr_formatter();
for (i, file) in files.iter().enumerate() {
writeln!(formatter, "{}: {}", i + 1, file.path().display())?;
choices.push((i + 1).to_string());
}
drop(formatter);
let index =
ui.prompt_choice("Choose a config file (default 1)", &choices, Some(0))?;
return Ok(files[index].clone());
}
files.pop().ok_or_else(|| user_error(not_found_error))
};
if self.user {
pick_one(
config_env.user_config_files(config)?,
"No user config path found to edit",
)
} else if self.repo {
pick_one(
config_env.repo_config_files(config)?,
"No repo config path found to edit",
)
} else {
panic!("No config_level provided")
}
}
} }
/// Manage config options /// Manage config options
@ -71,8 +127,11 @@ impl ConfigLevelArgs {
/// Operates on jj configuration, which comes from the config file and /// Operates on jj configuration, which comes from the config file and
/// environment variables. /// environment variables.
/// ///
/// For file locations, supported config options, and other details about jj /// See [`jj help -k config`] to know more about file locations, supported
/// config, see https://martinvonz.github.io/jj/latest/config/. /// config options, and other details about `jj config`.
///
/// [`jj help -k config`]:
/// https://jj-vcs.github.io/jj/latest/config/
#[derive(clap::Subcommand, Clone, Debug)] #[derive(clap::Subcommand, Clone, Debug)]
pub(crate) enum ConfigCommand { pub(crate) enum ConfigCommand {
#[command(visible_alias("e"))] #[command(visible_alias("e"))]

View File

@ -17,17 +17,16 @@ use std::io::Write as _;
use tracing::instrument; use tracing::instrument;
use super::ConfigLevelArgs; use super::ConfigLevelArgs;
use crate::cli_util::get_new_config_file_path;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::ui::Ui; use crate::ui::Ui;
/// Print the path to the config file /// Print the paths to the config files
/// ///
/// A config file at that path may or may not exist. /// A config file at that path may or may not exist.
/// ///
/// See `jj config edit` if you'd like to immediately edit the file. /// See `jj config edit` if you'd like to immediately edit a file.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct ConfigPathArgs { pub struct ConfigPathArgs {
#[command(flatten)] #[command(flatten)]
@ -40,13 +39,14 @@ pub fn cmd_config_path(
command: &CommandHelper, command: &CommandHelper,
args: &ConfigPathArgs, args: &ConfigPathArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.level.expect_source_kind(), command)?; for config_path in args.level.config_paths(command.config_env())? {
writeln!( writeln!(
ui.stdout(), ui.stdout(),
"{}", "{}",
config_path config_path
.to_str() .to_str()
.ok_or_else(|| user_error("The config path is not valid UTF-8"))? .ok_or_else(|| user_error("The config path is not valid UTF-8"))?
)?; )?;
}
Ok(()) Ok(())
} }

View File

@ -14,28 +14,41 @@
use std::io; use std::io;
use clap_complete::ArgValueCandidates;
use jj_lib::commit::Commit; use jj_lib::commit::Commit;
use jj_lib::repo::Repo; use jj_lib::config::ConfigNamePathBuf;
use jj_lib::config::ConfigValue;
use jj_lib::repo::Repo as _;
use tracing::instrument; use tracing::instrument;
use super::ConfigLevelArgs; use super::ConfigLevelArgs;
use crate::cli_util::get_new_config_file_path;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper; use crate::cli_util::WorkspaceCommandHelper;
use crate::command_error::user_error; use crate::command_error::user_error_with_message;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::config::parse_toml_value_or_bare_string; use crate::complete;
use crate::config::write_config_value_to_file; use crate::config::parse_value_or_bare_string;
use crate::config::ConfigNamePathBuf;
use crate::ui::Ui; use crate::ui::Ui;
/// Update config file to set the given option to a given value. /// Update a config file to set the given option to a given value.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct ConfigSetArgs { pub struct ConfigSetArgs {
#[arg(required = true)] #[arg(required = true, add = ArgValueCandidates::new(complete::leaf_config_keys))]
name: ConfigNamePathBuf, name: ConfigNamePathBuf,
#[arg(required = true)] /// New value to set
value: String, ///
/// The value should be specified as a TOML expression. If string value
/// isn't enclosed by any TOML constructs (such as apostrophes or array
/// notation), quotes can be omitted. Note that the value may also need
/// shell quoting. TOML multi-line strings can be useful if the value
/// contains apostrophes. For example, to set `foo.bar` to the string
/// "{don't}" use `jj config set --user foo.bar "'''{don't}'''"`. This is
/// valid in both Bash and Fish.
///
/// Alternative, e.g. to avoid dealing with shell quoting, use `jj config
/// edit` to edit the TOML file directly.
#[arg(required = true, value_parser = parse_value_or_bare_string)]
value: ConfigValue,
#[command(flatten)] #[command(flatten)]
level: ConfigLevelArgs, level: ConfigLevelArgs,
} }
@ -52,26 +65,20 @@ pub fn cmd_config_set(
command: &CommandHelper, command: &CommandHelper,
args: &ConfigSetArgs, args: &ConfigSetArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.level.expect_source_kind(), command)?; let mut file = args.level.edit_config_file(ui, command)?;
if config_path.is_dir() {
return Err(user_error(format!(
"Can't set config in path {path} (dirs not supported)",
path = config_path.display()
)));
}
// TODO(#531): Infer types based on schema (w/ --type arg to override).
let value = parse_toml_value_or_bare_string(&args.value);
// If the user is trying to change the author config, we should warn them that // If the user is trying to change the author config, we should warn them that
// it won't affect the working copy author // it won't affect the working copy author
if args.name == ConfigNamePathBuf::from_iter(vec!["user", "name"]) { if args.name == ConfigNamePathBuf::from_iter(vec!["user", "name"]) {
check_wc_author(ui, command, &value, AuthorChange::Name)?; check_wc_author(ui, command, &args.value, AuthorChange::Name)?;
} else if args.name == ConfigNamePathBuf::from_iter(vec!["user", "email"]) { } else if args.name == ConfigNamePathBuf::from_iter(vec!["user", "email"]) {
check_wc_author(ui, command, &value, AuthorChange::Email)?; check_wc_author(ui, command, &args.value, AuthorChange::Email)?;
}; };
write_config_value_to_file(&args.name, value, &config_path) file.set_value(&args.name, &args.value)
.map_err(|err| user_error_with_message(format!("Failed to set {}", args.name), err))?;
file.save()?;
Ok(())
} }
/// Returns the commit of the working copy if it exists. /// Returns the commit of the working copy if it exists.

View File

@ -12,21 +12,22 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use jj_lib::config::ConfigNamePathBuf;
use tracing::instrument; use tracing::instrument;
use super::ConfigLevelArgs; use super::ConfigLevelArgs;
use crate::cli_util::get_new_config_file_path;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::user_error_with_message;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::config::remove_config_value_from_file; use crate::complete;
use crate::config::ConfigNamePathBuf;
use crate::ui::Ui; use crate::ui::Ui;
/// Update config file to unset the given option. /// Update a config file to unset the given option.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct ConfigUnsetArgs { pub struct ConfigUnsetArgs {
#[arg(required = true)] #[arg(required = true, add = ArgValueCandidates::new(complete::leaf_config_keys))]
name: ConfigNamePathBuf, name: ConfigNamePathBuf,
#[command(flatten)] #[command(flatten)]
level: ConfigLevelArgs, level: ConfigLevelArgs,
@ -34,17 +35,17 @@ pub struct ConfigUnsetArgs {
#[instrument(skip_all)] #[instrument(skip_all)]
pub fn cmd_config_unset( pub fn cmd_config_unset(
_ui: &mut Ui, ui: &mut Ui,
command: &CommandHelper, command: &CommandHelper,
args: &ConfigUnsetArgs, args: &ConfigUnsetArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let config_path = get_new_config_file_path(&args.level.expect_source_kind(), command)?; let mut file = args.level.edit_config_file(ui, command)?;
if config_path.is_dir() { let old_value = file
return Err(user_error(format!( .delete_value(&args.name)
"Can't set config in path {path} (dirs not supported)", .map_err(|err| user_error_with_message(format!("Failed to unset {}", args.name), err))?;
path = config_path.display() if old_value.is_none() {
))); return Err(user_error(format!(r#""{}" doesn't exist"#, args.name)));
} }
file.save()?;
remove_config_value_from_file(&args.name, &config_path) Ok(())
} }

View File

@ -16,19 +16,20 @@ use std::fmt::Debug;
use std::io::Write as _; use std::io::Write as _;
use futures::executor::block_on_stream; use futures::executor::block_on_stream;
use jj_lib::backend::Backend;
use jj_lib::backend::CopyRecord; use jj_lib::backend::CopyRecord;
use jj_lib::repo::Repo as _;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::ui::Ui; use crate::ui::Ui;
/// Rebuild commit index /// Show information about file copies detected
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct CopyDetectionArgs { pub struct CopyDetectionArgs {
/// Show changes in this revision, compared to its parent(s) /// Show file copies detected in changed files in this revision, compared to
#[arg(default_value = "@")] /// its parent(s)
#[arg(default_value = "@", value_name = "REVSET")]
revision: RevisionArg, revision: RevisionArg,
} }
@ -38,14 +39,12 @@ pub fn cmd_debug_copy_detection(
args: &CopyDetectionArgs, args: &CopyDetectionArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let ws = command.workspace_helper(ui)?; let ws = command.workspace_helper(ui)?;
let Some(git) = ws.git_backend() else { let store = ws.repo().store();
writeln!(ui.stderr(), "Not a git backend.")?;
return Ok(());
};
let commit = ws.resolve_single_rev(ui, &args.revision)?; let commit = ws.resolve_single_rev(ui, &args.revision)?;
for parent_id in commit.parent_ids() { for parent_id in commit.parent_ids() {
for CopyRecord { target, source, .. } in for CopyRecord { target, source, .. } in
block_on_stream(git.get_copy_records(None, parent_id, commit.id())?) block_on_stream(store.get_copy_records(None, parent_id, commit.id())?)
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
{ {
writeln!( writeln!(

View File

@ -12,46 +12,38 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io::Write as _;
use clap::ArgGroup;
use jj_lib::file_util; use jj_lib::file_util;
use jj_lib::workspace::Workspace; use jj_lib::workspace::Workspace;
use tracing::instrument; use tracing::instrument;
use super::git;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::cli_error; use crate::command_error::cli_error;
use crate::command_error::user_error_with_hint;
use crate::command_error::user_error_with_message; use crate::command_error::user_error_with_message;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::ui::Ui; use crate::ui::Ui;
/// Create a new repo in the given directory /// Create a new repo in the given directory using the proof-of-concept simple
/// backend
/// ///
/// If the given directory does not exist, it will be created. If no directory /// The simple backend does not support cloning, fetching, or pushing.
/// is given, the current directory is used. ///
/// This command is otherwise analogous to `jj git init`. If the given directory
/// does not exist, it will be created. If no directory is given, the current
/// directory is used.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(group(ArgGroup::new("backend").args(&["git", "git_repo"])))] pub(crate) struct DebugInitSimpleArgs {
pub(crate) struct InitArgs {
/// The destination directory /// The destination directory
#[arg(default_value = ".", value_hint = clap::ValueHint::DirPath)] #[arg(default_value = ".", value_hint = clap::ValueHint::DirPath)]
destination: String, destination: String,
/// DEPRECATED: Use `jj git init`
/// Use the Git backend, creating a jj repo backed by a Git repo
#[arg(long, hide = true)]
git: bool,
/// DEPRECATED: Use `jj git init`
/// Path to a git repo the jj repo will be backed by
#[arg(long, hide = true, value_hint = clap::ValueHint::DirPath)]
git_repo: Option<String>,
} }
#[instrument(skip_all)] #[instrument(skip_all)]
pub(crate) fn cmd_init( pub(crate) fn cmd_debug_init_simple(
ui: &mut Ui, ui: &mut Ui,
command: &CommandHelper, command: &CommandHelper,
args: &InitArgs, args: &DebugInitSimpleArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
if command.global_args().ignore_working_copy { if command.global_args().ignore_working_copy {
return Err(cli_error("--ignore-working-copy is not respected")); return Err(cli_error("--ignore-working-copy is not respected"));
@ -62,29 +54,10 @@ pub(crate) fn cmd_init(
let cwd = command.cwd(); let cwd = command.cwd();
let wc_path = cwd.join(&args.destination); let wc_path = cwd.join(&args.destination);
let wc_path = file_util::create_or_reuse_dir(&wc_path) let wc_path = file_util::create_or_reuse_dir(&wc_path)
.and_then(|_| wc_path.canonicalize()) .and_then(|_| dunce::canonicalize(wc_path))
.map_err(|e| user_error_with_message("Failed to create workspace", e))?; .map_err(|e| user_error_with_message("Failed to create workspace", e))?;
// Preserve existing behaviour where `jj init` is not able to create Workspace::init_simple(&command.settings_for_new_workspace(&wc_path)?, &wc_path)?;
// a colocated repo.
let colocate = false;
if args.git || args.git_repo.is_some() {
git::init::do_init(ui, command, &wc_path, colocate, args.git_repo.as_deref())?;
writeln!(
ui.warning_default(),
"`--git` and `--git-repo` are deprecated.
Use `jj git init` instead"
)?;
} else {
if !command.settings().allow_native_backend() {
return Err(user_error_with_hint(
"The native backend is disallowed by default.",
"Did you mean to call `jj git init`?
Set `ui.allow-init-native` to allow initializing a repo with the native backend.",
));
}
Workspace::init_local(command.settings(), &wc_path)?;
}
let relative_wc_path = file_util::relative_path(cwd, &wc_path); let relative_wc_path = file_util::relative_path(cwd, &wc_path);
writeln!( writeln!(

View File

@ -15,7 +15,7 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Write as _; use std::io::Write as _;
use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopy as _;
use super::check_local_disk_wc; use super::check_local_disk_wc;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
@ -40,10 +40,11 @@ pub fn cmd_debug_local_working_copy(
for (file, state) in wc.file_states()? { for (file, state) in wc.file_states()? {
writeln!( writeln!(
ui.stdout(), ui.stdout(),
"{:?} {:13?} {:10?} {:?}", "{:?} {:13?} {:10?} {:?} {:?}",
state.file_type, state.file_type,
state.size, state.size,
state.mtime.0, state.mtime.0,
state.materialized_conflict_data,
file file
)?; )?;
} }

View File

@ -12,18 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
pub mod copy_detection; mod copy_detection;
pub mod fileset; mod fileset;
pub mod index; mod index;
pub mod local_working_copy; mod init_simple;
pub mod operation; mod local_working_copy;
pub mod reindex; mod operation;
pub mod revset; mod reindex;
pub mod snapshot; mod revset;
pub mod template; mod snapshot;
pub mod tree; mod template;
pub mod watchman; mod tree;
pub mod working_copy; mod watchman;
mod working_copy;
use std::any::Any; use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
@ -37,6 +38,8 @@ use self::fileset::cmd_debug_fileset;
use self::fileset::DebugFilesetArgs; use self::fileset::DebugFilesetArgs;
use self::index::cmd_debug_index; use self::index::cmd_debug_index;
use self::index::DebugIndexArgs; use self::index::DebugIndexArgs;
use self::init_simple::cmd_debug_init_simple;
use self::init_simple::DebugInitSimpleArgs;
use self::local_working_copy::cmd_debug_local_working_copy; use self::local_working_copy::cmd_debug_local_working_copy;
use self::local_working_copy::DebugLocalWorkingCopyArgs; use self::local_working_copy::DebugLocalWorkingCopyArgs;
use self::operation::cmd_debug_operation; use self::operation::cmd_debug_operation;
@ -67,6 +70,7 @@ pub enum DebugCommand {
CopyDetection(CopyDetectionArgs), CopyDetection(CopyDetectionArgs),
Fileset(DebugFilesetArgs), Fileset(DebugFilesetArgs),
Index(DebugIndexArgs), Index(DebugIndexArgs),
InitSimple(DebugInitSimpleArgs),
LocalWorkingCopy(DebugLocalWorkingCopyArgs), LocalWorkingCopy(DebugLocalWorkingCopyArgs),
#[command(visible_alias = "view")] #[command(visible_alias = "view")]
Operation(DebugOperationArgs), Operation(DebugOperationArgs),
@ -86,12 +90,13 @@ pub fn cmd_debug(
subcommand: &DebugCommand, subcommand: &DebugCommand,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
match subcommand { match subcommand {
DebugCommand::CopyDetection(args) => cmd_debug_copy_detection(ui, command, args),
DebugCommand::Fileset(args) => cmd_debug_fileset(ui, command, args), DebugCommand::Fileset(args) => cmd_debug_fileset(ui, command, args),
DebugCommand::Index(args) => cmd_debug_index(ui, command, args), DebugCommand::Index(args) => cmd_debug_index(ui, command, args),
DebugCommand::InitSimple(args) => cmd_debug_init_simple(ui, command, args),
DebugCommand::LocalWorkingCopy(args) => cmd_debug_local_working_copy(ui, command, args), DebugCommand::LocalWorkingCopy(args) => cmd_debug_local_working_copy(ui, command, args),
DebugCommand::Operation(args) => cmd_debug_operation(ui, command, args), DebugCommand::Operation(args) => cmd_debug_operation(ui, command, args),
DebugCommand::Reindex(args) => cmd_debug_reindex(ui, command, args), DebugCommand::Reindex(args) => cmd_debug_reindex(ui, command, args),
DebugCommand::CopyDetection(args) => cmd_debug_copy_detection(ui, command, args),
DebugCommand::Revset(args) => cmd_debug_revset(ui, command, args), DebugCommand::Revset(args) => cmd_debug_revset(ui, command, args),
DebugCommand::Snapshot(args) => cmd_debug_snapshot(ui, command, args), DebugCommand::Snapshot(args) => cmd_debug_snapshot(ui, command, args),
DebugCommand::Template(args) => cmd_debug_template(ui, command, args), DebugCommand::Template(args) => cmd_debug_template(ui, command, args),

View File

@ -15,17 +15,19 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Write as _; use std::io::Write as _;
use jj_lib::object_id::ObjectId; use clap_complete::ArgValueCandidates;
use jj_lib::object_id::ObjectId as _;
use jj_lib::op_walk; use jj_lib::op_walk;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Show information about an operation and its view /// Show information about an operation and its view
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct DebugOperationArgs { pub struct DebugOperationArgs {
#[arg(default_value = "@")] #[arg(default_value = "@", add = ArgValueCandidates::new(complete::operations))]
operation: String, operation: String,
#[arg(long, value_enum, default_value = "all")] #[arg(long, value_enum, default_value = "all")]
display: OperationDisplay, display: OperationDisplay,

View File

@ -15,7 +15,7 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Write as _; use std::io::Write as _;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use jj_lib::revset; use jj_lib::revset;
use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetDiagnostics;
@ -37,7 +37,7 @@ pub fn cmd_debug_revset(
args: &DebugRevsetArgs, args: &DebugRevsetArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let workspace_ctx = workspace_command.revset_parse_context(); let workspace_ctx = workspace_command.env().revset_parse_context();
let repo = workspace_command.repo().as_ref(); let repo = workspace_command.repo().as_ref();
let mut diagnostics = RevsetDiagnostics::new(); let mut diagnostics = RevsetDiagnostics::new();
@ -47,11 +47,6 @@ pub fn cmd_debug_revset(
writeln!(ui.stdout(), "{expression:#?}")?; writeln!(ui.stdout(), "{expression:#?}")?;
writeln!(ui.stdout())?; writeln!(ui.stdout())?;
let expression = revset::optimize(expression);
writeln!(ui.stdout(), "-- Optimized:")?;
writeln!(ui.stdout(), "{expression:#?}")?;
writeln!(ui.stdout())?;
let symbol_resolver = revset_util::default_symbol_resolver( let symbol_resolver = revset_util::default_symbol_resolver(
repo, repo,
command.revset_extensions().symbol_resolvers(), command.revset_extensions().symbol_resolvers(),
@ -62,7 +57,17 @@ pub fn cmd_debug_revset(
writeln!(ui.stdout(), "{expression:#?}")?; writeln!(ui.stdout(), "{expression:#?}")?;
writeln!(ui.stdout())?; writeln!(ui.stdout())?;
let revset = expression.evaluate(repo)?; let expression = revset::optimize(expression);
writeln!(ui.stdout(), "-- Optimized:")?;
writeln!(ui.stdout(), "{expression:#?}")?;
writeln!(ui.stdout())?;
let backend_expression = expression.to_backend_expression(repo);
writeln!(ui.stdout(), "-- Backend:")?;
writeln!(ui.stdout(), "{backend_expression:#?}")?;
writeln!(ui.stdout())?;
let revset = expression.evaluate_unoptimized(repo)?;
writeln!(ui.stdout(), "-- Evaluated:")?; writeln!(ui.stdout(), "-- Evaluated:")?;
writeln!(ui.stdout(), "{revset:#?}")?; writeln!(ui.stdout(), "{revset:#?}")?;
writeln!(ui.stdout())?; writeln!(ui.stdout())?;

View File

@ -17,7 +17,7 @@ use std::io::Write as _;
use jj_lib::backend::TreeId; use jj_lib::backend::TreeId;
use jj_lib::merged_tree::MergedTree; use jj_lib::merged_tree::MergedTree;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use jj_lib::repo_path::RepoPathBuf; use jj_lib::repo_path::RepoPathBuf;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
@ -29,12 +29,13 @@ use crate::ui::Ui;
/// List the recursive entries of a tree. /// List the recursive entries of a tree.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct DebugTreeArgs { pub struct DebugTreeArgs {
#[arg(long, short = 'r')] #[arg(long, short = 'r', value_name = "REVSET")]
revision: Option<RevisionArg>, revision: Option<RevisionArg>,
#[arg(long, conflicts_with = "revision")] #[arg(long, conflicts_with = "revision")]
id: Option<String>, id: Option<String>,
#[arg(long, requires = "id")] #[arg(long, requires = "id")]
dir: Option<String>, dir: Option<String>,
#[arg(value_name = "FILESETS")]
paths: Vec<String>, paths: Vec<String>,
// TODO: Add an option to include trees that are ancestors of the matched paths // TODO: Add an option to include trees that are ancestors of the matched paths
} }
@ -54,7 +55,7 @@ pub fn cmd_debug_tree(
RepoPathBuf::root() RepoPathBuf::root()
}; };
let store = workspace_command.repo().store(); let store = workspace_command.repo().store();
let tree = store.get_tree(&dir, &tree_id)?; let tree = store.get_tree(dir, &tree_id)?;
MergedTree::resolved(tree) MergedTree::resolved(tree)
} else { } else {
let commit = workspace_command let commit = workspace_command

View File

@ -50,15 +50,19 @@ pub fn cmd_debug_watchman(
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo().clone(); let repo = workspace_command.repo().clone();
let watchman_config = WatchmanConfig {
// The value is likely irrelevant here. TODO(ilyagr): confirm
register_trigger: false,
};
match subcommand { match subcommand {
DebugWatchmanCommand::Status => { DebugWatchmanCommand::Status => {
// TODO(ilyagr): It would be nice to add colors here // TODO(ilyagr): It would be nice to add colors here
let config = match command.settings().fsmonitor_settings()? { let config = match workspace_command.settings().fsmonitor_settings()? {
FsmonitorSettings::Watchman(config) => { FsmonitorSettings::Watchman(config) => {
writeln!(ui.stdout(), "Watchman is enabled via `core.fsmonitor`.")?; writeln!(ui.stdout(), "Watchman is enabled via `core.fsmonitor`.")?;
writeln!( writeln!(
ui.stdout(), ui.stdout(),
r"Background snapshotting is {}. Use `core.watchman.register_snapshot_trigger` to control it.", r"Background snapshotting is {}. Use `core.watchman.register-snapshot-trigger` to control it.",
if config.register_trigger { if config.register_trigger {
"enabled" "enabled"
} else { } else {
@ -76,7 +80,7 @@ pub fn cmd_debug_watchman(
ui.stdout(), ui.stdout(),
"Attempting to contact the `watchman` CLI regardless..." "Attempting to contact the `watchman` CLI regardless..."
)?; )?;
WatchmanConfig::default() watchman_config
} }
other_fsmonitor => { other_fsmonitor => {
return Err(user_error(format!( return Err(user_error(format!(
@ -102,12 +106,12 @@ pub fn cmd_debug_watchman(
} }
DebugWatchmanCommand::QueryClock => { DebugWatchmanCommand::QueryClock => {
let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?;
let (clock, _changed_files) = wc.query_watchman(&WatchmanConfig::default())?; let (clock, _changed_files) = wc.query_watchman(&watchman_config)?;
writeln!(ui.stdout(), "Clock: {clock:?}")?; writeln!(ui.stdout(), "Clock: {clock:?}")?;
} }
DebugWatchmanCommand::QueryChangedFiles => { DebugWatchmanCommand::QueryChangedFiles => {
let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?;
let (_clock, changed_files) = wc.query_watchman(&WatchmanConfig::default())?; let (_clock, changed_files) = wc.query_watchman(&watchman_config)?;
writeln!(ui.stdout(), "Changed files: {changed_files:?}")?; writeln!(ui.stdout(), "Changed files: {changed_files:?}")?;
} }
DebugWatchmanCommand::ResetClock => { DebugWatchmanCommand::ResetClock => {

View File

@ -14,22 +14,27 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
use std::io::Read; use std::io::Read as _;
use std::iter;
use itertools::Itertools; use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::backend::Signature; use jj_lib::backend::Signature;
use jj_lib::commit::CommitIteratorExt; use jj_lib::commit::CommitIteratorExt as _;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::description_util::add_trailers_with_template;
use crate::description_util::description_template; use crate::description_util::description_template;
use crate::description_util::edit_description; use crate::description_util::edit_description;
use crate::description_util::edit_multiple_descriptions; use crate::description_util::edit_multiple_descriptions;
use crate::description_util::join_message_paragraphs; use crate::description_util::join_message_paragraphs;
use crate::description_util::parse_trailers_template;
use crate::description_util::ParsedBulkEditMessage; use crate::description_util::ParsedBulkEditMessage;
use crate::text_util::parse_author; use crate::text_util::parse_author;
use crate::ui::Ui; use crate::ui::Ui;
@ -41,17 +46,29 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(visible_aliases = &["desc"])] #[command(visible_aliases = &["desc"])]
pub(crate) struct DescribeArgs { pub(crate) struct DescribeArgs {
/// The revision(s) whose description to edit /// The revision(s) whose description to edit (default: @)
#[arg(default_value = "@")] #[arg(
revisions: Vec<RevisionArg>, value_name = "REVSETS",
/// Ignored (but lets you pass `-r` for consistency with other commands) add = ArgValueCompleter::new(complete::revset_expression_mutable)
#[arg(short = 'r', hide = true, action = clap::ArgAction::Count)] )]
unused_revision: u8, revisions_pos: Vec<RevisionArg>,
#[arg(
short = 'r',
hide = true,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable)
)]
revisions_opt: Vec<RevisionArg>,
/// The change description to use (don't open editor) /// The change description to use (don't open editor)
/// ///
/// If multiple revisions are specified, the same description will be used /// If multiple revisions are specified, the same description will be used
/// for all of them. /// for all of them.
#[arg(long = "message", short, value_name = "MESSAGE")] #[arg(
long = "message",
short,
value_name = "MESSAGE",
conflicts_with = "stdin"
)]
message_paragraphs: Vec<String>, message_paragraphs: Vec<String>,
/// Read the change description from stdin /// Read the change description from stdin
/// ///
@ -62,8 +79,14 @@ pub(crate) struct DescribeArgs {
/// Don't open an editor /// Don't open an editor
/// ///
/// This is mainly useful in combination with e.g. `--reset-author`. /// This is mainly useful in combination with e.g. `--reset-author`.
#[arg(long)] #[arg(long, conflicts_with = "edit")]
no_edit: bool, no_edit: bool,
/// Open an editor
///
/// Forces an editor to open when using `--stdin` or `--message` to
/// allow the message to be edited afterwards.
#[arg(long)]
edit: bool,
/// Reset the author to the configured user /// Reset the author to the configured user
/// ///
/// This resets the author name, email, and timestamp. /// This resets the author name, email, and timestamp.
@ -93,15 +116,20 @@ pub(crate) fn cmd_describe(
args: &DescribeArgs, args: &DescribeArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let commits: Vec<_> = workspace_command let commits: Vec<_> = if !args.revisions_pos.is_empty() || !args.revisions_opt.is_empty() {
.parse_union_revsets(ui, &args.revisions)? workspace_command
.evaluate_to_commits()? .parse_union_revsets(ui, &[&*args.revisions_pos, &*args.revisions_opt].concat())?
.try_collect()?; // in reverse topological order } else {
workspace_command.parse_revset(ui, &RevisionArg::AT)?
}
.evaluate_to_commits()?
.try_collect()?; // in reverse topological order
if commits.is_empty() { if commits.is_empty() {
writeln!(ui.status(), "No revisions to describe.")?; writeln!(ui.status(), "No revisions to describe.")?;
return Ok(()); return Ok(());
} }
workspace_command.check_rewritable(commits.iter().ids())?; workspace_command.check_rewritable(commits.iter().ids())?;
let text_editor = workspace_command.text_editor()?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let tx_description = if commits.len() == 1 { let tx_description = if commits.len() == 1 {
@ -124,62 +152,70 @@ pub(crate) fn cmd_describe(
None None
}; };
let commit_descriptions: Vec<(_, _)> = if args.no_edit || shared_description.is_some() { // edit and no_edit are conflicting arguments and therefore it should not
commits // be possible for both to be true at the same time.
.iter() assert!(!(args.edit && args.no_edit));
.map(|commit| { let use_editor = args.edit || (shared_description.is_none() && !args.no_edit);
let new_description = shared_description
.as_deref() let mut commit_builders = commits
.unwrap_or_else(|| commit.description()); .iter()
(commit, new_description.to_owned()) .map(|commit| {
}) let mut commit_builder = tx.repo_mut().rewrite_commit(commit).detach();
.collect() if let Some(description) = &shared_description {
} else { commit_builder.set_description(description);
let temp_commits: Vec<(_, _)> = commits }
.iter() if args.reset_author {
let new_author = commit_builder.committer().clone();
commit_builder.set_author(new_author);
}
if let Some((name, email)) = args.author.clone() {
let new_author = Signature {
name,
email,
timestamp: commit_builder.author().timestamp,
};
commit_builder.set_author(new_author);
}
commit_builder
})
.collect_vec();
if let Some(trailer_template) = parse_trailers_template(ui, &tx)? {
for commit_builder in &mut commit_builders {
// The first trailer would become the first line of the description.
// Also, a commit with no description is treated in a special way in jujutsu: it
// can be discarded as soon as it's no longer the working copy. Adding a
// trailer to an empty description would break that logic.
if use_editor || !commit_builder.description().is_empty() {
let temp_commit = commit_builder.write_hidden()?;
let new_description = add_trailers_with_template(&trailer_template, &temp_commit)?;
commit_builder.set_description(new_description);
}
}
}
if use_editor {
let temp_commits: Vec<_> = iter::zip(&commits, &commit_builders)
// Edit descriptions in topological order // Edit descriptions in topological order
.rev() .rev()
.map(|commit| -> Result<_, CommandError> { .map(|(commit, commit_builder)| {
let mut commit_builder = tx commit_builder
.repo_mut() .write_hidden()
.rewrite_commit(command.settings(), commit) .map(|temp_commit| (commit.id(), temp_commit))
.detach();
if commit_builder.description().is_empty() {
commit_builder.set_description(command.settings().default_description());
}
if args.reset_author {
let new_author = commit_builder.committer().clone();
commit_builder.set_author(new_author);
}
if let Some((name, email)) = args.author.clone() {
let new_author = Signature {
name,
email,
timestamp: commit_builder.author().timestamp,
};
commit_builder.set_author(new_author);
}
let temp_commit = commit_builder.write_hidden()?;
Ok((commit.id(), temp_commit))
}) })
.try_collect()?; .try_collect()?;
if let [(_, temp_commit)] = &*temp_commits { if let [(_, temp_commit)] = &*temp_commits {
let template = description_template(ui, &tx, "", temp_commit)?; let template = description_template(ui, &tx, "", temp_commit)?;
let description = edit_description( let description = edit_description(&text_editor, &template)?;
tx.base_workspace_helper().repo_path(), commit_builders[0].set_description(description);
&template,
command.settings(),
)?;
vec![(&commits[0], description)]
} else { } else {
let ParsedBulkEditMessage { let ParsedBulkEditMessage {
descriptions, descriptions,
missing, missing,
duplicates, duplicates,
unexpected, unexpected,
} = edit_multiple_descriptions(ui, &tx, &temp_commits, command.settings())?; } = edit_multiple_descriptions(ui, &text_editor, &tx, &temp_commits)?;
if !missing.is_empty() { if !missing.is_empty() {
return Err(user_error(format!( return Err(user_error(format!(
"The description for the following commits were not found in the edited \ "The description for the following commits were not found in the edited \
@ -201,75 +237,59 @@ pub(crate) fn cmd_describe(
))); )));
} }
let commit_descriptions = commits for (commit, commit_builder) in iter::zip(&commits, &mut commit_builders) {
.iter() let description = descriptions.get(commit.id()).unwrap();
.map(|commit| { commit_builder.set_description(description);
let description = descriptions.get(commit.id()).unwrap().to_owned(); }
(commit, description)
})
.collect();
commit_descriptions
} }
}; };
// Filter out unchanged commits to avoid rebasing descendants in // Filter out unchanged commits to avoid rebasing descendants in
// `transform_descendants` below unnecessarily. // `transform_descendants` below unnecessarily.
let commit_descriptions: HashMap<_, _> = commit_descriptions let commit_builders: HashMap<_, _> = iter::zip(&commits, commit_builders)
.into_iter() .filter(|(old_commit, commit_builder)| {
.filter(|(commit, new_description)| { old_commit.description() != commit_builder.description()
new_description != commit.description()
|| args.reset_author || args.reset_author
|| args.author.as_ref().is_some_and(|(name, email)| { // Ignore author timestamp which could be updated if the old
name != &commit.author().name || email != &commit.author().email // commit was discardable.
}) || old_commit.author().name != commit_builder.author().name
|| old_commit.author().email != commit_builder.author().email
}) })
.map(|(commit, new_description)| (commit.id(), new_description)) .map(|(old_commit, commit_builder)| (old_commit.id(), commit_builder))
.collect(); .collect();
let mut num_described = 0; let mut num_described = 0;
let mut num_rebased = 0; let mut num_reparented = 0;
// Even though `MutRepo::rewrite_commit` and `MutRepo::rebase_descendants` can // Even though `MutRepo::rewrite_commit` and `MutRepo::rebase_descendants` can
// handle rewriting of a commit even if it is a descendant of another commit // handle rewriting of a commit even if it is a descendant of another commit
// being rewritten, using `MutRepo::transform_descendants` prevents us from // being rewritten, using `MutRepo::transform_descendants` prevents us from
// rewriting the same commit multiple times, and adding additional entries // rewriting the same commit multiple times, and adding additional entries
// in the predecessor chain. // in the predecessor chain.
tx.repo_mut().transform_descendants( tx.repo_mut().transform_descendants(
command.settings(), commit_builders.keys().map(|&id| id.clone()).collect(),
commit_descriptions
.keys()
.map(|&id| id.clone())
.collect_vec(),
|rewriter| { |rewriter| {
let old_commit_id = rewriter.old_commit().id().clone(); let old_commit_id = rewriter.old_commit().id().clone();
let mut commit_builder = rewriter.rebase(command.settings())?; let commit_builder = rewriter.reparent();
if let Some(description) = commit_descriptions.get(&old_commit_id) { if let Some(temp_builder) = commit_builders.get(&old_commit_id) {
commit_builder = commit_builder.set_description(description); commit_builder
if args.reset_author { .set_description(temp_builder.description())
let new_author = commit_builder.committer().clone(); .set_author(temp_builder.author().clone())
commit_builder = commit_builder.set_author(new_author); // Copy back committer for consistency with author timestamp
} .set_committer(temp_builder.committer().clone())
if let Some((name, email)) = args.author.clone() { .write()?;
let new_author = Signature {
name,
email,
timestamp: commit_builder.author().timestamp,
};
commit_builder = commit_builder.set_author(new_author);
}
num_described += 1; num_described += 1;
} else { } else {
num_rebased += 1; commit_builder.write()?;
num_reparented += 1;
} }
commit_builder.write()?;
Ok(()) Ok(())
}, },
)?; )?;
if num_described > 1 { if num_described > 1 {
writeln!(ui.status(), "Updated {num_described} commits")?; writeln!(ui.status(), "Updated {num_described} commits")?;
} }
if num_rebased > 0 { if num_reparented > 0 {
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_reparented} descendant commits")?;
} }
tx.finish(ui, tx_description)?; tx.finish(ui, tx_description)?;
Ok(()) Ok(())

View File

@ -12,16 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use itertools::Itertools; use clap_complete::ArgValueCompleter;
use indexmap::IndexSet;
use itertools::Itertools as _;
use jj_lib::copies::CopyRecords; use jj_lib::copies::CopyRecords;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use jj_lib::rewrite::merge_commit_trees; use jj_lib::rewrite::merge_commit_trees;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::print_unmatched_explicit_paths; use crate::cli_util::print_unmatched_explicit_paths;
use crate::cli_util::short_commit_hash;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::diff_util::get_copy_records; use crate::diff_util::get_copy_records;
use crate::diff_util::DiffFormatArgs; use crate::diff_util::DiffFormatArgs;
use crate::ui::Ui; use crate::ui::Ui;
@ -41,21 +46,49 @@ use crate::ui::Ui;
#[command(mut_arg("ignore_all_space", |a| a.short('w')))] #[command(mut_arg("ignore_all_space", |a| a.short('w')))]
#[command(mut_arg("ignore_space_change", |a| a.short('b')))] #[command(mut_arg("ignore_space_change", |a| a.short('b')))]
pub(crate) struct DiffArgs { pub(crate) struct DiffArgs {
/// Show changes in this revision, compared to its parent(s) /// Show changes in these revisions
/// ///
/// If the revision is a merge commit, this shows changes *from* the /// If there are multiple revisions, then then total diff for all of them
/// will be shown. For example, if you have a linear chain of revisions
/// A..D, then `jj diff -r B::D` equals `jj diff --from A --to D`. Multiple
/// heads and/or roots are supported, but gaps in the revset are not
/// supported (e.g. `jj diff -r 'A|C'` in a linear chain A..C).
///
/// If a revision is a merge commit, this shows changes *from* the
/// automatic merge of the contents of all of its parents *to* the contents /// automatic merge of the contents of all of its parents *to* the contents
/// of the revision itself. /// of the revision itself.
#[arg(long, short)] #[arg(
revision: Option<RevisionArg>, long,
short,
value_name = "REVSETS",
alias = "revision",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revisions: Option<Vec<RevisionArg>>,
/// Show changes from this revision /// Show changes from this revision
#[arg(long, conflicts_with = "revision")] #[arg(
long,
short,
conflicts_with = "revisions",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
from: Option<RevisionArg>, from: Option<RevisionArg>,
/// Show changes to this revision /// Show changes to this revision
#[arg(long, conflicts_with = "revision")] #[arg(
long,
short,
conflicts_with = "revisions",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
to: Option<RevisionArg>, to: Option<RevisionArg>,
/// Restrict the diff to these paths /// Restrict the diff to these paths
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(
value_name = "FILESETS",
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::modified_revision_or_range_files),
)]
paths: Vec<String>, paths: Vec<String>,
#[command(flatten)] #[command(flatten)]
format: DiffFormatArgs, format: DiffFormatArgs,
@ -71,14 +104,14 @@ pub(crate) fn cmd_diff(
let repo = workspace_command.repo(); let repo = workspace_command.repo();
let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?;
let matcher = fileset_expression.to_matcher(); let matcher = fileset_expression.to_matcher();
let resolve_revision = |r: &Option<RevisionArg>| {
workspace_command.resolve_single_rev(ui, r.as_ref().unwrap_or(&RevisionArg::AT))
};
let from_tree; let from_tree;
let to_tree; let to_tree;
let mut copy_records = CopyRecords::default(); let mut copy_records = CopyRecords::default();
if args.from.is_some() || args.to.is_some() { if args.from.is_some() || args.to.is_some() {
let resolve_revision = |r: &Option<RevisionArg>| {
workspace_command.resolve_single_rev(ui, r.as_ref().unwrap_or(&RevisionArg::AT))
};
let from = resolve_revision(&args.from)?; let from = resolve_revision(&args.from)?;
let to = resolve_revision(&args.to)?; let to = resolve_revision(&args.to)?;
from_tree = from.tree()?; from_tree = from.tree()?;
@ -87,14 +120,44 @@ pub(crate) fn cmd_diff(
let records = get_copy_records(repo.store(), from.id(), to.id(), &matcher)?; let records = get_copy_records(repo.store(), from.id(), to.id(), &matcher)?;
copy_records.add_records(records)?; copy_records.add_records(records)?;
} else { } else {
let to = resolve_revision(&args.revision)?; let revision_args = args
let parents: Vec<_> = to.parents().try_collect()?; .revisions
.as_deref()
.unwrap_or(std::slice::from_ref(&RevisionArg::AT));
let revisions_evaluator = workspace_command.parse_union_revsets(ui, revision_args)?;
let target_expression = revisions_evaluator.expression();
let mut gaps_revset = workspace_command
.attach_revset_evaluator(target_expression.connected().minus(target_expression))
.evaluate_to_commit_ids()?;
if let Some(commit_id) = gaps_revset.next() {
return Err(user_error_with_hint(
"Cannot diff revsets with gaps in.",
format!(
"Revision {} would need to be in the set.",
short_commit_hash(&commit_id?)
),
));
}
let heads: Vec<_> = workspace_command
.attach_revset_evaluator(target_expression.heads())
.evaluate_to_commits()?
.try_collect()?;
let roots: Vec<_> = workspace_command
.attach_revset_evaluator(target_expression.roots())
.evaluate_to_commits()?
.try_collect()?;
// Collect parents outside of revset to preserve parent order
let parents: IndexSet<_> = roots.iter().flat_map(|c| c.parents()).try_collect()?;
let parents = parents.into_iter().collect_vec();
from_tree = merge_commit_trees(repo.as_ref(), &parents)?; from_tree = merge_commit_trees(repo.as_ref(), &parents)?;
to_tree = to.tree()?; to_tree = merge_commit_trees(repo.as_ref(), &heads)?;
for p in &parents { for p in &parents {
let records = get_copy_records(repo.store(), p.id(), to.id(), &matcher)?; for to in &heads {
copy_records.add_records(records)?; let records = get_copy_records(repo.store(), p.id(), to.id(), &matcher)?;
copy_records.add_records(records)?;
}
} }
} }

View File

@ -12,17 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io::Write as _;
use itertools::Itertools; use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::matchers::EverythingMatcher; use jj_lib::matchers::EverythingMatcher;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use jj_lib::rewrite::merge_commit_trees; use jj_lib::rewrite::merge_commit_trees;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Touch up the content changes in a revision with a diff editor /// Touch up the content changes in a revision with a diff editor
@ -34,7 +36,7 @@ use crate::ui::Ui;
/// the "from" revision to the "to" revision. /// the "from" revision to the "to" revision.
/// ///
/// [diff editor]: /// [diff editor]:
/// https://martinvonz.github.io/jj/latest/config/#editing-diffs /// https://jj-vcs.github.io/jj/latest/config/#editing-diffs
/// ///
/// Edit the right side of the diff until it looks the way you want. Once you /// Edit the right side of the diff until it looks the way you want. Once you
/// close the editor, the revision specified with `-r` or `--to` will be /// close the editor, the revision specified with `-r` or `--to` will be
@ -48,17 +50,32 @@ pub(crate) struct DiffeditArgs {
/// The revision to touch up /// The revision to touch up
/// ///
/// Defaults to @ if neither --to nor --from are specified. /// Defaults to @ if neither --to nor --from are specified.
#[arg(long, short)] #[arg(
long,
short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revision: Option<RevisionArg>, revision: Option<RevisionArg>,
/// Show changes from this revision /// Show changes from this revision
/// ///
/// Defaults to @ if --to is specified. /// Defaults to @ if --to is specified.
#[arg(long, conflicts_with = "revision")] #[arg(
long, short,
conflicts_with = "revision",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
from: Option<RevisionArg>, from: Option<RevisionArg>,
/// Edit changes in this revision /// Edit changes in this revision
/// ///
/// Defaults to @ if --from is specified. /// Defaults to @ if --from is specified.
#[arg(long, conflicts_with = "revision")] #[arg(
long, short,
conflicts_with = "revision",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
to: Option<RevisionArg>, to: Option<RevisionArg>,
/// Specify diff editor to be used /// Specify diff editor to be used
#[arg(long, value_name = "NAME")] #[arg(long, value_name = "NAME")]
@ -119,25 +136,21 @@ don't make any changes, then the operation will be aborted.",
if tree_id == *target_commit.tree_id() { if tree_id == *target_commit.tree_id() {
writeln!(ui.status(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
} else { } else {
let new_commit = tx tx.repo_mut()
.repo_mut() .rewrite_commit(&target_commit)
.rewrite_commit(command.settings(), &target_commit)
.set_tree_id(tree_id) .set_tree_id(tree_id)
.write()?; .write()?;
// rebase_descendants early; otherwise `new_commit` would always have // rebase_descendants early; otherwise `new_commit` would always have
// a conflicted change id at this point. // a conflicted change id at this point.
let (num_rebased, extra_msg) = if args.restore_descendants { let (num_rebased, extra_msg) = if args.restore_descendants {
( (
tx.repo_mut().reparent_descendants(command.settings())?, tx.repo_mut().reparent_descendants()?,
" (while preserving their content)", " (while preserving their content)",
) )
} else { } else {
(tx.repo_mut().rebase_descendants(command.settings())?, "") (tx.repo_mut().rebase_descendants()?, "")
}; };
if let Some(mut formatter) = ui.status_formatter() { if let Some(mut formatter) = ui.status_formatter() {
write!(formatter, "Created ")?;
tx.write_commit_summary(formatter.as_mut(), &new_commit)?;
writeln!(formatter)?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!( writeln!(
formatter, formatter,

View File

@ -12,31 +12,91 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io::Write as _;
use indexmap::IndexMap; use bstr::ByteVec as _;
use itertools::Itertools; use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::backend::BackendResult;
use jj_lib::backend::CommitId; use jj_lib::backend::CommitId;
use jj_lib::commit::Commit; use jj_lib::repo::Repo as _;
use jj_lib::repo::Repo; use jj_lib::rewrite::duplicate_commits;
use jj_lib::rewrite::duplicate_commits_onto_parents;
use jj_lib::rewrite::DuplicateCommitsStats;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::compute_commit_location;
use crate::cli_util::short_commit_hash; use crate::cli_util::short_commit_hash;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::formatter::PlainTextFormatter;
use crate::ui::Ui; use crate::ui::Ui;
/// Create a new change with the same content as an existing one /// Create new changes with the same content as existing ones
///
/// When none of the `--destination`, `--insert-after`, or `--insert-before`
/// arguments are provided, commits will be duplicated onto their existing
/// parents or onto other newly duplicated commits.
///
/// When any of the `--destination`, `--insert-after`, or `--insert-before`
/// arguments are provided, the roots of the specified commits will be
/// duplicated onto the destination indicated by the arguments. Other specified
/// commits will be duplicated onto these newly duplicated commits. If the
/// `--insert-after` or `--insert-before` arguments are provided, the new
/// children indicated by the arguments will be rebased onto the heads of the
/// specified commits.
///
/// By default, the duplicated commits retain the descriptions of the originals.
/// This can be customized with the `templates.duplicate_description` setting.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct DuplicateArgs { pub(crate) struct DuplicateArgs {
/// The revision(s) to duplicate /// The revision(s) to duplicate (default: @)
#[arg(default_value = "@")] #[arg(
revisions: Vec<RevisionArg>, value_name = "REVSETS",
/// Ignored (but lets you pass `-r` for consistency with other commands) add = ArgValueCompleter::new(complete::revset_expression_all),
#[arg(short = 'r', hide = true, action = clap::ArgAction::Count)] )]
unused_revision: u8, revisions_pos: Vec<RevisionArg>,
#[arg(
short = 'r',
hide = true,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revisions_opt: Vec<RevisionArg>,
/// The revision(s) to duplicate onto (can be repeated to create a merge
/// commit)
#[arg(
long,
short,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
destination: Option<Vec<RevisionArg>>,
/// The revision(s) to insert after (can be repeated to create a merge
/// commit)
#[arg(
long,
short = 'A',
visible_alias = "after",
conflicts_with = "destination",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
insert_after: Option<Vec<RevisionArg>>,
/// The revision(s) to insert before (can be repeated to create a merge
/// commit)
#[arg(
long,
short = 'B',
visible_alias = "before",
conflicts_with = "destination",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
insert_before: Option<Vec<RevisionArg>>,
} }
#[instrument(skip_all)] #[instrument(skip_all)]
@ -46,8 +106,13 @@ pub(crate) fn cmd_duplicate(
args: &DuplicateArgs, args: &DuplicateArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let to_duplicate: Vec<CommitId> = workspace_command let to_duplicate: Vec<CommitId> =
.parse_union_revsets(ui, &args.revisions)? if !args.revisions_pos.is_empty() || !args.revisions_opt.is_empty() {
workspace_command
.parse_union_revsets(ui, &[&*args.revisions_pos, &*args.revisions_opt].concat())?
} else {
workspace_command.parse_revset(ui, &RevisionArg::AT)?
}
.evaluate_to_commit_ids()? .evaluate_to_commit_ids()?
.try_collect()?; // in reverse topological order .try_collect()?; // in reverse topological order
if to_duplicate.is_empty() { if to_duplicate.is_empty() {
@ -57,37 +122,104 @@ pub(crate) fn cmd_duplicate(
if to_duplicate.last() == Some(workspace_command.repo().store().root_commit_id()) { if to_duplicate.last() == Some(workspace_command.repo().store().root_commit_id()) {
return Err(user_error("Cannot duplicate the root commit")); return Err(user_error("Cannot duplicate the root commit"));
} }
let mut duplicated_old_to_new: IndexMap<&CommitId, Commit> = IndexMap::new();
let location = if args.destination.is_none()
&& args.insert_after.is_none()
&& args.insert_before.is_none()
{
None
} else {
Some(compute_commit_location(
ui,
&workspace_command,
args.destination.as_deref(),
args.insert_after.as_deref(),
args.insert_before.as_deref(),
"duplicated commits",
)?)
};
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let base_repo = tx.base_repo().clone();
let store = base_repo.store();
let mut_repo = tx.repo_mut();
for original_commit_id in to_duplicate.iter().rev() { if let Some((parent_commit_ids, children_commit_ids)) = &location {
// Topological order ensures that any parents of `original_commit` are if !parent_commit_ids.is_empty() {
// either not in `to_duplicate` or were already duplicated. for commit_id in &to_duplicate {
let original_commit = store.get_commit(original_commit_id)?; for parent_commit_id in parent_commit_ids {
let new_parents = original_commit if tx.repo().index().is_ancestor(commit_id, parent_commit_id) {
.parent_ids() writeln!(
.iter() ui.warning_default(),
.map(|id| duplicated_old_to_new.get(id).map_or(id, |c| c.id()).clone()) "Duplicating commit {} as a descendant of itself",
.collect(); short_commit_hash(commit_id)
let new_commit = mut_repo )?;
.rewrite_commit(command.settings(), &original_commit) break;
.generate_new_change_id() }
.set_parents(new_parents) }
.write()?; }
duplicated_old_to_new.insert(original_commit_id, new_commit);
for commit_id in &to_duplicate {
for child_commit_id in children_commit_ids {
if tx.repo().index().is_ancestor(child_commit_id, commit_id) {
writeln!(
ui.warning_default(),
"Duplicating commit {} as an ancestor of itself",
short_commit_hash(commit_id)
)?;
break;
}
}
}
}
} }
let new_descs = {
let template = tx
.settings()
.get_string("templates.duplicate_description")?;
let parsed = tx.parse_commit_template(ui, &template)?;
to_duplicate
.iter()
.map(|commit_id| -> BackendResult<_> {
let mut output = Vec::new();
let commit = tx.repo().store().get_commit(commit_id)?;
parsed
.format(&commit, &mut PlainTextFormatter::new(&mut output))
.expect("write() to vec backed formatter should never fail");
Ok((commit_id.clone(), output.into_string_lossy()))
})
.try_collect()?
};
let num_to_duplicate = to_duplicate.len();
let DuplicateCommitsStats {
duplicated_commits,
num_rebased,
} = if let Some((parent_commit_ids, children_commit_ids)) = location {
duplicate_commits(
tx.repo_mut(),
&to_duplicate,
&new_descs,
&parent_commit_ids,
&children_commit_ids,
)?
} else {
duplicate_commits_onto_parents(tx.repo_mut(), &to_duplicate, &new_descs)?
};
if let Some(mut formatter) = ui.status_formatter() { if let Some(mut formatter) = ui.status_formatter() {
for (old_id, new_commit) in &duplicated_old_to_new { for (old_id, new_commit) in &duplicated_commits {
write!(formatter, "Duplicated {} as ", short_commit_hash(old_id))?; write!(formatter, "Duplicated {} as ", short_commit_hash(old_id))?;
tx.write_commit_summary(formatter.as_mut(), new_commit)?; tx.write_commit_summary(formatter.as_mut(), new_commit)?;
writeln!(formatter)?; writeln!(formatter)?;
} }
if num_rebased > 0 {
writeln!(
ui.status(),
"Rebased {num_rebased} commits onto duplicated commits"
)?;
}
} }
tx.finish(ui, format!("duplicate {} commit(s)", to_duplicate.len()))?; tx.finish(ui, format!("duplicate {num_to_duplicate} commit(s)"))?;
Ok(()) Ok(())
} }

View File

@ -12,25 +12,29 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io::Write as _;
use jj_lib::object_id::ObjectId; use clap_complete::ArgValueCompleter;
use jj_lib::object_id::ObjectId as _;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Sets the specified revision as the working-copy revision /// Sets the specified revision as the working-copy revision
/// ///
/// Note: it is generally recommended to instead use `jj new` and `jj /// Note: it is [generally recommended] to instead use `jj new` and `jj
/// squash`. /// squash`.
/// ///
/// For more information, see https://martinvonz.github.io/jj/latest/FAQ#how-do-i-resume-working-on-an-existing-change /// [generally recommended]:
/// https://jj-vcs.github.io/jj/latest/FAQ#how-do-i-resume-working-on-an-existing-change
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct EditArgs { pub(crate) struct EditArgs {
/// The commit to edit /// The commit to edit
#[arg(value_name = "REVSET", add = ArgValueCompleter::new(complete::revset_expression_mutable))]
revision: RevisionArg, revision: RevisionArg,
/// Ignored (but lets you pass `-r` for consistency with other commands) /// Ignored (but lets you pass `-r` for consistency with other commands)
#[arg(short = 'r', hide = true)] #[arg(short = 'r', hide = true)]

View File

@ -12,9 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use itertools::Itertools; use std::convert::Infallible;
use clap_complete::ArgValueCandidates;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::commit::Commit; use jj_lib::commit::Commit;
use jj_lib::dag_walk::topo_order_reverse_ok; use jj_lib::dag_walk::topo_order_reverse_ok;
use jj_lib::graph::reverse_graph;
use jj_lib::graph::GraphEdge;
use jj_lib::matchers::EverythingMatcher; use jj_lib::matchers::EverythingMatcher;
use tracing::instrument; use tracing::instrument;
@ -24,10 +30,10 @@ use crate::cli_util::CommandHelper;
use crate::cli_util::LogContentFormat; use crate::cli_util::LogContentFormat;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitTemplatePropertyKind;
use crate::complete;
use crate::diff_util::DiffFormatArgs; use crate::diff_util::DiffFormatArgs;
use crate::graphlog::get_graphlog; use crate::graphlog::get_graphlog;
use crate::graphlog::Edge;
use crate::graphlog::GraphStyle; use crate::graphlog::GraphStyle;
use crate::ui::Ui; use crate::ui::Ui;
@ -37,26 +43,39 @@ use crate::ui::Ui;
/// of a change evolves when the change is updated, rebased, etc. /// of a change evolves when the change is updated, rebased, etc.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct EvologArgs { pub(crate) struct EvologArgs {
#[arg(long, short, default_value = "@")] #[arg(
long, short,
default_value = "@",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revision: RevisionArg, revision: RevisionArg,
/// Limit number of revisions to show /// Limit number of revisions to show
///
/// Applied after revisions are reordered topologically, but before being
/// reversed.
#[arg(long, short = 'n')] #[arg(long, short = 'n')]
limit: Option<usize>, limit: Option<usize>,
// TODO: Delete `-l` alias in jj 0.25+ /// Show revisions in the opposite order (older revisions first)
#[arg( #[arg(long)]
short = 'l', reversed: bool,
hide = true,
conflicts_with = "limit",
value_name = "LIMIT"
)]
deprecated_limit: Option<usize>,
/// Don't show the graph, show a flat list of revisions /// Don't show the graph, show a flat list of revisions
#[arg(long)] #[arg(long)]
no_graph: bool, no_graph: bool,
/// Render each revision using the given template /// Render each revision using the given template
/// ///
/// For the syntax, see https://martinvonz.github.io/jj/latest/templates/ /// Run `jj log -T` to list the built-in templates.
#[arg(long, short = 'T')] ///
/// You can also specify arbitrary template expressions using the
/// [built-in keywords]. See [`jj help -k templates`] for more
/// information.
///
/// [built-in keywords]:
/// https://jj-vcs.github.io/jj/latest/templates/#commit-keywords
///
/// [`jj help -k templates`]:
/// https://jj-vcs.github.io/jj/latest/templates/
#[arg(long, short = 'T', add = ArgValueCandidates::new(complete::template_aliases))]
template: Option<String>, template: Option<String>,
/// Show patch compared to the previous version of this change /// Show patch compared to the previous version of this change
/// ///
@ -80,8 +99,8 @@ pub(crate) fn cmd_evolog(
let start_commit = workspace_command.resolve_single_rev(ui, &args.revision)?; let start_commit = workspace_command.resolve_single_rev(ui, &args.revision)?;
let diff_renderer = workspace_command.diff_renderer_for_log(&args.diff_format, args.patch)?; let diff_renderer = workspace_command.diff_renderer_for_log(&args.diff_format, args.patch)?;
let graph_style = GraphStyle::from_settings(command.settings())?; let graph_style = GraphStyle::from_settings(workspace_command.settings())?;
let with_content_format = LogContentFormat::new(ui, command.settings())?; let with_content_format = LogContentFormat::new(ui, workspace_command.settings())?;
let template; let template;
let node_template; let node_template;
@ -89,22 +108,22 @@ pub(crate) fn cmd_evolog(
let language = workspace_command.commit_template_language(); let language = workspace_command.commit_template_language();
let template_string = match &args.template { let template_string = match &args.template {
Some(value) => value.to_string(), Some(value) => value.to_string(),
None => command.settings().config().get_string("templates.log")?, None => workspace_command.settings().get_string("templates.log")?,
}; };
template = workspace_command template = workspace_command
.parse_template( .parse_template(
ui, ui,
&language, &language,
&template_string, &template_string,
CommitTemplateLanguage::wrap_commit, CommitTemplatePropertyKind::wrap_commit,
)? )?
.labeled("log"); .labeled("log");
node_template = workspace_command node_template = workspace_command
.parse_template( .parse_template(
ui, ui,
&language, &language,
&get_node_template(graph_style, command.settings())?, &get_node_template(graph_style, workspace_command.settings())?,
CommitTemplateLanguage::wrap_commit_opt, CommitTemplatePropertyKind::wrap_commit_opt,
)? )?
.labeled("node"); .labeled("node");
} }
@ -132,24 +151,34 @@ pub(crate) fn cmd_evolog(
predecessors predecessors
}, },
)?; )?;
if args.deprecated_limit.is_some() { if let Some(n) = args.limit {
writeln!(
ui.warning_default(),
"The -l shorthand is deprecated, use -n instead."
)?;
}
if let Some(n) = args.limit.or(args.deprecated_limit) {
commits.truncate(n); commits.truncate(n);
} }
if !args.no_graph { if !args.no_graph {
let mut raw_output = formatter.raw()?; let mut raw_output = formatter.raw()?;
let mut graph = get_graphlog(graph_style, raw_output.as_mut()); let mut graph = get_graphlog(graph_style, raw_output.as_mut());
for commit in commits {
let edges = commit let commit_nodes = commits
.predecessor_ids() .into_iter()
.iter() .map(|c| {
.map(|id| Edge::Direct(id.clone())) let ids = c.predecessor_ids();
.collect_vec(); let edges = ids.iter().cloned().map(GraphEdge::direct).collect_vec();
(c, edges)
})
.collect_vec();
let commit_nodes = if args.reversed {
reverse_graph(
commit_nodes.into_iter().map(Result::<_, Infallible>::Ok),
Commit::id,
)
.unwrap()
} else {
commit_nodes
};
for node in commit_nodes {
let (commit, edges) = node;
let mut buffer = vec![]; let mut buffer = vec![];
let within_graph = with_content_format.sub_width(graph.width(commit.id(), &edges)); let within_graph = with_content_format.sub_width(graph.width(commit.id(), &edges));
within_graph.write(ui.new_formatter(&mut buffer).as_mut(), |formatter| { within_graph.write(ui.new_formatter(&mut buffer).as_mut(), |formatter| {
@ -179,6 +208,10 @@ pub(crate) fn cmd_evolog(
)?; )?;
} }
} else { } else {
if args.reversed {
commits.reverse();
}
for commit in commits { for commit in commits {
with_content_format with_content_format
.write(formatter, |formatter| template.format(&commit, formatter))?; .write(formatter, |formatter| template.format(&commit, formatter))?;

View File

@ -12,16 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use jj_lib::annotate::get_annotation_for_file; use clap_complete::ArgValueCandidates;
use clap_complete::ArgValueCompleter;
use jj_lib::annotate::FileAnnotation; use jj_lib::annotate::FileAnnotation;
use jj_lib::commit::Commit; use jj_lib::annotate::FileAnnotator;
use jj_lib::repo::Repo; use jj_lib::repo::Repo;
use jj_lib::revset::RevsetExpression;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commit_templater::AnnotationLine;
use crate::commit_templater::CommitTemplatePropertyKind;
use crate::complete;
use crate::templater::TemplateRenderer; use crate::templater::TemplateRenderer;
use crate::ui::Ui; use crate::ui::Ui;
@ -29,16 +34,38 @@ use crate::ui::Ui;
/// ///
/// Annotates a revision line by line. Each line includes the source change that /// Annotates a revision line by line. Each line includes the source change that
/// introduced the associated line. A path to the desired file must be provided. /// introduced the associated line. A path to the desired file must be provided.
/// The per-line prefix for each line can be customized via
/// template with the `templates.annotate_commit_summary` config variable.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct FileAnnotateArgs { pub(crate) struct FileAnnotateArgs {
/// the file to annotate /// the file to annotate
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::all_revision_files),
)]
path: String, path: String,
/// an optional revision to start at /// an optional revision to start at
#[arg(long, short)] #[arg(
long,
short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revision: Option<RevisionArg>, revision: Option<RevisionArg>,
/// Render each line using the given template
///
/// All 0-argument methods of the [`AnnotationLine` type] are available as
/// keywords in the template expression. See [`jj help -k templates`] for
/// more information.
///
/// If not specified, this defaults to the `templates.file_annotate`
/// setting.
///
/// [`AnnotationLine` type]:
/// https://jj-vcs.github.io/jj/latest/templates/#annotationline-type
///
/// [`jj help -k templates`]:
/// https://jj-vcs.github.io/jj/latest/templates/
#[arg(long, short = 'T', add = ArgValueCandidates::new(complete::template_aliases))]
template: Option<String>,
} }
#[instrument(skip_all)] #[instrument(skip_all)]
@ -63,13 +90,27 @@ pub(crate) fn cmd_file_annotate(
))); )));
} }
let annotate_commit_summary_text = command let template_text = match &args.template {
.settings() Some(value) => value.clone(),
.config() None => workspace_command
.get_string("templates.annotate_commit_summary")?; .settings()
let template = workspace_command.parse_commit_template(ui, &annotate_commit_summary_text)?; .get_string("templates.file_annotate")?,
};
let language = workspace_command.commit_template_language();
let template = workspace_command.parse_template(
ui,
&language,
&template_text,
CommitTemplatePropertyKind::wrap_annotation_line,
)?;
let annotation = get_annotation_for_file(repo.as_ref(), &starting_commit, &file_path)?; // TODO: Should we add an option to limit the domain to e.g. recent commits?
// Note that this is probably different from "--skip REVS", which won't
// exclude the revisions, but will ignore diffs in those revisions as if
// ancestor revisions had new content.
let mut annotator = FileAnnotator::from_commit(&starting_commit, &file_path)?;
annotator.compute(repo.as_ref(), &RevsetExpression::all())?;
let annotation = annotator.to_annotation();
render_file_annotation(repo.as_ref(), ui, &template, &annotation)?; render_file_annotation(repo.as_ref(), ui, &template, &annotation)?;
Ok(()) Ok(())
@ -78,16 +119,29 @@ pub(crate) fn cmd_file_annotate(
fn render_file_annotation( fn render_file_annotation(
repo: &dyn Repo, repo: &dyn Repo,
ui: &mut Ui, ui: &mut Ui,
template_render: &TemplateRenderer<Commit>, template_render: &TemplateRenderer<AnnotationLine>,
annotation: &FileAnnotation, annotation: &FileAnnotation,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
ui.request_pager(); ui.request_pager();
let mut formatter = ui.stdout_formatter(); let mut formatter = ui.stdout_formatter();
for (line_no, (commit_id, line)) in annotation.lines().enumerate() { let mut last_id = None;
let default_id = repo.store().root_commit_id();
for (line_number, (commit_id, content)) in annotation.lines().enumerate() {
/* At least in cases where the repository was jj-initialized shallowly,
then unshallow'd with git, some changes will not have a commit id
because jj does not import the unshallow'd commits. So we default
to the root commit id for now. */
let commit_id = commit_id.unwrap_or(default_id);
let commit = repo.store().get_commit(commit_id)?; let commit = repo.store().get_commit(commit_id)?;
template_render.format(&commit, formatter.as_mut())?; let first_line_in_hunk = last_id != Some(commit_id);
write!(formatter, " {:>4}: ", line_no + 1)?; let annotation_line = AnnotationLine {
formatter.write_all(line)?; commit,
content: content.to_owned(),
line_number: line_number + 1,
first_line_in_hunk,
};
template_render.format(&annotation_line, formatter.as_mut())?;
last_id = Some(commit_id);
} }
Ok(()) Ok(())

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCompleter;
use jj_lib::backend::TreeValue; use jj_lib::backend::TreeValue;
use jj_lib::merged_tree::MergedTreeBuilder; use jj_lib::merged_tree::MergedTreeBuilder;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId as _;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::print_unmatched_explicit_paths; use crate::cli_util::print_unmatched_explicit_paths;
@ -22,6 +23,7 @@ use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
@ -43,10 +45,20 @@ enum ChmodMode {
pub(crate) struct FileChmodArgs { pub(crate) struct FileChmodArgs {
mode: ChmodMode, mode: ChmodMode,
/// The revision to update /// The revision to update
#[arg(long, short, default_value = "@")] #[arg(
long, short,
default_value = "@",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revision: RevisionArg, revision: RevisionArg,
/// Paths to change the executable bit for /// Paths to change the executable bit for
#[arg(required = true, value_hint = clap::ValueHint::AnyPath)] #[arg(
required = true,
value_name = "FILESETS",
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::all_revision_files),
)]
paths: Vec<String>, paths: Vec<String>,
} }
@ -75,7 +87,7 @@ pub(crate) fn cmd_file_chmod(
let store = tree.store(); let store = tree.store();
let mut tree_builder = MergedTreeBuilder::new(commit.tree_id().clone()); let mut tree_builder = MergedTreeBuilder::new(commit.tree_id().clone());
for (repo_path, result) in tree.entries_matching(matcher.as_ref()) { for (repo_path, result) in tree.entries_matching(matcher.as_ref()) {
let tree_value = result?; let mut tree_value = result?;
let user_error_with_path = |msg: &str| { let user_error_with_path = |msg: &str| {
user_error(format!( user_error(format!(
"{msg} at '{}'.", "{msg} at '{}'.",
@ -94,22 +106,17 @@ pub(crate) fn cmd_file_chmod(
}; };
return Err(user_error_with_path(message)); return Err(user_error_with_path(message));
} }
let new_tree_value = tree_value.map(|value| match value { for value in tree_value.iter_mut().flatten() {
Some(TreeValue::File { id, executable: _ }) => Some(TreeValue::File { if let TreeValue::File { id: _, executable } = value {
id: id.clone(), *executable = executable_bit;
executable: executable_bit,
}),
Some(TreeValue::Conflict(_)) => {
panic!("Conflict sides must not themselves be conflicts")
} }
value => value.clone(), }
}); tree_builder.set_or_remove(repo_path, tree_value);
tree_builder.set_or_remove(repo_path, new_tree_value);
} }
let new_tree_id = tree_builder.write_tree(store)?; let new_tree_id = tree_builder.write_tree(store)?;
tx.repo_mut() tx.repo_mut()
.rewrite_commit(command.settings(), &commit) .rewrite_commit(&commit)
.set_tree_id(new_tree_id) .set_tree_id(new_tree_id)
.write()?; .write()?;
tx.finish( tx.finish(

View File

@ -12,23 +12,45 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use clap_complete::ArgValueCompleter;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commit_templater::CommitTemplatePropertyKind;
use crate::commit_templater::TreeEntry;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// List files in a revision /// List files in a revision
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct FileListArgs { pub(crate) struct FileListArgs {
/// The revision to list files in /// The revision to list files in
#[arg(long, short, default_value = "@")] #[arg(
long, short,
default_value = "@",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revision: RevisionArg, revision: RevisionArg,
/// Render each file entry using the given template
///
/// All 0-argument methods of the [`TreeEntry` type] are available as
/// keywords in the template expression. See [`jj help -k templates`] for
/// more information.
///
/// [`TreeEntry` type]:
/// https://jj-vcs.github.io/jj/latest/templates/#treeentry-type
///
/// [`jj help -k templates`]:
/// https://jj-vcs.github.io/jj/latest/templates/
#[arg(long, short = 'T')]
template: Option<String>,
/// Only list files matching these prefixes (instead of all files) /// Only list files matching these prefixes (instead of all files)
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(value_name = "FILESETS", value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>, paths: Vec<String>,
} }
@ -44,13 +66,30 @@ pub(crate) fn cmd_file_list(
let matcher = workspace_command let matcher = workspace_command
.parse_file_patterns(ui, &args.paths)? .parse_file_patterns(ui, &args.paths)?
.to_matcher(); .to_matcher();
let template = {
let language = workspace_command.commit_template_language();
let text = match &args.template {
Some(value) => value.to_owned(),
None => workspace_command.settings().get("templates.file_list")?,
};
workspace_command
.parse_template(
ui,
&language,
&text,
CommitTemplatePropertyKind::wrap_tree_entry,
)?
.labeled("file_list")
};
ui.request_pager(); ui.request_pager();
for (name, _value) in tree.entries_matching(matcher.as_ref()) { let mut formatter = ui.stdout_formatter();
writeln!( for (path, value) in tree.entries_matching(matcher.as_ref()) {
ui.stdout(), let entry = TreeEntry {
"{}", path,
&workspace_command.format_file_path(&name) value: value?,
)?; };
template.format(&entry, formatter.as_mut())?;
} }
Ok(()) Ok(())
} }

View File

@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
pub mod annotate; mod annotate;
pub mod chmod; mod chmod;
pub mod list; mod list;
pub mod show; mod show;
pub mod track; mod track;
pub mod untrack; mod untrack;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;

View File

@ -13,8 +13,9 @@
// limitations under the License. // limitations under the License.
use std::io; use std::io;
use std::io::Write; use std::io::Write as _;
use clap_complete::ArgValueCompleter;
use jj_lib::backend::BackendResult; use jj_lib::backend::BackendResult;
use jj_lib::conflicts::materialize_merge_result; use jj_lib::conflicts::materialize_merge_result;
use jj_lib::conflicts::materialize_tree_value; use jj_lib::conflicts::materialize_tree_value;
@ -22,9 +23,9 @@ use jj_lib::conflicts::MaterializedTreeValue;
use jj_lib::fileset::FilePattern; use jj_lib::fileset::FilePattern;
use jj_lib::fileset::FilesetExpression; use jj_lib::fileset::FilesetExpression;
use jj_lib::merge::MergedTreeValue; use jj_lib::merge::MergedTreeValue;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use jj_lib::repo_path::RepoPath; use jj_lib::repo_path::RepoPath;
use pollster::FutureExt; use pollster::FutureExt as _;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::print_unmatched_explicit_paths; use crate::cli_util::print_unmatched_explicit_paths;
@ -33,6 +34,7 @@ use crate::cli_util::RevisionArg;
use crate::cli_util::WorkspaceCommandHelper; use crate::cli_util::WorkspaceCommandHelper;
use crate::command_error::user_error; use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Print contents of files in a revision /// Print contents of files in a revision
@ -42,10 +44,20 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct FileShowArgs { pub(crate) struct FileShowArgs {
/// The revision to get the file contents from /// The revision to get the file contents from
#[arg(long, short, default_value = "@")] #[arg(
long, short,
default_value = "@",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revision: RevisionArg, revision: RevisionArg,
/// Paths to print /// Paths to print
#[arg(required = true, value_hint = clap::ValueHint::FilePath)] #[arg(
required = true,
value_name = "FILESETS",
value_hint = clap::ValueHint::FilePath,
add = ArgValueCompleter::new(complete::all_revision_files),
)]
paths: Vec<String>, paths: Vec<String>,
} }
@ -117,11 +129,15 @@ fn write_tree_entries<P: AsRef<RepoPath>>(
"Path '{ui_path}' exists but access is denied: {err}" "Path '{ui_path}' exists but access is denied: {err}"
)?; )?;
} }
MaterializedTreeValue::File { mut reader, .. } => { MaterializedTreeValue::File(mut file) => {
io::copy(&mut reader, &mut ui.stdout_formatter().as_mut())?; io::copy(&mut file.reader, &mut ui.stdout_formatter().as_mut())?;
} }
MaterializedTreeValue::FileConflict { contents, .. } => { MaterializedTreeValue::FileConflict(file) => {
materialize_merge_result(&contents, &mut ui.stdout_formatter())?; materialize_merge_result(
&file.contents,
workspace_command.env().conflict_marker_style(),
&mut ui.stdout_formatter(),
)?;
} }
MaterializedTreeValue::OtherConflict { id } => { MaterializedTreeValue::OtherConflict { id } => {
ui.stdout_formatter().write_all(id.describe().as_bytes())?; ui.stdout_formatter().write_all(id.describe().as_bytes())?;

View File

@ -12,11 +12,17 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io;
use std::io::Write as _;
use jj_lib::working_copy::SnapshotOptions; use indoc::writedoc;
use itertools::Itertools as _;
use jj_lib::repo_path::RepoPathUiConverter;
use jj_lib::working_copy::SnapshotStats;
use jj_lib::working_copy::UntrackedReason;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::print_untracked_files;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::ui::Ui; use crate::ui::Ui;
@ -33,7 +39,7 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct FileTrackArgs { pub(crate) struct FileTrackArgs {
/// Paths to track /// Paths to track
#[arg(required = true, value_hint = clap::ValueHint::AnyPath)] #[arg(required = true, value_name = "FILESETS", value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>, paths: Vec<String>,
} }
@ -43,26 +49,73 @@ pub(crate) fn cmd_file_track(
command: &CommandHelper, command: &CommandHelper,
args: &FileTrackArgs, args: &FileTrackArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let (mut workspace_command, auto_stats) = command.workspace_helper_with_stats(ui)?;
let matcher = workspace_command let matcher = workspace_command
.parse_file_patterns(ui, &args.paths)? .parse_file_patterns(ui, &args.paths)?
.to_matcher(); .to_matcher();
let options = workspace_command.snapshot_options_with_start_tracking_matcher(&matcher)?;
let mut tx = workspace_command.start_transaction().into_inner(); let mut tx = workspace_command.start_transaction().into_inner();
let base_ignores = workspace_command.base_ignores()?;
let (mut locked_ws, _wc_commit) = workspace_command.start_working_copy_mutation()?; let (mut locked_ws, _wc_commit) = workspace_command.start_working_copy_mutation()?;
locked_ws.locked_wc().snapshot(&SnapshotOptions { let (_tree_id, track_stats) = locked_ws.locked_wc().snapshot(&options)?;
base_ignores, let num_rebased = tx.repo_mut().rebase_descendants()?;
fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
start_tracking_matcher: &matcher,
max_new_file_size: command.settings().max_new_file_size()?,
})?;
let num_rebased = tx.repo_mut().rebase_descendants(command.settings())?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
let repo = tx.commit("track paths"); let repo = tx.commit("track paths")?;
locked_ws.finish(repo.op_id().clone())?; locked_ws.finish(repo.op_id().clone())?;
print_track_snapshot_stats(
ui,
auto_stats,
track_stats,
workspace_command.env().path_converter(),
)?;
Ok(())
}
pub fn print_track_snapshot_stats(
ui: &Ui,
auto_stats: SnapshotStats,
track_stats: SnapshotStats,
path_converter: &RepoPathUiConverter,
) -> io::Result<()> {
let mut merged_untracked_paths = auto_stats.untracked_paths;
for (path, reason) in track_stats
.untracked_paths
.into_iter()
// focus on files that are now tracked with `file track`
.filter(|(_, reason)| !matches!(reason, UntrackedReason::FileNotAutoTracked))
{
// if the path was previously rejected because it wasn't tracked, update its
// reason
merged_untracked_paths.insert(path, reason);
}
print_untracked_files(ui, &merged_untracked_paths, path_converter)?;
let (large_files, sizes): (Vec<_>, Vec<_>) = merged_untracked_paths
.iter()
.filter_map(|(path, reason)| match reason {
UntrackedReason::FileTooLarge { size, .. } => Some((path, *size)),
UntrackedReason::FileNotAutoTracked => None,
})
.unzip();
if let Some(size) = sizes.iter().max() {
let large_files_list = large_files
.iter()
.map(|path| path_converter.format_file_path(path))
.join(" ");
writedoc!(
ui.hint_default(),
r"
This is to prevent large files from being added by accident. You can fix this by:
- Adding the file to `.gitignore`
- Run `jj config set --repo snapshot.max-new-file-size {size}`
This will increase the maximum file size allowed for new files, in this repository only.
- Run `jj --config snapshot.max-new-file-size={size} file track {large_files_list}`
This will increase the maximum file size allowed for new files, for this command only.
"
)?;
}
Ok(()) Ok(())
} }

View File

@ -12,18 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io::Write as _;
use itertools::Itertools; use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::merge::Merge; use jj_lib::merge::Merge;
use jj_lib::merged_tree::MergedTreeBuilder; use jj_lib::merged_tree::MergedTreeBuilder;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use jj_lib::working_copy::SnapshotOptions;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::print_snapshot_stats;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error_with_hint; use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Stop tracking specified paths in the working copy /// Stop tracking specified paths in the working copy
@ -33,7 +35,12 @@ pub(crate) struct FileUntrackArgs {
/// ///
/// The paths could be ignored via a .gitignore or .git/info/exclude (in /// The paths could be ignored via a .gitignore or .git/info/exclude (in
/// colocated repos). /// colocated repos).
#[arg(required = true, value_hint = clap::ValueHint::AnyPath)] #[arg(
required = true,
value_name = "FILESETS",
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::all_revision_files),
)]
paths: Vec<String>, paths: Vec<String>,
} }
@ -48,10 +55,11 @@ pub(crate) fn cmd_file_untrack(
let matcher = workspace_command let matcher = workspace_command
.parse_file_patterns(ui, &args.paths)? .parse_file_patterns(ui, &args.paths)?
.to_matcher(); .to_matcher();
let auto_tracking_matcher = workspace_command.auto_tracking_matcher(ui)?;
let options =
workspace_command.snapshot_options_with_start_tracking_matcher(&auto_tracking_matcher)?;
let mut tx = workspace_command.start_transaction().into_inner(); let mut tx = workspace_command.start_transaction().into_inner();
let base_ignores = workspace_command.base_ignores()?;
let auto_tracking_matcher = workspace_command.auto_tracking_matcher(ui)?;
let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?; let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?;
// Create a new tree without the unwanted files // Create a new tree without the unwanted files
let mut tree_builder = MergedTreeBuilder::new(wc_commit.tree_id().clone()); let mut tree_builder = MergedTreeBuilder::new(wc_commit.tree_id().clone());
@ -62,20 +70,14 @@ pub(crate) fn cmd_file_untrack(
let new_tree_id = tree_builder.write_tree(&store)?; let new_tree_id = tree_builder.write_tree(&store)?;
let new_commit = tx let new_commit = tx
.repo_mut() .repo_mut()
.rewrite_commit(command.settings(), &wc_commit) .rewrite_commit(&wc_commit)
.set_tree_id(new_tree_id) .set_tree_id(new_tree_id)
.write()?; .write()?;
// Reset the working copy to the new commit // Reset the working copy to the new commit
locked_ws.locked_wc().reset(&new_commit)?; locked_ws.locked_wc().reset(&new_commit)?;
// Commit the working copy again so we can inform the user if paths couldn't be // Commit the working copy again so we can inform the user if paths couldn't be
// untracked because they're not ignored. // untracked because they're not ignored.
let wc_tree_id = locked_ws.locked_wc().snapshot(&SnapshotOptions { let (wc_tree_id, stats) = locked_ws.locked_wc().snapshot(&options)?;
base_ignores,
fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
start_tracking_matcher: &auto_tracking_matcher,
max_new_file_size: command.settings().max_new_file_size()?,
})?;
if wc_tree_id != *new_commit.tree_id() { if wc_tree_id != *new_commit.tree_id() {
let wc_tree = store.get_root_tree(&wc_tree_id)?; let wc_tree = store.get_root_tree(&wc_tree_id)?;
let added_back = wc_tree.entries_matching(matcher.as_ref()).collect_vec(); let added_back = wc_tree.entries_matching(matcher.as_ref()).collect_vec();
@ -103,11 +105,12 @@ Make sure they're ignored, then try again.",
locked_ws.locked_wc().reset(&new_commit)?; locked_ws.locked_wc().reset(&new_commit)?;
} }
} }
let num_rebased = tx.repo_mut().rebase_descendants(command.settings())?; let num_rebased = tx.repo_mut().rebase_descendants()?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
let repo = tx.commit("untrack paths"); let repo = tx.commit("untrack paths")?;
locked_ws.finish(repo.op_id().clone())?; locked_ws.finish(repo.op_id().clone())?;
print_snapshot_stats(ui, &stats, workspace_command.env().path_converter())?;
Ok(()) Ok(())
} }

View File

@ -13,35 +13,26 @@
// limitations under the License. // limitations under the License.
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::io::Write as _;
use std::io::Write; use std::path::Path;
use std::process::Stdio; use std::process::Stdio;
use std::sync::mpsc::channel;
use futures::StreamExt; use clap_complete::ArgValueCompleter;
use itertools::Itertools; use itertools::Itertools as _;
use jj_lib::backend::BackendError;
use jj_lib::backend::CommitId; use jj_lib::backend::CommitId;
use jj_lib::backend::FileId; use jj_lib::backend::FileId;
use jj_lib::backend::TreeValue;
use jj_lib::fileset; use jj_lib::fileset;
use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetDiagnostics;
use jj_lib::fileset::FilesetExpression; use jj_lib::fileset::FilesetExpression;
use jj_lib::matchers::EverythingMatcher; use jj_lib::fix::fix_files;
use jj_lib::fix::FileToFix;
use jj_lib::fix::FixError;
use jj_lib::fix::ParallelFileFixer;
use jj_lib::matchers::Matcher; use jj_lib::matchers::Matcher;
use jj_lib::merged_tree::MergedTree;
use jj_lib::merged_tree::MergedTreeBuilder;
use jj_lib::merged_tree::TreeDiffEntry;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPathBuf;
use jj_lib::repo_path::RepoPathUiConverter; use jj_lib::repo_path::RepoPathUiConverter;
use jj_lib::revset::RevsetExpression; use jj_lib::settings::UserSettings;
use jj_lib::revset::RevsetIteratorExt;
use jj_lib::store::Store; use jj_lib::store::Store;
use jj_lib::tree::Tree; use pollster::FutureExt as _;
use pollster::FutureExt;
use rayon::iter::IntoParallelIterator;
use rayon::prelude::ParallelIterator;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
@ -49,7 +40,7 @@ use crate::cli_util::RevisionArg;
use crate::command_error::config_error; use crate::command_error::config_error;
use crate::command_error::print_parse_diagnostics; use crate::command_error::print_parse_diagnostics;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::config::to_toml_value; use crate::complete;
use crate::config::CommandNameAndArgs; use crate::config::CommandNameAndArgs;
use crate::ui::Ui; use crate::ui::Ui;
@ -84,6 +75,9 @@ use crate::ui::Ui;
/// empty, no files will be affected by the tool. If there are multiple /// empty, no files will be affected by the tool. If there are multiple
/// patterns, the tool is applied only once to each file in the union of the /// patterns, the tool is applied only once to each file in the union of the
/// patterns. /// patterns.
/// - `enabled`: Enables or disables the tool. If omitted, the tool is enabled.
/// This is useful for defining disabled tools in user configuration that can
/// be enabled in individual repositories with one config setting.
/// ///
/// For example, the following configuration defines how two code formatters /// For example, the following configuration defines how two code formatters
/// (`clang-format` and `black`) will apply to three different file extensions /// (`clang-format` and `black`) will apply to three different file extensions
@ -104,30 +98,21 @@ use crate::ui::Ui;
/// currently unspecified, and may change between releases. If two tools affect /// currently unspecified, and may change between releases. If two tools affect
/// the same file, the second tool to run will receive its input from the /// the same file, the second tool to run will receive its input from the
/// output of the first tool. /// output of the first tool.
///
/// There is also a deprecated configuration schema that defines a single
/// command that will affect all changed files in the specified revisions. For
/// example, the following configuration would apply the Rust formatter to all
/// changed files (whether they are Rust files or not):
///
/// ```toml
/// [fix]
/// tool-command = ["rustfmt", "--emit", "stdout"]
/// ```
///
/// The tool defined by `tool-command` acts as if it was the first entry in
/// `fix.tools`, and uses `pattern = "all()"``. Support for `tool-command`
/// will be removed in a future version.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)] #[command(verbatim_doc_comment)]
pub(crate) struct FixArgs { pub(crate) struct FixArgs {
/// Fix files in the specified revision(s) and their descendants. If no /// Fix files in the specified revision(s) and their descendants. If no
/// revisions are specified, this defaults to the `revsets.fix` setting, or /// revisions are specified, this defaults to the `revsets.fix` setting, or
/// `reachable(@, mutable())` if it is not set. /// `reachable(@, mutable())` if it is not set.
#[arg(long, short)] #[arg(
long,
short,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
source: Vec<RevisionArg>, source: Vec<RevisionArg>,
/// Fix only these paths /// Fix only these paths
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(value_name = "FILESETS", value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>, paths: Vec<String>,
/// Fix unchanged files in addition to changed ones. If no paths are /// Fix unchanged files in addition to changed ones. If no paths are
/// specified, all files in the repo will be fixed. /// specified, all files in the repo will be fixed.
@ -142,9 +127,10 @@ pub(crate) fn cmd_fix(
args: &FixArgs, args: &FixArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let tools_config = get_tools_config(ui, command.settings().config())?; let workspace_root = workspace_command.workspace_root().to_owned();
let tools_config = get_tools_config(ui, workspace_command.settings())?;
let root_commits: Vec<CommitId> = if args.source.is_empty() { let root_commits: Vec<CommitId> = if args.source.is_empty() {
let revs = command.settings().config().get_string("revsets.fix")?; let revs = workspace_command.settings().get_string("revsets.fix")?;
workspace_command.parse_revset(ui, &RevisionArg::from(revs))? workspace_command.parse_revset(ui, &RevisionArg::from(revs))?
} else { } else {
workspace_command.parse_union_revsets(ui, &args.source)? workspace_command.parse_union_revsets(ui, &args.source)?
@ -157,240 +143,102 @@ pub(crate) fn cmd_fix(
.to_matcher(); .to_matcher();
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let mut parallel_fixer = ParallelFileFixer::new(|store, file_to_fix| {
// Collect all of the unique `ToolInput`s we're going to use. Tools should be fix_one_file(&workspace_root, &tools_config, store, file_to_fix)
// deterministic, and should not consider outside information, so it is safe to });
// deduplicate inputs that correspond to multiple files or commits. This is let summary = fix_files(
// typically more efficient, but it does prevent certain use cases like root_commits,
// providing commit IDs as inputs to be inserted into files. We also need to &matcher,
// record the mapping between tool inputs and paths/commits, to efficiently args.include_unchanged_files,
// rewrite the commits later. tx.repo_mut(),
// &mut parallel_fixer,
// If a path is being fixed in a particular commit, it must also be fixed in all
// that commit's descendants. We do this as a way of propagating changes,
// under the assumption that it is more useful than performing a rebase and
// risking merge conflicts. In the case of code formatters, rebasing wouldn't
// reliably produce well formatted code anyway. Deduplicating inputs helps
// to prevent quadratic growth in the number of tool executions required for
// doing this in long chains of commits with disjoint sets of modified files.
let commits: Vec<_> = RevsetExpression::commits(root_commits.clone())
.descendants()
.evaluate_programmatic(tx.base_repo().as_ref())?
.iter()
.commits(tx.repo().store())
.try_collect()?;
let mut unique_tool_inputs: HashSet<ToolInput> = HashSet::new();
let mut commit_paths: HashMap<CommitId, HashSet<RepoPathBuf>> = HashMap::new();
for commit in commits.iter().rev() {
let mut paths: HashSet<RepoPathBuf> = HashSet::new();
// If --include-unchanged-files, we always fix every matching file in the tree.
// Otherwise, we fix the matching changed files in this commit, plus any that
// were fixed in ancestors, so we don't lose those changes. We do this
// instead of rebasing onto those changes, to avoid merge conflicts.
let parent_tree = if args.include_unchanged_files {
MergedTree::resolved(Tree::empty(tx.repo().store().clone(), RepoPathBuf::root()))
} else {
for parent_id in commit.parent_ids() {
if let Some(parent_paths) = commit_paths.get(parent_id) {
paths.extend(parent_paths.iter().cloned());
}
}
commit.parent_tree(tx.repo())?
};
// TODO: handle copy tracking
let mut diff_stream = parent_tree.diff_stream(&commit.tree()?, &matcher);
async {
while let Some(TreeDiffEntry {
path: repo_path,
values,
}) = diff_stream.next().await
{
let (_before, after) = values?;
// Deleted files have no file content to fix, and they have no terms in `after`,
// so we don't add any tool inputs for them. Conflicted files produce one tool
// input for each side of the conflict.
for term in after.into_iter().flatten() {
// We currently only support fixing the content of normal files, so we skip
// directories and symlinks, and we ignore the executable bit.
if let TreeValue::File { id, executable: _ } = term {
// TODO: Skip the file if its content is larger than some configured size,
// preferably without actually reading it yet.
let tool_input = ToolInput {
file_id: id.clone(),
repo_path: repo_path.clone(),
};
unique_tool_inputs.insert(tool_input.clone());
paths.insert(repo_path.clone());
}
}
}
Ok::<(), BackendError>(())
}
.block_on()?;
commit_paths.insert(commit.id().clone(), paths);
}
// Run the configured tool on all of the chosen inputs.
let fixed_file_ids = fix_file_ids(
tx.repo().store().as_ref(),
&tools_config,
&unique_tool_inputs,
)?;
// Substitute the fixed file IDs into all of the affected commits. Currently,
// fixes cannot delete or rename files, change the executable bit, or modify
// other parts of the commit like the description.
let mut num_checked_commits = 0;
let mut num_fixed_commits = 0;
tx.repo_mut().transform_descendants(
command.settings(),
root_commits.iter().cloned().collect_vec(),
|mut rewriter| {
// TODO: Build the trees in parallel before `transform_descendants()` and only
// keep the tree IDs in memory, so we can pass them to the rewriter.
let repo_paths = commit_paths.get(rewriter.old_commit().id()).unwrap();
let old_tree = rewriter.old_commit().tree()?;
let mut tree_builder = MergedTreeBuilder::new(old_tree.id().clone());
let mut changes = 0;
for repo_path in repo_paths {
let old_value = old_tree.path_value(repo_path)?;
let new_value = old_value.map(|old_term| {
if let Some(TreeValue::File { id, executable }) = old_term {
let tool_input = ToolInput {
file_id: id.clone(),
repo_path: repo_path.clone(),
};
if let Some(new_id) = fixed_file_ids.get(&tool_input) {
return Some(TreeValue::File {
id: new_id.clone(),
executable: *executable,
});
}
}
old_term.clone()
});
if new_value != old_value {
tree_builder.set_or_remove(repo_path.clone(), new_value);
changes += 1;
}
}
num_checked_commits += 1;
if changes > 0 {
num_fixed_commits += 1;
let new_tree = tree_builder.write_tree(rewriter.mut_repo().store())?;
let builder = rewriter.reparent(command.settings())?;
builder.set_tree_id(new_tree).write()?;
}
Ok(())
},
)?; )?;
writeln!( writeln!(
ui.status(), ui.status(),
"Fixed {num_fixed_commits} commits of {num_checked_commits} checked." "Fixed {} commits of {} checked.",
summary.num_fixed_commits,
summary.num_checked_commits
)?; )?;
tx.finish(ui, format!("fixed {num_fixed_commits} commits")) tx.finish(ui, format!("fixed {} commits", summary.num_fixed_commits))
} }
/// Represents the API between `jj fix` and the tools it runs. /// Invokes all matching tools (if any) to file_to_fix. If the content is
// TODO: Add the set of changed line/byte ranges, so those can be passed into code formatters via /// successfully transformed the new content is written and the new FileId is
// flags. This will help avoid introducing unrelated changes when working on code with out of date /// returned. Returns None if the content is unchanged.
// formatting. ///
#[derive(PartialEq, Eq, Hash, Clone)] /// The matching tools are invoked in order, with the result of one tool feeding
struct ToolInput { /// into the next tool. Returns FixError if there is an error reading or writing
/// File content is the primary input, provided on the tool's standard /// the file. However, if a tool invocation fails for whatever reason, the tool
/// input. We use the `FileId` as a placeholder here, so we can hold all /// is simply skipped and we proceed to invoke the next tool (this is
/// the inputs in memory without also holding all the content at once. /// indistinguishable from succeeding with no changes).
file_id: FileId,
/// The path is provided to allow passing it into the tool so it can
/// potentially:
/// - Choose different behaviors for different file names, extensions, etc.
/// - Update parts of the file's content that should be derived from the
/// file's path.
repo_path: RepoPathBuf,
}
/// Applies `run_tool()` to the inputs and stores the resulting file content.
/// ///
/// Returns a map describing the subset of `tool_inputs` that resulted in
/// changed file content. Failures when handling an input will cause it to be
/// omitted from the return value, which is indistinguishable from succeeding
/// with no changes.
/// TODO: Better error handling so we can tell the user what went wrong with /// TODO: Better error handling so we can tell the user what went wrong with
/// each failed input. /// each failed input.
fn fix_file_ids<'a>( fn fix_one_file(
store: &Store, workspace_root: &Path,
tools_config: &ToolsConfig, tools_config: &ToolsConfig,
tool_inputs: &'a HashSet<ToolInput>, store: &Store,
) -> Result<HashMap<&'a ToolInput, FileId>, CommandError> { file_to_fix: &FileToFix,
let (updates_tx, updates_rx) = channel(); ) -> Result<Option<FileId>, FixError> {
// TODO: Switch to futures, or document the decision not to. We don't need let mut matching_tools = tools_config
// threads unless the threads will be doing more than waiting for pipes. .tools
tool_inputs.into_par_iter().try_for_each_init( .iter()
|| updates_tx.clone(), .filter(|tool_config| tool_config.matcher.matches(&file_to_fix.repo_path))
|updates_tx, tool_input| -> Result<(), CommandError> { .peekable();
let mut matching_tools = tools_config if matching_tools.peek().is_some() {
.tools // The first matching tool gets its input from the committed file, and any
.iter() // subsequent matching tool gets its input from the previous matching tool's
.filter(|tool_config| tool_config.matcher.matches(&tool_input.repo_path)) // output.
.peekable(); let mut old_content = vec![];
if matching_tools.peek().is_some() { let mut read = store.read_file(&file_to_fix.repo_path, &file_to_fix.file_id)?;
// The first matching tool gets its input from the committed file, and any read.read_to_end(&mut old_content)?;
// subsequent matching tool gets its input from the previous matching tool's let new_content = matching_tools.fold(old_content.clone(), |prev_content, tool_config| {
// output. match run_tool(
let mut old_content = vec![]; workspace_root,
let mut read = store.read_file(&tool_input.repo_path, &tool_input.file_id)?; &tool_config.command,
read.read_to_end(&mut old_content)?; file_to_fix,
let new_content = &prev_content,
matching_tools.fold(old_content.clone(), |prev_content, tool_config| { ) {
match run_tool(&tool_config.command, tool_input, &prev_content) { Ok(next_content) => next_content,
Ok(next_content) => next_content, // TODO: Because the stderr is passed through, this isn't always failing
// TODO: Because the stderr is passed through, this isn't always failing // silently, but it should do something better will the exit code, tool
// silently, but it should do something better will the exit code, tool // name, etc.
// name, etc. Err(_) => prev_content,
Err(_) => prev_content,
}
});
if new_content != old_content {
// TODO: send futures back over channel
let new_file_id = store
.write_file(&tool_input.repo_path, &mut new_content.as_slice())
.block_on()?;
updates_tx.send((tool_input, new_file_id)).unwrap();
}
} }
Ok(()) });
}, if new_content != old_content {
)?; // TODO: send futures back over channel
drop(updates_tx); let new_file_id = store
let mut result = HashMap::new(); .write_file(&file_to_fix.repo_path, &mut new_content.as_slice())
while let Ok((tool_input, new_file_id)) = updates_rx.recv() { .block_on()?;
result.insert(tool_input, new_file_id); return Ok(Some(new_file_id));
}
} }
Ok(result) Ok(None)
} }
/// Runs the `tool_command` to fix the given file content. /// Runs the `tool_command` to fix the given file content.
/// ///
/// The `old_content` is assumed to be that of the `tool_input`'s `FileId`, but /// The `old_content` is assumed to be that of the `file_to_fix`'s `FileId`, but
/// this is not verified. /// this is not verified.
/// ///
/// Returns the new file content, whose value will be the same as `old_content` /// Returns the new file content, whose value will be the same as `old_content`
/// unless the command introduced changes. Returns `None` if there were any /// unless the command introduced changes. Returns `None` if there were any
/// failures when starting, stopping, or communicating with the subprocess. /// failures when starting, stopping, or communicating with the subprocess.
fn run_tool( fn run_tool(
workspace_root: &Path,
tool_command: &CommandNameAndArgs, tool_command: &CommandNameAndArgs,
tool_input: &ToolInput, file_to_fix: &FileToFix,
old_content: &[u8], old_content: &[u8],
) -> Result<Vec<u8>, ()> { ) -> Result<Vec<u8>, ()> {
// TODO: Pipe stderr so we can tell the user which commit, file, and tool it is // TODO: Pipe stderr so we can tell the user which commit, file, and tool it is
// associated with. // associated with.
let mut vars: HashMap<&str, &str> = HashMap::new(); let mut vars: HashMap<&str, &str> = HashMap::new();
vars.insert("path", tool_input.repo_path.as_internal_file_string()); vars.insert("path", file_to_fix.repo_path.as_internal_file_string());
let mut child = tool_command let mut command = tool_command.to_command_with_variables(&vars);
.to_command_with_variables(&vars) tracing::debug!(?command, ?file_to_fix.repo_path, "spawning fix tool");
let mut child = command
.current_dir(workspace_root)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
@ -403,6 +251,7 @@ fn run_tool(
Some(child.wait_with_output().or(Err(()))) Some(child.wait_with_output().or(Err(())))
}) })
.unwrap()?; .unwrap()?;
tracing::debug!(?command, ?output.status, "fix tool exited:");
if output.status.success() { if output.status.success() {
Ok(output.stdout) Ok(output.stdout)
} else { } else {
@ -416,6 +265,8 @@ struct ToolConfig {
command: CommandNameAndArgs, command: CommandNameAndArgs,
/// The matcher that determines if this tool matches a file. /// The matcher that determines if this tool matches a file.
matcher: Box<dyn Matcher>, matcher: Box<dyn Matcher>,
/// Whether the tool is enabled
enabled: bool,
// TODO: Store the `name` field here and print it with the command's stderr, to clearly // TODO: Store the `name` field here and print it with the command's stderr, to clearly
// associate any errors/warnings with the tool and its configuration entry. // associate any errors/warnings with the tool and its configuration entry.
} }
@ -433,83 +284,59 @@ struct ToolsConfig {
struct RawToolConfig { struct RawToolConfig {
command: CommandNameAndArgs, command: CommandNameAndArgs,
patterns: Vec<String>, patterns: Vec<String>,
#[serde(default = "default_tool_enabled")]
enabled: bool,
}
fn default_tool_enabled() -> bool {
true
} }
/// Parses the `fix.tools` config table. /// Parses the `fix.tools` config table.
/// ///
/// Parses the deprecated `fix.tool-command` config as if it was the first entry
/// in `fix.tools`.
///
/// Fails if any of the commands or patterns are obviously unusable, but does /// Fails if any of the commands or patterns are obviously unusable, but does
/// not check for issues that might still occur later like missing executables. /// not check for issues that might still occur later like missing executables.
/// This is a place where we could fail earlier in some cases, though. /// This is a place where we could fail earlier in some cases, though.
fn get_tools_config(ui: &mut Ui, config: &config::Config) -> Result<ToolsConfig, CommandError> { fn get_tools_config(ui: &mut Ui, settings: &UserSettings) -> Result<ToolsConfig, CommandError> {
let mut tools_config = ToolsConfig { tools: Vec::new() }; let mut tools: Vec<ToolConfig> = settings
// TODO: Remove this block of code and associated documentation after at least .table_keys("fix.tools")
// one release where the feature is marked deprecated. // Sort keys early so errors are deterministic.
if let Ok(tool_command) = config.get::<CommandNameAndArgs>("fix.tool-command") { .sorted()
// This doesn't change the displayed indices of the `fix.tools` definitions, and .map(|name| -> Result<ToolConfig, CommandError> {
// doesn't have a `name` that could conflict with them. That would matter more let mut diagnostics = FilesetDiagnostics::new();
// if we already had better error handling that made use of the `name`. let tool: RawToolConfig = settings.get(["fix", "tools", name])?;
tools_config.tools.push(ToolConfig { let expression = FilesetExpression::union_all(
command: tool_command, tool.patterns
matcher: Box::new(EverythingMatcher), .iter()
}); .map(|arg| {
fileset::parse(
writeln!( &mut diagnostics,
ui.warning_default(), arg,
r"The `fix.tool-command` config option is deprecated and will be removed in a future version." &RepoPathUiConverter::Fs {
)?; cwd: "".into(),
writeln!( base: "".into(),
ui.hint_default(), },
r###"Replace it with the following: )
[fix.tools.legacy-tool-command] })
command = {} .try_collect()?,
patterns = ["all()"] );
"###, print_parse_diagnostics(ui, &format!("In `fix.tools.{name}`"), &diagnostics)?;
to_toml_value(&config.get::<config::Value>("fix.tool-command").unwrap()).unwrap() Ok(ToolConfig {
)?; command: tool.command,
} matcher: expression.to_matcher(),
if let Ok(tools_table) = config.get_table("fix.tools") { enabled: tool.enabled,
// Convert the map into a sorted vector early so errors are deterministic.
let mut tools: Vec<ToolConfig> = tools_table
.into_iter()
.sorted_by(|a, b| a.0.cmp(&b.0))
.map(|(name, value)| -> Result<ToolConfig, CommandError> {
let mut diagnostics = FilesetDiagnostics::new();
let tool: RawToolConfig = value.try_deserialize()?;
let expression = FilesetExpression::union_all(
tool.patterns
.iter()
.map(|arg| {
fileset::parse(
&mut diagnostics,
arg,
&RepoPathUiConverter::Fs {
cwd: "".into(),
base: "".into(),
},
)
})
.try_collect()?,
);
print_parse_diagnostics(ui, &format!("In `fix.tools.{name}`"), &diagnostics)?;
Ok(ToolConfig {
command: tool.command,
matcher: expression.to_matcher(),
})
}) })
.try_collect()?; })
tools_config.tools.append(&mut tools); .try_collect()?;
if tools.is_empty() {
return Err(config_error("No `fix.tools` are configured"));
} }
if tools_config.tools.is_empty() { tools.retain(|t| t.enabled);
// TODO: This is not a useful message when one or both fields are present but if tools.is_empty() {
// have the wrong type. After removing `fix.tool-command`, it will be simpler to
// propagate any errors from `config.get_array("fix.tools")`.
Err(config_error( Err(config_error(
"At least one entry of `fix.tools` or `fix.tool-command` is required.".to_string(), "At least one entry of `fix.tools` must be enabled.".to_string(),
)) ))
} else { } else {
Ok(tools_config) Ok(ToolsConfig { tools })
} }
} }

View File

@ -14,18 +14,20 @@
use std::fs; use std::fs;
use std::io; use std::io;
use std::io::Write; use std::io::Write as _;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::path::Path; use std::path::Path;
use std::path::PathBuf;
use jj_lib::git; use jj_lib::git;
use jj_lib::git::GitFetchError; use jj_lib::git::GitFetch;
use jj_lib::git::GitFetchStats; use jj_lib::ref_name::RefNameBuf;
use jj_lib::repo::Repo; use jj_lib::ref_name::RemoteName;
use jj_lib::ref_name::RemoteNameBuf;
use jj_lib::repo::Repo as _;
use jj_lib::str_util::StringPattern; use jj_lib::str_util::StringPattern;
use jj_lib::workspace::Workspace; use jj_lib::workspace::Workspace;
use super::write_repository_level_trunk_alias;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper; use crate::cli_util::WorkspaceCommandHelper;
use crate::command_error::cli_error; use crate::command_error::cli_error;
@ -33,10 +35,9 @@ use crate::command_error::user_error;
use crate::command_error::user_error_with_message; use crate::command_error::user_error_with_message;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commands::git::maybe_add_gitignore; use crate::commands::git::maybe_add_gitignore;
use crate::config::write_config_value_to_file; use crate::git_util::absolute_git_url;
use crate::config::ConfigNamePathBuf; #[cfg(feature = "git2")]
use crate::git_util::get_git_repo; use crate::git_util::print_git2_deprecation_warning;
use crate::git_util::map_git_error;
use crate::git_util::print_git_import_stats; use crate::git_util::print_git_import_stats;
use crate::git_util::with_remote_git_callbacks; use crate::git_util::with_remote_git_callbacks;
use crate::ui::Ui; use crate::ui::Ui;
@ -47,7 +48,9 @@ use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct GitCloneArgs { pub struct GitCloneArgs {
/// URL or path of the Git repo to clone /// URL or path of the Git repo to clone
#[arg(value_hint = clap::ValueHint::DirPath)] ///
/// Local path will be resolved to absolute form.
#[arg(value_hint = clap::ValueHint::Url)]
source: String, source: String,
/// Specifies the target directory for the Jujutsu repository clone. /// Specifies the target directory for the Jujutsu repository clone.
/// If not provided, defaults to a directory named after the last component /// If not provided, defaults to a directory named after the last component
@ -57,7 +60,7 @@ pub struct GitCloneArgs {
destination: Option<String>, destination: Option<String>,
/// Name of the newly created remote /// Name of the newly created remote
#[arg(long = "remote", default_value = "origin")] #[arg(long = "remote", default_value = "origin")]
remote_name: String, remote_name: RemoteNameBuf,
/// Whether or not to colocate the Jujutsu repo with the git repo /// Whether or not to colocate the Jujutsu repo with the git repo
#[arg(long)] #[arg(long)]
colocate: bool, colocate: bool,
@ -66,23 +69,6 @@ pub struct GitCloneArgs {
depth: Option<NonZeroU32>, depth: Option<NonZeroU32>,
} }
fn absolute_git_source(cwd: &Path, source: &str) -> String {
// Git appears to turn URL-like source to absolute path if local git directory
// exits, and fails because '$PWD/https' is unsupported protocol. Since it would
// be tedious to copy the exact git (or libgit2) behavior, we simply assume a
// source containing ':' is a URL, SSH remote, or absolute path with Windows
// drive letter.
if !source.contains(':') && Path::new(source).exists() {
// It's less likely that cwd isn't utf-8, so just fall back to original source.
cwd.join(source)
.into_os_string()
.into_string()
.unwrap_or_else(|_| source.to_owned())
} else {
source.to_owned()
}
}
fn clone_destination_for_source(source: &str) -> Option<&str> { fn clone_destination_for_source(source: &str) -> Option<&str> {
let destination = source.strip_suffix(".git").unwrap_or(source); let destination = source.strip_suffix(".git").unwrap_or(source);
let destination = destination.strip_suffix('/').unwrap_or(destination); let destination = destination.strip_suffix('/').unwrap_or(destination);
@ -108,7 +94,7 @@ pub fn cmd_git_clone(
if command.global_args().at_operation.is_some() { if command.global_args().at_operation.is_some() {
return Err(cli_error("--at-op is not respected")); return Err(cli_error("--at-op is not respected"));
} }
let source = absolute_git_source(command.cwd(), &args.source); let source = absolute_git_url(command.cwd(), &args.source)?;
let wc_path_str = args let wc_path_str = args
.destination .destination
.as_deref() .as_deref()
@ -129,18 +115,18 @@ pub fn cmd_git_clone(
// Canonicalize because fs::remove_dir_all() doesn't seem to like e.g. // Canonicalize because fs::remove_dir_all() doesn't seem to like e.g.
// `/some/path/.` // `/some/path/.`
let canonical_wc_path: PathBuf = wc_path let canonical_wc_path = dunce::canonicalize(&wc_path)
.canonicalize()
.map_err(|err| user_error_with_message(format!("Failed to create {wc_path_str}"), err))?; .map_err(|err| user_error_with_message(format!("Failed to create {wc_path_str}"), err))?;
let clone_result = do_git_clone(
ui, let clone_result = (|| -> Result<_, CommandError> {
command, let workspace_command = init_workspace(ui, command, &canonical_wc_path, args.colocate)?;
args.colocate, #[cfg(feature = "git2")]
args.depth, print_git2_deprecation_warning(ui, workspace_command.settings())?;
remote_name, let mut workspace_command =
&source, configure_remote(ui, command, workspace_command, remote_name, &source)?;
&canonical_wc_path, let default_branch = fetch_new_remote(ui, &mut workspace_command, remote_name, args.depth)?;
); Ok((workspace_command, default_branch))
})();
if clone_result.is_err() { if clone_result.is_err() {
let clean_up_dirs = || -> io::Result<()> { let clean_up_dirs = || -> io::Result<()> {
fs::remove_dir_all(canonical_wc_path.join(".jj"))?; fs::remove_dir_all(canonical_wc_path.join(".jj"))?;
@ -163,30 +149,19 @@ pub fn cmd_git_clone(
} }
} }
let (mut workspace_command, stats) = clone_result?; let (mut workspace_command, default_branch) = clone_result?;
if let Some(default_branch) = &stats.default_branch { if let Some(name) = &default_branch {
// Set repository level `trunk()` alias to the default remote branch. let default_symbol = name.to_remote_symbol(remote_name);
let config_path = workspace_command.repo_path().join("config.toml"); write_repository_level_trunk_alias(ui, workspace_command.repo_path(), default_symbol)?;
write_config_value_to_file(
&ConfigNamePathBuf::from_iter(["revset-aliases", "trunk()"]),
format!("{default_branch}@{remote_name}").into(),
&config_path,
)?;
writeln!(
ui.status(),
"Setting the revset alias \"trunk()\" to \"{default_branch}@{remote_name}\""
)?;
let default_branch_remote_ref = workspace_command let default_branch_remote_ref = workspace_command
.repo() .repo()
.view() .view()
.get_remote_bookmark(default_branch, remote_name); .get_remote_bookmark(default_symbol);
if let Some(commit_id) = default_branch_remote_ref.target.as_normal().cloned() { if let Some(commit_id) = default_branch_remote_ref.target.as_normal().cloned() {
let mut checkout_tx = workspace_command.start_transaction(); let mut checkout_tx = workspace_command.start_transaction();
// For convenience, create local bookmark as Git would do. // For convenience, create local bookmark as Git would do.
checkout_tx checkout_tx.repo_mut().track_remote_bookmark(default_symbol);
.repo_mut()
.track_remote_bookmark(default_branch, remote_name);
if let Ok(commit) = checkout_tx.repo().store().get_commit(&commit_id) { if let Ok(commit) = checkout_tx.repo().store().get_commit(&commit_id) {
checkout_tx.check_out(&commit)?; checkout_tx.check_out(&commit)?;
} }
@ -196,53 +171,65 @@ pub fn cmd_git_clone(
Ok(()) Ok(())
} }
fn do_git_clone( fn init_workspace(
ui: &mut Ui, ui: &Ui,
command: &CommandHelper, command: &CommandHelper,
colocate: bool,
depth: Option<NonZeroU32>,
remote_name: &str,
source: &str,
wc_path: &Path, wc_path: &Path,
) -> Result<(WorkspaceCommandHelper, GitFetchStats), CommandError> { colocate: bool,
) -> Result<WorkspaceCommandHelper, CommandError> {
let settings = command.settings_for_new_workspace(wc_path)?;
let (workspace, repo) = if colocate { let (workspace, repo) = if colocate {
Workspace::init_colocated_git(command.settings(), wc_path)? Workspace::init_colocated_git(&settings, wc_path)?
} else { } else {
Workspace::init_internal_git(command.settings(), wc_path)? Workspace::init_internal_git(&settings, wc_path)?
}; };
let git_repo = get_git_repo(repo.store())?; let workspace_command = command.for_workable_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?;
Ok(workspace_command)
}
fn configure_remote(
ui: &Ui,
command: &CommandHelper,
workspace_command: WorkspaceCommandHelper,
remote_name: &RemoteName,
source: &str,
) -> Result<WorkspaceCommandHelper, CommandError> {
git::add_remote(workspace_command.repo().store(), remote_name, source)?;
// Reload workspace to apply new remote configuration to
// gix::ThreadSafeRepository behind the store.
let workspace = command.load_workspace_at(
workspace_command.workspace_root(),
workspace_command.settings(),
)?;
let op = workspace
.repo_loader()
.load_operation(workspace_command.repo().op_id())?;
let repo = workspace.repo_loader().load_at(&op)?;
command.for_workable_repo(ui, workspace, repo)
}
fn fetch_new_remote(
ui: &Ui,
workspace_command: &mut WorkspaceCommandHelper,
remote_name: &RemoteName,
depth: Option<NonZeroU32>,
) -> Result<Option<RefNameBuf>, CommandError> {
writeln!( writeln!(
ui.status(), ui.status(),
r#"Fetching into new repo in "{}""#, r#"Fetching into new repo in "{}""#,
wc_path.display() workspace_command.workspace_root().display()
)?; )?;
let mut workspace_command = command.for_workable_repo(ui, workspace, repo)?; let git_settings = workspace_command.settings().git_settings()?;
maybe_add_gitignore(&workspace_command)?;
git_repo.remote(remote_name, source).unwrap();
let mut fetch_tx = workspace_command.start_transaction(); let mut fetch_tx = workspace_command.start_transaction();
let mut git_fetch = GitFetch::new(fetch_tx.repo_mut(), &git_settings)?;
let stats = with_remote_git_callbacks(ui, None, |cb| { with_remote_git_callbacks(ui, |cb| {
git::fetch( git_fetch.fetch(remote_name, &[StringPattern::everything()], cb, depth)
fetch_tx.repo_mut(),
&git_repo,
remote_name,
&[StringPattern::everything()],
cb,
&command.settings().git_settings(),
depth,
)
})
.map_err(|err| match err {
GitFetchError::NoSuchRemote(_) => {
panic!("shouldn't happen as we just created the git remote")
}
GitFetchError::GitImportError(err) => CommandError::from(err),
GitFetchError::InternalGitError(err) => map_git_error(err),
GitFetchError::InvalidBranchPattern => {
unreachable!("we didn't provide any globs")
}
})?; })?;
print_git_import_stats(ui, fetch_tx.repo(), &stats.import_stats, true)?; let default_branch =
with_remote_git_callbacks(ui, |cb| git_fetch.get_default_branch(remote_name, cb))?;
let import_stats = git_fetch.import_refs()?;
print_git_import_stats(ui, fetch_tx.repo(), &import_stats, true)?;
fetch_tx.finish(ui, "fetch from git remote into empty repo")?; fetch_tx.finish(ui, "fetch from git remote into empty repo")?;
Ok((workspace_command, stats)) Ok(default_branch)
} }

View File

@ -16,7 +16,7 @@ use jj_lib::git;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::git_util::print_failed_git_export; use crate::git_util::print_git_export_stats;
use crate::ui::Ui; use crate::ui::Ui;
/// Update the underlying Git repo with changes made in the repo /// Update the underlying Git repo with changes made in the repo
@ -30,8 +30,8 @@ pub fn cmd_git_export(
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let failed_refs = git::export_refs(tx.repo_mut())?; let stats = git::export_refs(tx.repo_mut())?;
tx.finish(ui, "export git refs")?; tx.finish(ui, "export git refs")?;
print_failed_git_export(ui, &failed_refs)?; print_git_export_stats(ui, &stats)?;
Ok(()) Ok(())
} }

View File

@ -12,17 +12,29 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use itertools::Itertools; use std::collections::HashSet;
use jj_lib::repo::Repo;
use jj_lib::settings::ConfigResultExt as _; use clap_complete::ArgValueCandidates;
use jj_lib::settings::UserSettings; use itertools::Itertools as _;
use jj_lib::config::ConfigGetResultExt as _;
use jj_lib::git;
use jj_lib::git::GitFetch;
use jj_lib::ref_name::RemoteName;
use jj_lib::repo::Repo as _;
use jj_lib::str_util::StringPattern; use jj_lib::str_util::StringPattern;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper;
use crate::cli_util::WorkspaceCommandTransaction;
use crate::command_error::config_error;
use crate::command_error::user_error;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commands::git::get_single_remote; use crate::commands::git::get_single_remote;
use crate::git_util::get_git_repo; use crate::complete;
use crate::git_util::git_fetch; #[cfg(feature = "git2")]
use crate::git_util::print_git2_deprecation_warning;
use crate::git_util::print_git_import_stats;
use crate::git_util::with_remote_git_callbacks;
use crate::ui::Ui; use crate::ui::Ui;
/// Fetch from a Git remote /// Fetch from a Git remote
@ -34,72 +46,176 @@ pub struct GitFetchArgs {
/// Fetch only some of the branches /// Fetch only some of the branches
/// ///
/// By default, the specified name matches exactly. Use `glob:` prefix to /// By default, the specified name matches exactly. Use `glob:` prefix to
/// expand `*` as a glob. The other wildcard characters aren't supported. /// expand `*` as a glob, e.g. `--branch 'glob:push-*'`. Other wildcard
#[arg(long, short, alias="bookmark", default_value = "glob:*", value_parser = StringPattern::parse)] /// characters such as `?` are *not* supported.
#[arg(
long, short,
alias = "bookmark",
default_value = "glob:*",
value_parser = StringPattern::parse,
add = ArgValueCandidates::new(complete::bookmarks),
)]
branch: Vec<StringPattern>, branch: Vec<StringPattern>,
/// The remote to fetch from (only named remotes are supported, can be /// The remote to fetch from (only named remotes are supported, can be
/// repeated) /// repeated)
#[arg(long = "remote", value_name = "REMOTE")] ///
remotes: Vec<String>, /// This defaults to the `git.fetch` setting. If that is not configured, and
/// if there are multiple remotes, the remote named "origin" will be used.
///
/// By default, the specified remote names matches exactly. Use a [string
/// pattern], e.g. `--remote 'glob:*'`, to select remotes using
/// patterns.
///
/// [string pattern]:
/// https://jj-vcs.github.io/jj/latest/revsets#string-patterns
#[arg(
long = "remote",
value_name = "REMOTE",
value_parser = StringPattern::parse,
add = ArgValueCandidates::new(complete::git_remotes),
)]
remotes: Vec<StringPattern>,
/// Fetch from all remotes /// Fetch from all remotes
#[arg(long, conflicts_with = "remotes")] #[arg(long, conflicts_with = "remotes")]
all_remotes: bool, all_remotes: bool,
} }
#[tracing::instrument(skip(ui, command))] #[tracing::instrument(skip_all)]
pub fn cmd_git_fetch( pub fn cmd_git_fetch(
ui: &mut Ui, ui: &mut Ui,
command: &CommandHelper, command: &CommandHelper,
args: &GitFetchArgs, args: &GitFetchArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let git_repo = get_git_repo(workspace_command.repo().store())?; let remote_patterns = if args.all_remotes {
let remotes = if args.all_remotes { vec![StringPattern::everything()]
get_all_remotes(&git_repo)?
} else if args.remotes.is_empty() { } else if args.remotes.is_empty() {
get_default_fetch_remotes(ui, command.settings(), &git_repo)? get_default_fetch_remotes(ui, &workspace_command)?
} else { } else {
args.remotes.clone() args.remotes.clone()
}; };
let all_remotes = git::get_all_remote_names(workspace_command.repo().store())?;
let mut matching_remotes = HashSet::new();
for pattern in remote_patterns {
let remotes = all_remotes
.iter()
.filter(|r| pattern.matches(r.as_str()))
.collect_vec();
if remotes.is_empty() {
writeln!(ui.warning_default(), "No git remotes matching '{pattern}'")?;
} else {
matching_remotes.extend(remotes);
}
}
if matching_remotes.is_empty() {
return Err(user_error("No git remotes to push"));
}
let remotes = matching_remotes
.iter()
.map(|r| r.as_ref())
.sorted()
.collect_vec();
#[cfg(feature = "git2")]
print_git2_deprecation_warning(ui, workspace_command.settings())?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
git_fetch(ui, &mut tx, &git_repo, &remotes, &args.branch)?; do_git_fetch(ui, &mut tx, &remotes, &args.branch)?;
tx.finish( tx.finish(
ui, ui,
format!("fetch from git remote(s) {}", remotes.iter().join(",")), format!(
"fetch from git remote(s) {}",
remotes.iter().map(|n| n.as_symbol()).join(",")
),
)?; )?;
Ok(()) Ok(())
} }
const DEFAULT_REMOTE: &str = "origin"; const DEFAULT_REMOTE: &RemoteName = RemoteName::new("origin");
fn get_default_fetch_remotes( fn get_default_fetch_remotes(
ui: &Ui, ui: &Ui,
settings: &UserSettings, workspace_command: &WorkspaceCommandHelper,
git_repo: &git2::Repository, ) -> Result<Vec<StringPattern>, CommandError> {
) -> Result<Vec<String>, CommandError> {
const KEY: &str = "git.fetch"; const KEY: &str = "git.fetch";
if let Ok(remotes) = settings.config().get(KEY) { let settings = workspace_command.settings();
Ok(remotes) if let Ok(remotes) = settings.get::<Vec<String>>(KEY) {
} else if let Some(remote) = settings.config().get_string(KEY).optional()? { remotes
Ok(vec![remote]) .into_iter()
} else if let Some(remote) = get_single_remote(git_repo)? { .map(|r| parse_remote_pattern(&r))
.try_collect()
} else if let Some(remote) = settings.get_string(KEY).optional()? {
Ok(vec![parse_remote_pattern(&remote)?])
} else if let Some(remote) = get_single_remote(workspace_command.repo().store())? {
// if nothing was explicitly configured, try to guess // if nothing was explicitly configured, try to guess
if remote != DEFAULT_REMOTE { if remote != DEFAULT_REMOTE {
writeln!( writeln!(
ui.hint_default(), ui.hint_default(),
"Fetching from the only existing remote: {remote}" "Fetching from the only existing remote: {remote}",
remote = remote.as_symbol()
)?; )?;
} }
Ok(vec![remote]) Ok(vec![StringPattern::exact(remote)])
} else { } else {
Ok(vec![DEFAULT_REMOTE.to_owned()]) Ok(vec![StringPattern::exact(DEFAULT_REMOTE)])
} }
} }
fn get_all_remotes(git_repo: &git2::Repository) -> Result<Vec<String>, CommandError> { fn parse_remote_pattern(remote: &str) -> Result<StringPattern, CommandError> {
let git_remotes = git_repo.remotes()?; StringPattern::parse(remote).map_err(config_error)
Ok(git_remotes }
.iter()
.filter_map(|x| x.map(ToOwned::to_owned)) fn do_git_fetch(
.collect()) ui: &mut Ui,
tx: &mut WorkspaceCommandTransaction,
remotes: &[&RemoteName],
branch_names: &[StringPattern],
) -> Result<(), CommandError> {
let git_settings = tx.settings().git_settings()?;
let mut git_fetch = GitFetch::new(tx.repo_mut(), &git_settings)?;
for remote_name in remotes {
with_remote_git_callbacks(ui, |callbacks| {
git_fetch.fetch(remote_name, branch_names, callbacks, None)
})?;
}
let import_stats = git_fetch.import_refs()?;
print_git_import_stats(ui, tx.repo(), &import_stats, true)?;
warn_if_branches_not_found(ui, tx, branch_names, remotes)
}
fn warn_if_branches_not_found(
ui: &mut Ui,
tx: &WorkspaceCommandTransaction,
branches: &[StringPattern],
remotes: &[&RemoteName],
) -> Result<(), CommandError> {
for branch in branches {
let matches = remotes.iter().any(|&remote| {
let remote = StringPattern::exact(remote);
tx.repo()
.view()
.remote_bookmarks_matching(branch, &remote)
.next()
.is_some()
|| tx
.base_repo()
.view()
.remote_bookmarks_matching(branch, &remote)
.next()
.is_some()
});
if !matches {
writeln!(
ui.warning_default(),
"No branch matching `{branch}` found on any specified/configured remote",
)?;
}
}
Ok(())
} }

View File

@ -32,11 +32,12 @@ pub fn cmd_git_import(
_args: &GitImportArgs, _args: &GitImportArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let git_settings = workspace_command.settings().git_settings()?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
// In non-colocated repo, Git HEAD will never be moved internally by jj. // In non-colocated repo, Git HEAD will never be moved internally by jj.
// That's why cmd_git_export() doesn't export the HEAD ref. // That's why cmd_git_export() doesn't export the HEAD ref.
git::import_head(tx.repo_mut())?; git::import_head(tx.repo_mut())?;
let stats = git::import_refs(tx.repo_mut(), &command.settings().git_settings())?; let stats = git::import_refs(tx.repo_mut(), &git_settings)?;
print_git_import_stats(ui, tx.repo(), &stats, true)?; print_git_import_stats(ui, tx.repo(), &stats, true)?;
tx.finish(ui, "import git refs")?; tx.finish(ui, "import git refs")?;
Ok(()) Ok(())

View File

@ -12,33 +12,35 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io;
use std::io::Write as _;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str;
use std::sync::Arc; use std::sync::Arc;
use itertools::Itertools as _;
use jj_lib::file_util; use jj_lib::file_util;
use jj_lib::git; use jj_lib::git;
use jj_lib::git::parse_git_ref; use jj_lib::git::parse_git_ref;
use jj_lib::git::RefName; use jj_lib::git::GitRefKind;
use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::ReadonlyRepo;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use jj_lib::view::View;
use jj_lib::workspace::Workspace; use jj_lib::workspace::Workspace;
use crate::cli_util::print_trackable_remote_bookmarks; use super::write_repository_level_trunk_alias;
use crate::cli_util::start_repo_transaction; use crate::cli_util::start_repo_transaction;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper; use crate::cli_util::WorkspaceCommandHelper;
use crate::command_error::cli_error; use crate::command_error::cli_error;
use crate::command_error::internal_error;
use crate::command_error::user_error_with_hint; use crate::command_error::user_error_with_hint;
use crate::command_error::user_error_with_message; use crate::command_error::user_error_with_message;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commands::git::maybe_add_gitignore; use crate::commands::git::maybe_add_gitignore;
use crate::config::write_config_value_to_file;
use crate::config::ConfigNamePathBuf;
use crate::git_util::get_git_repo;
use crate::git_util::is_colocated_git_workspace; use crate::git_util::is_colocated_git_workspace;
use crate::git_util::print_failed_git_export; use crate::git_util::print_git_export_stats;
use crate::git_util::print_git_import_stats; use crate::git_util::print_git_import_stats;
use crate::ui::Ui; use crate::ui::Ui;
@ -92,7 +94,7 @@ pub fn cmd_git_init(
let cwd = command.cwd(); let cwd = command.cwd();
let wc_path = cwd.join(&args.destination); let wc_path = cwd.join(&args.destination);
let wc_path = file_util::create_or_reuse_dir(&wc_path) let wc_path = file_util::create_or_reuse_dir(&wc_path)
.and_then(|_| wc_path.canonicalize()) .and_then(|_| dunce::canonicalize(wc_path))
.map_err(|e| user_error_with_message("Failed to create workspace", e))?; .map_err(|e| user_error_with_message("Failed to create workspace", e))?;
do_init( do_init(
@ -113,7 +115,7 @@ pub fn cmd_git_init(
Ok(()) Ok(())
} }
pub fn do_init( fn do_init(
ui: &mut Ui, ui: &mut Ui,
command: &CommandHelper, command: &CommandHelper,
workspace_root: &Path, workspace_root: &Path,
@ -155,20 +157,20 @@ pub fn do_init(
GitInitMode::Internal GitInitMode::Internal
}; };
let settings = command.settings_for_new_workspace(workspace_root)?;
match &init_mode { match &init_mode {
GitInitMode::Colocate => { GitInitMode::Colocate => {
let (workspace, repo) = let (workspace, repo) = Workspace::init_colocated_git(&settings, workspace_root)?;
Workspace::init_colocated_git(command.settings(), workspace_root)?;
let workspace_command = command.for_workable_repo(ui, workspace, repo)?; let workspace_command = command.for_workable_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?; maybe_add_gitignore(&workspace_command)?;
} }
GitInitMode::External(git_repo_path) => { GitInitMode::External(git_repo_path) => {
let (workspace, repo) = let (workspace, repo) =
Workspace::init_external_git(command.settings(), workspace_root, git_repo_path)?; Workspace::init_external_git(&settings, workspace_root, git_repo_path)?;
// Import refs first so all the reachable commits are indexed in // Import refs first so all the reachable commits are indexed in
// chronological order. // chronological order.
let colocated = is_colocated_git_workspace(&workspace, &repo); let colocated = is_colocated_git_workspace(&workspace, &repo);
let repo = init_git_refs(ui, command, repo, colocated)?; let repo = init_git_refs(ui, repo, command.string_args(), colocated)?;
let mut workspace_command = command.for_workable_repo(ui, workspace, repo)?; let mut workspace_command = command.for_workable_repo(ui, workspace, repo)?;
maybe_add_gitignore(&workspace_command)?; maybe_add_gitignore(&workspace_command)?;
workspace_command.maybe_snapshot(ui)?; workspace_command.maybe_snapshot(ui)?;
@ -187,7 +189,7 @@ pub fn do_init(
print_trackable_remote_bookmarks(ui, workspace_command.repo().view())?; print_trackable_remote_bookmarks(ui, workspace_command.repo().view())?;
} }
GitInitMode::Internal => { GitInitMode::Internal => {
Workspace::init_internal_git(command.settings(), workspace_root)?; Workspace::init_internal_git(&settings, workspace_root)?;
} }
} }
Ok(()) Ok(())
@ -200,31 +202,26 @@ pub fn do_init(
/// moves the Git HEAD to the working copy parent. /// moves the Git HEAD to the working copy parent.
fn init_git_refs( fn init_git_refs(
ui: &mut Ui, ui: &mut Ui,
command: &CommandHelper,
repo: Arc<ReadonlyRepo>, repo: Arc<ReadonlyRepo>,
string_args: &[String],
colocated: bool, colocated: bool,
) -> Result<Arc<ReadonlyRepo>, CommandError> { ) -> Result<Arc<ReadonlyRepo>, CommandError> {
let mut tx = start_repo_transaction(&repo, command.settings(), command.string_args()); let mut git_settings = repo.settings().git_settings()?;
let mut tx = start_repo_transaction(&repo, string_args);
// There should be no old refs to abandon, but enforce it. // There should be no old refs to abandon, but enforce it.
let mut git_settings = command.settings().git_settings();
git_settings.abandon_unreachable_commits = false; git_settings.abandon_unreachable_commits = false;
let stats = git::import_some_refs( let stats = git::import_refs(tx.repo_mut(), &git_settings)?;
tx.repo_mut(), print_git_import_stats(ui, tx.repo(), &stats, false)?;
&git_settings,
// Initial import shouldn't fail because of reserved remote name.
|ref_name| !git::is_reserved_git_remote_ref(ref_name),
)?;
if !tx.repo().has_changes() { if !tx.repo().has_changes() {
return Ok(repo); return Ok(repo);
} }
print_git_import_stats(ui, tx.repo(), &stats, false)?;
if colocated { if colocated {
// If git.auto-local-bookmark = true, local bookmarks could be created for // If git.auto-local-bookmark = true, local bookmarks could be created for
// the imported remote branches. // the imported remote branches.
let failed_refs = git::export_refs(tx.repo_mut())?; let stats = git::export_refs(tx.repo_mut())?;
print_failed_git_export(ui, &failed_refs)?; print_git_export_stats(ui, &stats)?;
} }
let repo = tx.commit("import git refs"); let repo = tx.commit("import git refs")?;
writeln!( writeln!(
ui.status(), ui.status(),
"Done importing changes from the underlying Git repo." "Done importing changes from the underlying Git repo."
@ -237,27 +234,56 @@ pub fn maybe_set_repository_level_trunk_alias(
ui: &Ui, ui: &Ui,
workspace_command: &WorkspaceCommandHelper, workspace_command: &WorkspaceCommandHelper,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let git_repo = get_git_repo(workspace_command.repo().store())?; let git_repo = git::get_git_repo(workspace_command.repo().store())?;
if let Ok(reference) = git_repo.find_reference("refs/remotes/origin/HEAD") { if let Some(reference) = git_repo
if let Some(reference_name) = reference.symbolic_target() { .try_find_reference("refs/remotes/origin/HEAD")
if let Some(RefName::RemoteBranch { .map_err(internal_error)?
branch: default_branch, {
.. if let Some(reference_name) = reference.target().try_name() {
}) = parse_git_ref(reference_name) if let Some((GitRefKind::Bookmark, symbol)) = str::from_utf8(reference_name.as_bstr())
.ok()
.and_then(|name| parse_git_ref(name.as_ref()))
{ {
let config_path = workspace_command.repo_path().join("config.toml"); // TODO: Can we assume the symbolic target points to the same remote?
write_config_value_to_file( let symbol = symbol.name.to_remote_symbol("origin".as_ref());
&ConfigNamePathBuf::from_iter(["revset-aliases", "trunk()"]), write_repository_level_trunk_alias(ui, workspace_command.repo_path(), symbol)?;
format!("{default_branch}@origin").into(),
&config_path,
)?;
writeln!(
ui.status(),
"Setting the revset alias \"trunk()\" to \"{default_branch}@origin\"",
)?;
} }
}; };
}; };
Ok(()) Ok(())
} }
fn print_trackable_remote_bookmarks(ui: &Ui, view: &View) -> io::Result<()> {
let remote_bookmark_symbols = view
.bookmarks()
.filter(|(_, bookmark_target)| bookmark_target.local_target.is_present())
.flat_map(|(name, bookmark_target)| {
bookmark_target
.remote_refs
.into_iter()
.filter(|&(_, remote_ref)| !remote_ref.is_tracked())
.map(move |(remote, _)| name.to_remote_symbol(remote))
})
.collect_vec();
if remote_bookmark_symbols.is_empty() {
return Ok(());
}
if let Some(mut formatter) = ui.status_formatter() {
writeln!(
formatter.labeled("hint").with_heading("Hint: "),
"The following remote bookmarks aren't associated with the existing local bookmarks:"
)?;
for symbol in &remote_bookmark_symbols {
write!(formatter, " ")?;
writeln!(formatter.labeled("bookmark"), "{symbol}")?;
}
writeln!(
formatter.labeled("hint").with_heading("Hint: "),
"Run `jj bookmark track {syms}` to keep local bookmarks updated on future pulls.",
syms = remote_bookmark_symbols.iter().join(" "),
)?;
}
Ok(())
}

View File

@ -12,16 +12,25 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
pub mod clone; mod clone;
pub mod export; mod export;
pub mod fetch; mod fetch;
pub mod import; mod import;
pub mod init; mod init;
pub mod push; mod push;
pub mod remote; mod remote;
pub mod submodule; mod root;
use std::path::Path;
use clap::Subcommand; use clap::Subcommand;
use jj_lib::config::ConfigFile;
use jj_lib::config::ConfigSource;
use jj_lib::git;
use jj_lib::git::UnexpectedGitBackendError;
use jj_lib::ref_name::RemoteNameBuf;
use jj_lib::ref_name::RemoteRefSymbol;
use jj_lib::store::Store;
use self::clone::cmd_git_clone; use self::clone::cmd_git_clone;
use self::clone::GitCloneArgs; use self::clone::GitCloneArgs;
@ -37,8 +46,8 @@ use self::push::cmd_git_push;
use self::push::GitPushArgs; use self::push::GitPushArgs;
use self::remote::cmd_git_remote; use self::remote::cmd_git_remote;
use self::remote::RemoteCommand; use self::remote::RemoteCommand;
use self::submodule::cmd_git_submodule; use self::root::cmd_git_root;
use self::submodule::GitSubmoduleCommand; use self::root::GitRootArgs;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper; use crate::cli_util::WorkspaceCommandHelper;
use crate::command_error::user_error_with_message; use crate::command_error::user_error_with_message;
@ -47,8 +56,13 @@ use crate::ui::Ui;
/// Commands for working with Git remotes and the underlying Git repo /// Commands for working with Git remotes and the underlying Git repo
/// ///
/// For a comparison with Git, including a table of commands, see /// See this [comparison], including a [table of commands].
/// https://martinvonz.github.io/jj/latest/git-comparison/. ///
/// [comparison]:
/// https://jj-vcs.github.io/jj/latest/git-comparison/.
///
/// [table of commands]:
/// https://jj-vcs.github.io/jj/latest/git-command-table
#[derive(Subcommand, Clone, Debug)] #[derive(Subcommand, Clone, Debug)]
pub enum GitCommand { pub enum GitCommand {
Clone(GitCloneArgs), Clone(GitCloneArgs),
@ -59,8 +73,7 @@ pub enum GitCommand {
Push(GitPushArgs), Push(GitPushArgs),
#[command(subcommand)] #[command(subcommand)]
Remote(RemoteCommand), Remote(RemoteCommand),
#[command(subcommand, hide = true)] Root(GitRootArgs),
Submodule(GitSubmoduleCommand),
} }
pub fn cmd_git( pub fn cmd_git(
@ -76,7 +89,7 @@ pub fn cmd_git(
GitCommand::Init(args) => cmd_git_init(ui, command, args), GitCommand::Init(args) => cmd_git_init(ui, command, args),
GitCommand::Push(args) => cmd_git_push(ui, command, args), GitCommand::Push(args) => cmd_git_push(ui, command, args),
GitCommand::Remote(args) => cmd_git_remote(ui, command, args), GitCommand::Remote(args) => cmd_git_remote(ui, command, args),
GitCommand::Submodule(args) => cmd_git_submodule(ui, command, args), GitCommand::Root(args) => cmd_git_root(ui, command, args),
} }
} }
@ -95,10 +108,27 @@ pub fn maybe_add_gitignore(workspace_command: &WorkspaceCommandHelper) -> Result
} }
} }
fn get_single_remote(git_repo: &git2::Repository) -> Result<Option<String>, CommandError> { fn get_single_remote(store: &Store) -> Result<Option<RemoteNameBuf>, UnexpectedGitBackendError> {
let git_remotes = git_repo.remotes()?; let mut names = git::get_all_remote_names(store)?;
Ok(match git_remotes.len() { Ok(match names.len() {
1 => git_remotes.get(0).map(ToOwned::to_owned), 1 => names.pop(),
_ => None, _ => None,
}) })
} }
/// Sets repository level `trunk()` alias to the specified remote symbol.
fn write_repository_level_trunk_alias(
ui: &Ui,
repo_path: &Path,
symbol: RemoteRefSymbol<'_>,
) -> Result<(), CommandError> {
let mut file = ConfigFile::load_or_empty(ConfigSource::Repo, repo_path.join("config.toml"))?;
file.set_value(["revset-aliases", "trunk()"], symbol.to_string())
.expect("initial repo config shouldn't have invalid values");
file.save()?;
writeln!(
ui.status(),
"Setting the revset alias `trunk()` to `{symbol}`",
)?;
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@ -13,19 +13,23 @@
// limitations under the License. // limitations under the License.
use jj_lib::git; use jj_lib::git;
use jj_lib::repo::Repo; use jj_lib::ref_name::RemoteNameBuf;
use jj_lib::repo::Repo as _;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::git_util::get_git_repo; use crate::git_util::absolute_git_url;
use crate::ui::Ui; use crate::ui::Ui;
/// Add a Git remote /// Add a Git remote
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct GitRemoteAddArgs { pub struct GitRemoteAddArgs {
/// The remote's name /// The remote's name
remote: String, remote: RemoteNameBuf,
/// The remote's URL /// The remote's URL or path
///
/// Local path will be resolved to absolute form.
#[arg(value_hint = clap::ValueHint::Url)]
url: String, url: String,
} }
@ -35,8 +39,7 @@ pub fn cmd_git_remote_add(
args: &GitRemoteAddArgs, args: &GitRemoteAddArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo(); let url = absolute_git_url(command.cwd(), &args.url)?;
let git_repo = get_git_repo(repo.store())?; git::add_remote(workspace_command.repo().store(), &args.remote, &url)?;
git::add_remote(&git_repo, &args.remote, &args.url)?;
Ok(()) Ok(())
} }

View File

@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io::Write; use std::io::Write as _;
use jj_lib::repo::Repo; use jj_lib::git;
use jj_lib::repo::Repo as _;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::user_error_with_message;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::git_util::get_git_repo;
use crate::ui::Ui; use crate::ui::Ui;
/// List Git remotes /// List Git remotes
@ -31,16 +32,24 @@ pub fn cmd_git_remote_list(
_args: &GitRemoteListArgs, _args: &GitRemoteListArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo(); let git_repo = git::get_git_repo(workspace_command.repo().store())?;
let git_repo = get_git_repo(repo.store())?; for remote_name in git_repo.remote_names() {
for remote_name in git_repo.remotes()?.iter().flatten() { let remote = match git_repo.try_find_remote(&*remote_name) {
let remote = git_repo.find_remote(remote_name)?; Some(Ok(remote)) => remote,
writeln!( Some(Err(err)) => {
ui.stdout(), return Err(user_error_with_message(
"{} {}", format!("Failed to load configured remote {remote_name}"),
remote_name, err,
remote.url().unwrap_or("<no URL>") ))
)?; }
None => continue, // ignore empty [remote "<name>"] section
};
// TODO: print push url (by default or by some flag)?
let fetch_url = remote
.url(gix::remote::Direction::Fetch)
.map(|url| url.to_bstring())
.unwrap_or_else(|| "<no URL>".into());
writeln!(ui.stdout(), "{remote_name} {fetch_url}")?;
} }
Ok(()) Ok(())
} }

View File

@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
pub mod add; mod add;
pub mod list; mod list;
pub mod remove; mod remove;
pub mod rename; mod rename;
pub mod set_url; mod set_url;
use clap::Subcommand; use clap::Subcommand;

View File

@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use jj_lib::git; use jj_lib::git;
use jj_lib::repo::Repo; use jj_lib::ref_name::RemoteNameBuf;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::git_util::get_git_repo; use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Remove a Git remote and forget its bookmarks /// Remove a Git remote and forget its bookmarks
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct GitRemoteRemoveArgs { pub struct GitRemoteRemoveArgs {
/// The remote's name /// The remote's name
remote: String, #[arg(add = ArgValueCandidates::new(complete::git_remotes))]
remote: RemoteNameBuf,
} }
pub fn cmd_git_remote_remove( pub fn cmd_git_remote_remove(
@ -33,12 +35,10 @@ pub fn cmd_git_remote_remove(
args: &GitRemoteRemoveArgs, args: &GitRemoteRemoveArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
git::remove_remote(tx.repo_mut(), &git_repo, &args.remote)?; git::remove_remote(tx.repo_mut(), &args.remote)?;
if tx.repo().has_changes() { if tx.repo().has_changes() {
tx.finish(ui, format!("remove git remote {}", &args.remote)) tx.finish(ui, format!("remove git remote {}", args.remote.as_symbol()))
} else { } else {
Ok(()) // Do not print "Nothing changed." Ok(()) // Do not print "Nothing changed."
} }

View File

@ -12,21 +12,23 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use jj_lib::git; use jj_lib::git;
use jj_lib::repo::Repo; use jj_lib::ref_name::RemoteNameBuf;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::git_util::get_git_repo; use crate::complete;
use crate::ui::Ui; use crate::ui::Ui;
/// Rename a Git remote /// Rename a Git remote
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct GitRemoteRenameArgs { pub struct GitRemoteRenameArgs {
/// The name of an existing remote /// The name of an existing remote
old: String, #[arg(add = ArgValueCandidates::new(complete::git_remotes))]
old: RemoteNameBuf,
/// The desired name for `old` /// The desired name for `old`
new: String, new: RemoteNameBuf,
} }
pub fn cmd_git_remote_rename( pub fn cmd_git_remote_rename(
@ -35,14 +37,16 @@ pub fn cmd_git_remote_rename(
args: &GitRemoteRenameArgs, args: &GitRemoteRenameArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?; let mut workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let git_repo = get_git_repo(repo.store())?;
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
git::rename_remote(tx.repo_mut(), &git_repo, &args.old, &args.new)?; git::rename_remote(tx.repo_mut(), &args.old, &args.new)?;
if tx.repo().has_changes() { if tx.repo().has_changes() {
tx.finish( tx.finish(
ui, ui,
format!("rename git remote {} to {}", &args.old, &args.new), format!(
"rename git remote {old} to {new}",
old = args.old.as_symbol(),
new = args.new.as_symbol()
),
) )
} else { } else {
Ok(()) // Do not print "Nothing changed." Ok(()) // Do not print "Nothing changed."

View File

@ -12,20 +12,27 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use jj_lib::git; use jj_lib::git;
use jj_lib::repo::Repo; use jj_lib::ref_name::RemoteNameBuf;
use jj_lib::repo::Repo as _;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::git_util::get_git_repo; use crate::complete;
use crate::git_util::absolute_git_url;
use crate::ui::Ui; use crate::ui::Ui;
/// Set the URL of a Git remote /// Set the URL of a Git remote
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct GitRemoteSetUrlArgs { pub struct GitRemoteSetUrlArgs {
/// The remote's name /// The remote's name
remote: String, #[arg(add = ArgValueCandidates::new(complete::git_remotes))]
/// The desired url for `remote` remote: RemoteNameBuf,
/// The desired URL or path for `remote`
///
/// Local path will be resolved to absolute form.
#[arg(value_hint = clap::ValueHint::Url)]
url: String, url: String,
} }
@ -35,8 +42,7 @@ pub fn cmd_git_remote_set_url(
args: &GitRemoteSetUrlArgs, args: &GitRemoteSetUrlArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo(); let url = absolute_git_url(command.cwd(), &args.url)?;
let git_repo = get_git_repo(repo.store())?; git::set_remote_url(workspace_command.repo().store(), &args.remote, &url)?;
git::set_remote_url(&git_repo, &args.remote, &args.url)?;
Ok(()) Ok(())
} }

View File

@ -0,0 +1,44 @@
// Copyright 2025 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::io::Write as _;
use jj_lib::repo::Repo as _;
use tracing::instrument;
use crate::cli_util::CommandHelper;
use crate::command_error::user_error;
use crate::command_error::CommandError;
use crate::ui::Ui;
/// Show the underlying Git directory of a repository using the Git backend
#[derive(clap::Args, Clone, Debug)]
pub struct GitRootArgs {}
#[instrument(skip_all)]
pub fn cmd_git_root(
ui: &mut Ui,
command: &CommandHelper,
_args: &GitRootArgs,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let store = workspace_command.repo().store();
let git_backend = jj_lib::git::get_git_backend(store)?;
let root = git_backend
.git_repo_path()
.to_str()
.ok_or_else(|| user_error("The workspace root is not valid UTF-8"))?;
writeln!(ui.stdout(), "{root}")?;
Ok(())
}

View File

@ -1,91 +0,0 @@
// Copyright 2020-2023 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::io::Write;
use clap::Subcommand;
use jj_lib::backend::TreeValue;
use jj_lib::git::parse_gitmodules;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::command_error::user_error;
use crate::command_error::CommandError;
use crate::ui::Ui;
/// FOR INTERNAL USE ONLY Interact with git submodules
#[derive(Subcommand, Clone, Debug)]
pub enum GitSubmoduleCommand {
/// Print the relevant contents from .gitmodules. For debugging purposes
/// only.
PrintGitmodules(PrintArgs),
}
pub fn cmd_git_submodule(
ui: &mut Ui,
command: &CommandHelper,
subcommand: &GitSubmoduleCommand,
) -> Result<(), CommandError> {
match subcommand {
GitSubmoduleCommand::PrintGitmodules(args) => cmd_submodule_print(ui, command, args),
}
}
// TODO: break everything below into a separate file as soon as there is more
// than one subcommand here.
/// Print debugging info about Git submodules
#[derive(clap::Args, Clone, Debug)]
#[command(hide = true)]
pub struct PrintArgs {
/// Read .gitmodules from the given revision.
#[arg(long, short = 'r', default_value = "@")]
revisions: RevisionArg,
}
fn cmd_submodule_print(
ui: &mut Ui,
command: &CommandHelper,
args: &PrintArgs,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let commit = workspace_command.resolve_single_rev(ui, &args.revisions)?;
let tree = commit.tree()?;
let gitmodules_path = RepoPath::from_internal_string(".gitmodules");
let mut gitmodules_file = match tree.path_value(gitmodules_path)?.into_resolved() {
Ok(None) => {
writeln!(ui.status(), "No submodules!")?;
return Ok(());
}
Ok(Some(TreeValue::File { id, .. })) => repo.store().read_file(gitmodules_path, &id)?,
_ => {
return Err(user_error(".gitmodules is not a file."));
}
};
let submodules = parse_gitmodules(&mut gitmodules_file)?;
for (name, submodule) in submodules {
writeln!(
ui.stdout(),
"name:{}\nurl:{}\npath:{}\n\n",
name,
submodule.url,
submodule.path
)?;
}
Ok(())
}

View File

@ -13,12 +13,12 @@
// limitations under the License. // limitations under the License.
use std::fmt::Write as _; use std::fmt::Write as _;
use std::io::Write; use std::io::Write as _;
use clap::builder::PossibleValue; use clap::builder::PossibleValue;
use clap::builder::StyledStr; use clap::builder::StyledStr;
use crossterm::style::Stylize; use crossterm::style::Stylize as _;
use itertools::Itertools; use itertools::Itertools as _;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
@ -58,11 +58,15 @@ pub(crate) fn cmd_help(
return Ok(()); return Ok(());
} }
let mut args_to_show_help = vec![command.app().get_name()]; let bin_name = command
.string_args()
.first()
.map_or(command.app().get_name(), |name| name.as_ref());
let mut args_to_show_help = vec![bin_name];
args_to_show_help.extend(args.command.iter().map(|s| s.as_str())); args_to_show_help.extend(args.command.iter().map(|s| s.as_str()));
args_to_show_help.push("--help"); args_to_show_help.push("--help");
// TODO: `help log -- -r` will gives an cryptic error, ideally, it should state // TODO: `help log -- -r` will give a cryptic error, ideally, it should state
// that the subcommand `log -r` doesn't exist. // that the subcommand `log -r` doesn't exist.
let help_err = command let help_err = command
.app() .app()
@ -93,13 +97,38 @@ struct Keyword {
// //
// TODO: Find a way to render markdown using ANSI escape codes. // TODO: Find a way to render markdown using ANSI escape codes.
// //
// Maybe we can steal some ideas from https://github.com/martinvonz/jj/pull/3130 // Maybe we can steal some ideas from https://github.com/jj-vcs/jj/pull/3130
const KEYWORDS: &[Keyword] = &[ const KEYWORDS: &[Keyword] = &[
Keyword {
name: "bookmarks",
description: "Named pointers to revisions (similar to Git's branches)",
content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "bookmarks.md")),
},
Keyword {
name: "config",
description: "How and where to set configuration options",
content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "config.md")),
},
Keyword {
name: "filesets",
description: "A functional language for selecting a set of files",
content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "filesets.md")),
},
Keyword {
name: "glossary",
description: "Definitions of various terms",
content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "glossary.md")),
},
Keyword { Keyword {
name: "revsets", name: "revsets",
description: "A functional language for selecting a set of revision", description: "A functional language for selecting a set of revision",
content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "revsets.md")), content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "revsets.md")),
}, },
Keyword {
name: "templates",
description: "A functional language to customize command output",
content: include_str!(concat!("../../", env!("JJ_DOCS_DIR"), "templates.md")),
},
Keyword { Keyword {
name: "tutorial", name: "tutorial",
description: "Show a tutorial to get started with jj", description: "Show a tutorial to get started with jj",
@ -115,7 +144,7 @@ pub fn show_keyword_hint_after_help() -> StyledStr {
let mut ret = StyledStr::new(); let mut ret = StyledStr::new();
writeln!( writeln!(
ret, ret,
"{} list available keywords. Use {} to show help for one of these keywords.", "{} lists available keywords. Use {} to show help for one of these keywords.",
"'jj help --help'".bold(), "'jj help --help'".bold(),
"'jj help -k'".bold(), "'jj help -k'".bold(),
) )

View File

@ -15,11 +15,13 @@
use std::slice; use std::slice;
use clap::ArgGroup; use clap::ArgGroup;
use clap_complete::ArgValueCompleter;
use tracing::instrument; use tracing::instrument;
use crate::cli_util::CommandHelper; use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::complete;
use crate::diff_util::DiffFormatArgs; use crate::diff_util::DiffFormatArgs;
use crate::ui::Ui; use crate::ui::Ui;
@ -34,13 +36,27 @@ use crate::ui::Ui;
#[command(mut_arg("ignore_space_change", |a| a.short('b')))] #[command(mut_arg("ignore_space_change", |a| a.short('b')))]
pub(crate) struct InterdiffArgs { pub(crate) struct InterdiffArgs {
/// Show changes from this revision /// Show changes from this revision
#[arg(long)] #[arg(
long,
short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
from: Option<RevisionArg>, from: Option<RevisionArg>,
/// Show changes to this revision /// Show changes to this revision
#[arg(long)] #[arg(
long,
short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
to: Option<RevisionArg>, to: Option<RevisionArg>,
/// Restrict the diff to these paths /// Restrict the diff to these paths
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(
value_name = "FILESETS",
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::interdiff_files),
)]
paths: Vec<String>, paths: Vec<String>,
#[command(flatten)] #[command(flatten)]
format: DiffFormatArgs, format: DiffFormatArgs,

View File

@ -12,16 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use clap_complete::ArgValueCandidates;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::backend::CommitId; use jj_lib::backend::CommitId;
use jj_lib::config::ConfigGetError;
use jj_lib::config::ConfigGetResultExt as _;
use jj_lib::graph::reverse_graph;
use jj_lib::graph::GraphEdge;
use jj_lib::graph::GraphEdgeType; use jj_lib::graph::GraphEdgeType;
use jj_lib::graph::ReverseGraphIterator;
use jj_lib::graph::TopoGroupedGraphIterator; use jj_lib::graph::TopoGroupedGraphIterator;
use jj_lib::repo::Repo; use jj_lib::repo::Repo as _;
use jj_lib::revset::RevsetEvaluationError; use jj_lib::revset::RevsetEvaluationError;
use jj_lib::revset::RevsetExpression; use jj_lib::revset::RevsetExpression;
use jj_lib::revset::RevsetFilterPredicate; use jj_lib::revset::RevsetFilterPredicate;
use jj_lib::revset::RevsetIteratorExt; use jj_lib::revset::RevsetIteratorExt as _;
use jj_lib::settings::ConfigResultExt as _;
use jj_lib::settings::UserSettings; use jj_lib::settings::UserSettings;
use tracing::instrument; use tracing::instrument;
@ -30,10 +35,10 @@ use crate::cli_util::CommandHelper;
use crate::cli_util::LogContentFormat; use crate::cli_util::LogContentFormat;
use crate::cli_util::RevisionArg; use crate::cli_util::RevisionArg;
use crate::command_error::CommandError; use crate::command_error::CommandError;
use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitTemplatePropertyKind;
use crate::complete;
use crate::diff_util::DiffFormatArgs; use crate::diff_util::DiffFormatArgs;
use crate::graphlog::get_graphlog; use crate::graphlog::get_graphlog;
use crate::graphlog::Edge;
use crate::graphlog::GraphStyle; use crate::graphlog::GraphStyle;
use crate::ui::Ui; use crate::ui::Ui;
@ -41,44 +46,72 @@ use crate::ui::Ui;
/// ///
/// Renders a graphical view of the project's history, ordered with children /// Renders a graphical view of the project's history, ordered with children
/// before parents. By default, the output only includes mutable revisions, /// before parents. By default, the output only includes mutable revisions,
/// along with some additional revisions for context. /// along with some additional revisions for context. Use `jj log -r ::` to see
/// all revisions. See [`jj help -k revsets`] for information about the syntax.
///
/// [`jj help -k revsets`]:
/// https://jj-vcs.github.io/jj/latest/revsets/
/// ///
/// Spans of revisions that are not included in the graph per `--revisions` are /// Spans of revisions that are not included in the graph per `--revisions` are
/// rendered as a synthetic node labeled "(elided revisions)". /// rendered as a synthetic node labeled "(elided revisions)".
///
/// The working-copy commit is indicated by a `@` symbol in the graph.
/// [Immutable revisions] have a `◆` symbol. Other commits have a `○` symbol.
/// All of these symbols can be [customized].
///
/// [Immutable revisions]:
/// https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits
///
/// [customized]:
/// https://jj-vcs.github.io/jj/latest/config/#node-style
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub(crate) struct LogArgs { pub(crate) struct LogArgs {
/// Which revisions to show /// Which revisions to show
/// ///
/// If no paths nor revisions are specified, this defaults to the /// If no paths nor revisions are specified, this defaults to the
/// `revsets.log` setting. /// `revsets.log` setting.
#[arg(long, short)] #[arg(
long,
short,
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revisions: Vec<RevisionArg>, revisions: Vec<RevisionArg>,
/// Show revisions modifying the given paths /// Show revisions modifying the given paths
#[arg(value_hint = clap::ValueHint::AnyPath)] #[arg(
value_name = "FILESETS",
value_hint = clap::ValueHint::AnyPath,
add = ArgValueCompleter::new(complete::log_files),
)]
paths: Vec<String>, paths: Vec<String>,
/// Limit number of revisions to show
///
/// Applied after revisions are filtered and reordered topologically, but
/// before being reversed.
#[arg(long, short = 'n')]
limit: Option<usize>,
/// Show revisions in the opposite order (older revisions first) /// Show revisions in the opposite order (older revisions first)
#[arg(long)] #[arg(long)]
reversed: bool, reversed: bool,
/// Limit number of revisions to show
///
/// Applied after revisions are filtered and reordered.
#[arg(long, short = 'n')]
limit: Option<usize>,
// TODO: Delete `-l` alias in jj 0.25+
#[arg(
short = 'l',
hide = true,
conflicts_with = "limit",
value_name = "LIMIT"
)]
deprecated_limit: Option<usize>,
/// Don't show the graph, show a flat list of revisions /// Don't show the graph, show a flat list of revisions
#[arg(long)] #[arg(long)]
no_graph: bool, no_graph: bool,
/// Render each revision using the given template /// Render each revision using the given template
/// ///
/// For the syntax, see https://martinvonz.github.io/jj/latest/templates/ /// Run `jj log -T` to list the built-in templates.
#[arg(long, short = 'T')] ///
/// You can also specify arbitrary template expressions using the
/// [built-in keywords]. See [`jj help -k templates`] for more
/// information.
///
/// If not specified, this defaults to the `templates.log` setting.
///
/// [built-in keywords]:
/// https://jj-vcs.github.io/jj/latest/templates/#commit-keywords
///
/// [`jj help -k templates`]:
/// https://jj-vcs.github.io/jj/latest/templates/
#[arg(long, short = 'T', add = ArgValueCandidates::new(complete::template_aliases))]
template: Option<String>, template: Option<String>,
/// Show patch /// Show patch
#[arg(long, short = 'p')] #[arg(long, short = 'p')]
@ -94,13 +127,14 @@ pub(crate) fn cmd_log(
args: &LogArgs, args: &LogArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let settings = workspace_command.settings();
let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?;
let revset_expression = { let revset_expression = {
// only use default revset if neither revset nor path are specified // only use default revset if neither revset nor path are specified
let mut expression = if args.revisions.is_empty() && args.paths.is_empty() { let mut expression = if args.revisions.is_empty() && args.paths.is_empty() {
workspace_command let revset_string = settings.get_string("revsets.log")?;
.parse_revset(ui, &RevisionArg::from(command.settings().default_revset()))? workspace_command.parse_revset(ui, &RevisionArg::from(revset_string))?
} else if !args.revisions.is_empty() { } else if !args.revisions.is_empty() {
workspace_command.parse_union_revsets(ui, &args.revisions)? workspace_command.parse_union_revsets(ui, &args.revisions)?
} else { } else {
@ -115,6 +149,8 @@ pub(crate) fn cmd_log(
} }
expression expression
}; };
let prio_revset = settings.get_string("revsets.log-graph-prioritize")?;
let prio_revset = workspace_command.parse_revset(ui, &RevisionArg::from(prio_revset))?;
let repo = workspace_command.repo(); let repo = workspace_command.repo();
let matcher = fileset_expression.to_matcher(); let matcher = fileset_expression.to_matcher();
@ -122,13 +158,10 @@ pub(crate) fn cmd_log(
let store = repo.store(); let store = repo.store();
let diff_renderer = workspace_command.diff_renderer_for_log(&args.diff_format, args.patch)?; let diff_renderer = workspace_command.diff_renderer_for_log(&args.diff_format, args.patch)?;
let graph_style = GraphStyle::from_settings(command.settings())?; let graph_style = GraphStyle::from_settings(settings)?;
let use_elided_nodes = command let use_elided_nodes = settings.get_bool("ui.log-synthetic-elided-nodes")?;
.settings() let with_content_format = LogContentFormat::new(ui, settings)?;
.config()
.get_bool("ui.log-synthetic-elided-nodes")?;
let with_content_format = LogContentFormat::new(ui, command.settings())?;
let template; let template;
let node_template; let node_template;
@ -136,22 +169,22 @@ pub(crate) fn cmd_log(
let language = workspace_command.commit_template_language(); let language = workspace_command.commit_template_language();
let template_string = match &args.template { let template_string = match &args.template {
Some(value) => value.to_string(), Some(value) => value.to_string(),
None => command.settings().config().get_string("templates.log")?, None => settings.get_string("templates.log")?,
}; };
template = workspace_command template = workspace_command
.parse_template( .parse_template(
ui, ui,
&language, &language,
&template_string, &template_string,
CommitTemplateLanguage::wrap_commit, CommitTemplatePropertyKind::wrap_commit,
)? )?
.labeled("log"); .labeled("log");
node_template = workspace_command node_template = workspace_command
.parse_template( .parse_template(
ui, ui,
&language, &language,
&get_node_template(graph_style, command.settings())?, &get_node_template(graph_style, settings)?,
CommitTemplateLanguage::wrap_commit_opt, CommitTemplatePropertyKind::wrap_commit_opt,
)? )?
.labeled("node"); .labeled("node");
} }
@ -161,53 +194,60 @@ pub(crate) fn cmd_log(
let mut formatter = ui.stdout_formatter(); let mut formatter = ui.stdout_formatter();
let formatter = formatter.as_mut(); let formatter = formatter.as_mut();
if args.deprecated_limit.is_some() {
writeln!(
ui.warning_default(),
"The -l shorthand is deprecated, use -n instead."
)?;
}
let limit = args.limit.or(args.deprecated_limit).unwrap_or(usize::MAX);
if !args.no_graph { if !args.no_graph {
let mut raw_output = formatter.raw()?; let mut raw_output = formatter.raw()?;
let mut graph = get_graphlog(graph_style, raw_output.as_mut()); let mut graph = get_graphlog(graph_style, raw_output.as_mut());
let forward_iter = TopoGroupedGraphIterator::new(revset.iter_graph()); let iter: Box<dyn Iterator<Item = _>> = {
let iter: Box<dyn Iterator<Item = _>> = if args.reversed { let mut forward_iter = TopoGroupedGraphIterator::new(revset.iter_graph());
Box::new(ReverseGraphIterator::new(forward_iter)?)
} else { let has_commit = revset.containing_fn();
Box::new(forward_iter)
for prio in prio_revset.evaluate_to_commit_ids()? {
let prio = prio?;
if has_commit(&prio)? {
forward_iter.prioritize_branch(prio);
}
}
// The input to TopoGroupedGraphIterator shouldn't be truncated
// because the prioritized commit must exist in the input set.
let forward_iter = forward_iter.take(args.limit.unwrap_or(usize::MAX));
if args.reversed {
Box::new(reverse_graph(forward_iter, |id| id)?.into_iter().map(Ok))
} else {
Box::new(forward_iter)
}
}; };
for node in iter.take(limit) { for node in iter {
let (commit_id, edges) = node?; let (commit_id, edges) = node?;
// The graph is keyed by (CommitId, is_synthetic) // The graph is keyed by (CommitId, is_synthetic)
let mut graphlog_edges = vec![]; let mut graphlog_edges = vec![];
// TODO: Should we update revset.iter_graph() to yield this flag instead of all // TODO: Should we update revset.iter_graph() to yield a `has_missing` flag
// the missing edges since we don't care about where they point here // instead of all the missing edges since we don't care about
// anyway? // where they point here anyway?
let mut has_missing = false; let mut missing_edge_id = None;
let mut elided_targets = vec![]; let mut elided_targets = vec![];
for edge in edges { for edge in edges {
match edge.edge_type { match edge.edge_type {
GraphEdgeType::Missing => { GraphEdgeType::Missing => {
has_missing = true; missing_edge_id = Some(edge.target);
} }
GraphEdgeType::Direct => { GraphEdgeType::Direct => {
graphlog_edges.push(Edge::Direct((edge.target, false))); graphlog_edges.push(GraphEdge::direct((edge.target, false)));
} }
GraphEdgeType::Indirect => { GraphEdgeType::Indirect => {
if use_elided_nodes { if use_elided_nodes {
elided_targets.push(edge.target.clone()); elided_targets.push(edge.target.clone());
graphlog_edges.push(Edge::Direct((edge.target, true))); graphlog_edges.push(GraphEdge::direct((edge.target, true)));
} else { } else {
graphlog_edges.push(Edge::Indirect((edge.target, false))); graphlog_edges.push(GraphEdge::indirect((edge.target, false)));
} }
} }
} }
} }
if has_missing { if let Some(missing_edge_id) = missing_edge_id {
graphlog_edges.push(Edge::Missing); graphlog_edges.push(GraphEdge::missing((missing_edge_id, false)));
} }
let mut buffer = vec![]; let mut buffer = vec![];
let key = (commit_id, false); let key = (commit_id, false);
@ -241,7 +281,7 @@ pub(crate) fn cmd_log(
for elided_target in elided_targets { for elided_target in elided_targets {
let elided_key = (elided_target, true); let elided_key = (elided_target, true);
let real_key = (elided_key.0.clone(), false); let real_key = (elided_key.0.clone(), false);
let edges = [Edge::Direct(real_key)]; let edges = [GraphEdge::direct(real_key)];
let mut buffer = vec![]; let mut buffer = vec![];
let within_graph = let within_graph =
with_content_format.sub_width(graph.width(&elided_key, &edges)); with_content_format.sub_width(graph.width(&elided_key, &edges));
@ -258,13 +298,16 @@ pub(crate) fn cmd_log(
} }
} }
} else { } else {
let iter: Box<dyn Iterator<Item = Result<CommitId, RevsetEvaluationError>>> = let iter: Box<dyn Iterator<Item = Result<CommitId, RevsetEvaluationError>>> = {
let forward_iter = revset.iter().take(args.limit.unwrap_or(usize::MAX));
if args.reversed { if args.reversed {
Box::new(revset.iter().reversed()?) let entries: Vec<_> = forward_iter.try_collect()?;
Box::new(entries.into_iter().rev().map(Ok))
} else { } else {
Box::new(revset.iter()) Box::new(forward_iter)
}; }
for commit_or_error in iter.commits(store).take(limit) { };
for commit_or_error in iter.commits(store) {
let commit = commit_or_error?; let commit = commit_or_error?;
with_content_format with_content_format
.write(formatter, |formatter| template.format(&commit, formatter))?; .write(formatter, |formatter| template.format(&commit, formatter))?;
@ -283,9 +326,9 @@ pub(crate) fn cmd_log(
// For users of e.g. Mercurial, where `.` indicates the current commit. // For users of e.g. Mercurial, where `.` indicates the current commit.
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"The argument {only_path:?} is being interpreted as a path, but this is often not \ "The argument {only_path:?} is being interpreted as a fileset expression, but \
useful because all non-empty commits touch '.'. If you meant to show the \ this is often not useful because all non-empty commits touch '.'. If you meant \
working copy commit, pass -r '@' instead." to show the working copy commit, pass -r '@' instead."
)?; )?;
} else if revset.is_empty() } else if revset.is_empty()
&& workspace_command && workspace_command
@ -294,8 +337,8 @@ pub(crate) fn cmd_log(
{ {
writeln!( writeln!(
ui.warning_default(), ui.warning_default(),
"The argument {only_path:?} is being interpreted as a path. To specify a revset, \ "The argument {only_path:?} is being interpreted as a fileset expression. To \
pass -r {only_path:?} instead." specify a revset, pass -r {only_path:?} instead."
)?; )?;
} }
} }
@ -306,11 +349,8 @@ pub(crate) fn cmd_log(
pub fn get_node_template( pub fn get_node_template(
style: GraphStyle, style: GraphStyle,
settings: &UserSettings, settings: &UserSettings,
) -> Result<String, config::ConfigError> { ) -> Result<String, ConfigGetError> {
let symbol = settings let symbol = settings.get_string("templates.log_node").optional()?;
.config()
.get_string("templates.log_node")
.optional()?;
let default = if style.is_ascii() { let default = if style.is_ascii() {
"builtin_log_node_ascii" "builtin_log_node_ascii"
} else { } else {

Some files were not shown because too many files have changed in this diff Show More