pax_global_header00006660000000000000000000000064152103120620014502gustar00rootroot0000000000000052 comment=b8f7c455170e3273897aaf94431f8ccfb1afa7ad jj-vcs-jj-b8f7c45/000077500000000000000000000000001521031206200137235ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.cargo/000077500000000000000000000000001521031206200150745ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.cargo/config-ci.toml000066400000000000000000000003521521031206200176270ustar00rootroot00000000000000include = [ "config.toml", ] [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"] jj-vcs-jj-b8f7c45/.cargo/config.toml000066400000000000000000000010611521031206200172340ustar00rootroot00000000000000[target.'cfg(all(target_family = "windows", target_env = "msvc"))'] linker = "rust-lld.exe" rustflags = [ # 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 # download a raw .exe e.g. from the CI system "-Ctarget-feature=+crt-static", # Without this, at opt-level = 0 (dev profile) the compiler will generate code # with enough async futures that the default 1MiB stack will overflow. "-Clink-arg=/STACK:8388608" # 8MiB ] jj-vcs-jj-b8f7c45/.config/000077500000000000000000000000001521031206200152465ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.config/codespell-additional-dict000066400000000000000000000001021521031206200221630ustar00rootroot00000000000000co-locate->colocate co-located->colocated co-location->colocation jj-vcs-jj-b8f7c45/.config/mise.toml000066400000000000000000000114041521031206200171000ustar00rootroot00000000000000#:schema https://mise.jdx.dev/schema/mise.json # Mise support is experimental. # See https://jj-vcs.github.io/jj/latest/contributing/#alternative-development-setup-with-mise [vars] bacon_version = "3.16.0" cargo_insta_version = "1.46.3" cargo_nextest_version = "0.9.113" rust_version = "1.89" uv_version = "0.8.13" node_version = "24" [settings] # the default cmd doesn't support shell expansion, use PowerShell instead windows_default_inline_shell_args = "pwsh -Command" [tools] # We need cargo-binstall so that Mise would download "cargo:" tools instead of building them. "cargo-binstall" = "latest" [tasks."review:cli-reference"] description = "Build and review the changes to the command line reference (cli/tests/cli-reference@.md.snap)" tools.rust = "{{vars.rust_version}}" tools."cargo:cargo-insta" = "{{vars.cargo_insta_version}}" run = "cargo insta test --review --workspace -- test_generate_md_cli_help" [tasks."review:test"] alias = "test:review" description = "Run the tests and review changes to snapshots" tools.rust = "{{vars.rust_version}}" tools."cargo:cargo-insta" = "{{vars.cargo_insta_version}}" tools."cargo:cargo-nextest" = "{{vars.cargo_nextest_version}}" env.NEXTEST_STATUS_LEVEL = "slow" env.NEXTEST_FINAL_STATUS_LEVEL = "none" run = "cargo insta test --review --workspace --test-runner nextest --" [tasks."build:cli-reference"] description = "Build the command line reference (cli/tests/cli-reference@.md.snap)" tools.rust = "{{vars.rust_version}}" tools."cargo:cargo-insta" = "{{vars.cargo_insta_version}}" run = "cargo insta test --accept --workspace -- test_generate_md_cli_help" [tasks."build:debug"] alias = "build" description = "Build jj in debug mode" tools.rust = "{{vars.rust_version}}" run = "cargo build" [tasks."build:docs"] description = "Build documentation into rendered-docs/ for offline use" tools.uv = "{{vars.uv_version}}" run = "uv run mkdocs build" [tasks."build:web"] description = "Build the new astro-based documentation" tools.node = "{{vars.node_version}}" run = "npm install && npm run build" dir = "web/docs" [tasks."build:release"] description = "Build jj in release mode" tools.rust = "{{vars.rust_version}}" run = "cargo build --release" [tasks."build:gen-protos"] description = "Generate Protocol Buffers definitions" tools.protoc = "latest" tools.rust = "{{vars.rust_version}}" run = "cargo run --bin gen-protos" [tasks."check:clippy"] description = "Lint the code with Clippy" tools.rust = "{{vars.rust_version}}" run = "cargo clippy --workspace --all-targets" [tasks."check:codespell"] description = "Check code for common misspellings" tools.uv = "{{vars.uv_version}}" run = "uv run codespell $(jj file list)" [tasks."check:format"] description = "Check the code format" tools.rust = "nightly" run = "cargo fmt --check" [tasks."check:test"] alias = "test" description = "Run the tests" tools.rust = "{{vars.rust_version}}" tools."cargo:cargo-insta" = "{{vars.cargo_insta_version}}" tools."cargo:cargo-nextest" = "{{vars.cargo_nextest_version}}" env.NEXTEST_STATUS_LEVEL = "slow" env.NEXTEST_FINAL_STATUS_LEVEL = "none" run = "cargo insta test --check --workspace --test-runner nextest --" [tasks."check:zizmor"] description = "Check GitHub workflows with Zizmor" tools."cargo:zizmor" = "latest" run = "zizmor ." [tasks."fix:clippy"] description = "Fix the clippy alerts" tools.rust = "{{vars.rust_version}}" run = "cargo clippy --fix" [tasks."fix:codespell"] description = "Fix common code misspellings" tools.uv = "{{vars.uv_version}}" run = "uv run codespell -w $(jj file list)" [tasks."fix:format"] description = "Format the code" tools.rust = "nightly" run = "cargo fmt" [tasks."fix:test"] alias = "test:accept" description = "Accept changes to snapshot tests" tools.rust = "{{vars.rust_version}}" tools."cargo:cargo-insta" = "{{vars.cargo_insta_version}}" tools."cargo:cargo-nextest" = "{{vars.cargo_nextest_version}}" env.NEXTEST_STATUS_LEVEL = "slow" env.NEXTEST_FINAL_STATUS_LEVEL = "none" run = "cargo insta test --accept --workspace --test-runner nextest --" [tasks."serve:docs"] description = "Preview documentation with live reloading" tools.uv = "{{vars.uv_version}}" run = "uv run mkdocs serve" [tasks."serve:web"] description = "Preview the new astro-based documentation with live reloading" tools.node = "{{vars.node_version}}" run = "npm install && npm run dev" dir = "web/docs" [tasks."clippy:watch"] description = "Lint the code with Clippy when the code changes" tools.rust = "{{vars.rust_version}}" tools."cargo:bacon" = "{{vars.bacon_version}}" run = "bacon clippy -- --workspace --all-targets" [tasks."test:watch"] description = "Run tests when the code changes" tools.rust = "{{vars.rust_version}}" tools."cargo:bacon" = "{{vars.bacon_version}}" tools."cargo:cargo-nextest" = "{{vars.cargo_nextest_version}}" run = "bacon nextest -- --nff --workspace" jj-vcs-jj-b8f7c45/.config/nextest.toml000066400000000000000000000002141521031206200176320ustar00rootroot00000000000000[profile.default] slow-timeout = { period = "10s" } [profile.ci] slow-timeout = { period = "10s", terminate-after = 10 } fail-fast = false jj-vcs-jj-b8f7c45/.editorconfig000066400000000000000000000004671521031206200164070ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.rs] indent_style = space 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 jj-vcs-jj-b8f7c45/.envrc.recommended000066400000000000000000000004641521031206200173260ustar00rootroot00000000000000# To use: echo "source_env .envrc.recommended" >> .envrc if has nix; then if ! has nix_direnv_version || ! nix_direnv_version 3.0.5; then source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w=" fi use flake fi jj-vcs-jj-b8f7c45/.gitattributes000066400000000000000000000002151521031206200166140ustar00rootroot00000000000000Cargo.lock linguist-generated=true merge=binary flake.lock linguist-generated=true merge=binary uv.lock linguist-generated=true merge=binary jj-vcs-jj-b8f7c45/.github/000077500000000000000000000000001521031206200152635ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.github/CODEOWNERS000066400000000000000000000001601521031206200166530ustar00rootroot00000000000000# The maintainers own all files. # See GOVERNANCE.md for the list of current maintainers. * @jj-vcs/maintainers jj-vcs-jj-b8f7c45/.github/ISSUE_TEMPLATE/000077500000000000000000000000001521031206200174465ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000011331521031206200221360ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ## Description ## Steps to Reproduce the Problem 1. 1. 1. ## Expected Behavior ## Actual Behavior ## Specifications - Platform: - Version: jj-vcs-jj-b8f7c45/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011271521031206200231740ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: 'FR: ' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. jj-vcs-jj-b8f7c45/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000024431521031206200210670ustar00rootroot00000000000000 # Checklist If applicable: - [ ] I have updated `CHANGELOG.md` - [ ] I have updated the documentation (`README.md`, `docs/`, `demos/`) - [ ] I have updated the config schema (`cli/src/config-schema.json`) - [ ] I have added/updated tests to cover my changes - [ ] I fully understand the code that I am submitting (what it does, how it works, how it's organized), including any code drafted by an LLM. - [ ] For any prose generated by an LLM, I have proof-read and copy-edited with an eye towards deleting anything that is irrelevant, clarifying anything that is confusing, and adding details that are relevant. This includes, for example, commit descriptions, PR descriptions, and code comments. jj-vcs-jj-b8f7c45/.github/actions/000077500000000000000000000000001521031206200167235ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.github/actions/setup-windows/000077500000000000000000000000001521031206200215535ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.github/actions/setup-windows/action.yml000066400000000000000000000043461521031206200235620ustar00rootroot00000000000000name: 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: - name: 'Set up D: drive' shell: pwsh run: | # zizmor: ignore[github-env] all values are hardcoded literals, not attacker-controlled # 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 won’t 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 "@ jj-vcs-jj-b8f7c45/.github/dependabot.yml000066400000000000000000000012301521031206200201070ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 10 commit-message: prefix: "cargo:" groups: cargo-dependencies: patterns: - "*" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 10 commit-message: prefix: "github:" groups: github-dependencies: patterns: - "*" - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 10 commit-message: prefix: "npm:" groups: npm-dependencies: patterns: - "*" jj-vcs-jj-b8f7c45/.github/scripts/000077500000000000000000000000001521031206200167525ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/.github/scripts/count-cargo-lock-packages000077500000000000000000000003151521031206200236220ustar00rootroot00000000000000#!/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 jj-vcs-jj-b8f7c45/.github/scripts/docs-build-deploy000077500000000000000000000024011521031206200222140ustar00rootroot00000000000000#!/bin/sh # Set up a virtual environment with the required tools, build, and deploy the docs. # # Run from the root directory of the project as # .github/scripts/docs-build-deploy prerelease main # All arguments after the first are passed to `mike deploy`, run # `uv run -- mike deploy --help` for options. Note that `mike deploy` # creates a commit directly on the `gh-pages` branch. set -ev # Affects the generation of `sitemap.xml.gz` by `mkdocs`. See # https://github.com/jimporter/mike/issues/103 and # https://reproducible-builds.org/docs/source-date-epoch/ export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct docs/ mkdocs.yml) # TODO: `--alias-type symlink` is the # default, and may be nicer in some ways. However, # this requires deploying to GH Pages via a "custom GitHub Action", as in # https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow. # Otherwise, you get an error: # > 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. uv run -- mike deploy --alias-type copy "$@" jj-vcs-jj-b8f7c45/.github/scripts/dragon-bureaucrat000077500000000000000000000032361521031206200223110ustar00rootroot00000000000000#!/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) # 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 <- ${{ 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-aarch64-gnu, macos-x86_64, macos-aarch64, windows-x86_64, windows-aarch64] include: - build: linux-x86_64-gnu os: ubuntu-24.04 cargo_flags: "--all-features" extra_targets: "" - build: linux-aarch64-gnu os: ubuntu-24.04-arm cargo_flags: "--all-features" extra_targets: "" - build: macos-x86_64 os: macos-26 cargo_flags: "--target x86_64-apple-darwin" extra_targets: "x86_64-apple-darwin" - build: macos-aarch64 os: macos-15 cargo_flags: "" extra_targets: "" - build: windows-x86_64 os: windows-2022 cargo_flags: "" extra_targets: "" - build: windows-aarch64 os: windows-11-arm cargo_flags: "" extra_targets: "" runs-on: ${{ matrix.os }} # macos-x86_64 runners are inconsistent and prone to hitting the timeout continue-on-error: ${{ matrix.os == 'macos-x86_64' }} # 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. # # keep a log of updates (along with committed date) below: # # 2025-03-20 (aseipp): peak p99 builds seemed to be long, bump 15m -> 20m # 2025-10-06 (aseipp): x86 macos runners consistently slower, bump 20m -> 25m # # The "nix flake" jobs below use the same timeout, so update there too if # you update here. timeout-minutes: 25 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - name: Set up Windows Builders if: startswith(matrix.build, 'windows-x86_64') # FIXME: aarch64 doesn't have D:\ yet uses: ./.github/actions/setup-windows - name: Install Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: 1.89 # comma-separated list of extra targets to install targets: ${{ matrix.extra_targets }} - uses: taiki-e/install-action@6887963ccf37a9ddcd8c5fa4baeb3e1e5fd61fa1 with: tool: nextest - name: Install mold uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 with: make-default: false - name: Build run: >- cargo build --config .cargo/config-ci.toml --workspace --all-targets --verbose ${{ matrix.cargo_flags }} - 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 no-git: name: build (no git) runs-on: ubuntu-24.04 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - name: Install Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: 1.89 - 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: 25 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: fetch-depth: 0 persist-credentials: false - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 - run: nix flake check -L --show-trace check-protos: name: check (protos) runs-on: ubuntu-24.04 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe with: command: check ${{ matrix.checks }} check-codespell: name: check (codespell) runs-on: ubuntu-24.04 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b 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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: 1.89 # 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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b 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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # '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@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - name: Install the latest version of uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b - 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@87557b9c84dde89fdd9b10e88954ac2f4248e463 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@df4cb1c069e1874edd31b4311f1884172cec0e10 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 it’s 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: 550 # 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 jj-vcs-jj-b8f7c45/.github/workflows/dependabot.yml000066400000000000000000000012111521031206200221430ustar00rootroot00000000000000name: Enable auto-merge for Dependabot PRs on: pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true permissions: {} jobs: dependabot-auto-merge: name: 'Dependabot auto-merge' permissions: contents: write pull-requests: write runs-on: ubuntu-24.04 if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} steps: - name: Enable auto-merge for Dependabot PRs run: gh pr merge --auto --rebase "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} jj-vcs-jj-b8f7c45/.github/workflows/docs.yml000066400000000000000000000053241521031206200207770ustar00rootroot00000000000000name: website on: push: branches: - main permissions: {} jobs: prerelease-docs-build-deploy: # 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: matrix: os: [ubuntu-24.04] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 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" - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b with: version: "0.5.1" - name: Install dependencies, compile and deploy docs run: | git config user.name 'jj-docs[bot]' git config user.email 'jj-docs[bot]@users.noreply.github.io' 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`" run: git diff --stat gh-pages^ gh-pages || echo "(No diffs)" starlight-docs-build-deploy: # Deploy Starlight-based docs to /starlight for comparison with MkDocs version needs: prerelease-docs-build-deploy permissions: contents: write if: github.repository_owner == 'jj-vcs' # Stops this job from running on forks runs-on: ubuntu-24.04 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: true - run: "git fetch origin gh-pages --depth=1" - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e with: node-version: '22' - name: Install dependencies working-directory: web/docs run: npm ci - name: Build Starlight docs working-directory: web/docs run: npm run build -- --base /starlight/ - name: Deploy to gh-pages/starlight run: | git config user.name 'jj-docs[bot]' git config user.email 'jj-docs[bot]@users.noreply.github.io' git checkout gh-pages rm -rf starlight mv web/docs/dist starlight git add starlight git commit -m "Deploy Starlight docs to /starlight" || echo "No changes to commit" git push origin gh-pages jj-vcs-jj-b8f7c45/.github/workflows/pr.yml000066400000000000000000000064651521031206200204770ustar00rootroot00000000000000name: Validate pull request on: # pull_request_target is needed so the GITHUB_TOKEN has pull-requests: write # permission for PRs from forks. This is safe because the workflow never # checks out or executes any code from the PR; it only reads commit metadata # via the GitHub API and posts reviews. pull_request_target: # zizmor: ignore[dangerous-triggers] types: [opened, synchronize, edited] permissions: contents: read pull-requests: write jobs: check-pr: name: Commit subject lines runs-on: ubuntu-latest steps: - name: Run validation script uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const pullRequest = { pull_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }; const { data: commits } = await github.rest.pulls.listCommits(pullRequest); const { data: reviews } = await github.rest.pulls.listReviews(pullRequest); // Get latest review by `github-actions[bot]`. const githubActionsBot = 41898282; const latestReview = reviews .filter(r => r.user.id === githubActionsBot) .at(-1); // Ensure commit subject lines are in the form `area or areas: description`. const subjectLineFormat = /^.+:\s.+$/; const badCommits = commits .map(c => [c.sha.slice(0, 8), c.commit.message.split('\n')[0]]) .filter(([_, s]) => !subjectLineFormat.test(s)) .map(([sha, title]) => `- ${sha}: ${title}`); if (badCommits.length > 0) { // One or more commits are bad. Make sure changes are requested. const body = 'The following commits do not follow our format for subject lines:\n\n' + `${badCommits.join('\n')}\n\nCommits should have a subject line following the ` + 'format `: `. Please review the [commit guidelines]' + '(https://jj-vcs.github.io/jj/prerelease/contributing/#commit-guidelines) for ' + 'more information.'; if (latestReview?.state !== 'CHANGES_REQUESTED') { await github.rest.pulls.createReview({ event: 'REQUEST_CHANGES', ...pullRequest, body, }); } else { await github.rest.pulls.updateReview({ review_id: latestReview.id, ...pullRequest, body, }); } await core.setFailed('One or more commits were not formatted correctly.'); } else { // All commits are formatted correctly. Dismiss any change requests. if (latestReview && latestReview.state === 'CHANGES_REQUESTED') { await github.rest.pulls.dismissReview({ message: 'All commits are now correctly formatted. Thank you for your contribution!', review_id: latestReview.id, ...pullRequest, }); } console.log('All commits were correctly formatted.'); } jj-vcs-jj-b8f7c45/.github/workflows/release.yml000066400000000000000000000124271521031206200214710ustar00rootroot00000000000000name: Release on: release: types: [published] permissions: read-all env: CARGO_INCREMENTAL: 0 jobs: build-release: name: build-release permissions: contents: write strategy: fail-fast: false matrix: build: [linux-x86_64-musl, linux-aarch64-musl, macos-x86_64, macos-aarch64, win-x86_64, win-aarch64] include: - build: linux-x86_64-musl os: ubuntu-24.04 target: x86_64-unknown-linux-musl - build: linux-aarch64-musl os: ubuntu-24.04-arm target: aarch64-unknown-linux-musl - build: macos-x86_64 os: macos-15 target: x86_64-apple-darwin - build: macos-aarch64 os: macos-15 target: aarch64-apple-darwin - build: win-x86_64 os: windows-2022 target: x86_64-pc-windows-msvc - build: win-aarch64 os: windows-11-arm target: aarch64-pc-windows-msvc runs-on: ${{ matrix.os }} steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - name: Install packages (Ubuntu) if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get update sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools - name: Install Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: stable target: ${{ matrix.target }} - name: Build release binary shell: bash run: cargo build --target ${{ matrix.target }} --verbose --release - name: Build archive shell: bash run: | outdir="target/${{ matrix.target }}/release" staging="jj-${RELEASE_TAG_NAME}-${{ matrix.target }}" mkdir "$staging" cp {README.md,LICENSE} "$staging/" if [[ "${{ matrix.os }}" == windows* ]]; then cp "$outdir/jj.exe" "$staging/" cd "$staging" 7z a "../$staging.zip" . echo "ASSET=$staging.zip" >> $GITHUB_ENV else cp "$outdir/jj" "$staging/" tar czf "$staging.tar.gz" -C "$staging" . echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV fi env: RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - name: Upload release archive uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ env.ASSET }} asset_name: ${{ env.ASSET }} asset_content_type: application/octet-stream docs-release-archive: runs-on: ubuntu-24.04 permissions: contents: write steps: - name: Install packages (Ubuntu) run: | sudo apt-get update sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b with: version: "0.5.1" - name: Compile docs and zip them up run: | uv run mkdocs build archive="jj-${RELEASE_TAG_NAME}-docs-html.tar.gz" tar czf "$archive" -C "rendered-docs" . echo "ASSET=$archive" >> $GITHUB_ENV env: MKDOCS_OFFLINE: true RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} - name: Upload release archive uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ env.ASSET }} asset_name: ${{ env.ASSET }} asset_content_type: application/octet-stream docs-deploy-website-latest-release: runs-on: ubuntu-24.04 permissions: contents: write steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 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" - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: 3.11 - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b with: version: "0.5.1" - name: Install dependencies, compile and deploy docs to the "latest release" section of the website run: | git config user.name 'jj-docs[bot]' git config user.email 'jj-docs[bot]@users.noreply.github.io' # Using the 'latest' tag below makes the website default # to this version. .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`" run: git diff --stat gh-pages^ gh-pages || echo "(No diffs)" jj-vcs-jj-b8f7c45/.github/workflows/required-checks-hack.yml000066400000000000000000000006041521031206200240250ustar00rootroot00000000000000name: 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 doesn’t # 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 jj-vcs-jj-b8f7c45/.github/workflows/review.yml000066400000000000000000000056531521031206200213550ustar00rootroot00000000000000name: Pull request reviews on: pull_request_review: types: [submitted] jobs: check-author: if: github.event.review.state == 'approved' runs-on: ubuntu-latest permissions: pull-requests: write contents: read issues: write steps: - name: Check if PR author is a contributor uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 with: script: | const { pull_request, review } = context.payload; const { owner, repo } = context.repo; const author = pull_request.user.login; const reviewer = review.user.login; const blacklistedAuthors = [ "dependabot[bot]", ]; if (blacklistedAuthors.includes(author)) { core.info("Author is blacklisted from nudge, skipping."); return; } if (reviewer === author) { core.info("Author reviewed their own pull request, skipping."); return; } const getPermissionLevel = async (username) => { const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ username, owner, repo, }); return data.permission; }; const isMaintainer = async (username) => ["maintain", "admin"].includes(await getPermissionLevel(username)); const isContributor = async (username) => ["write", "maintain", "admin"].includes(await getPermissionLevel(username)); if (await isContributor(author)) { core.info(`Author @${author} already has write access.`); return; } if (!await isMaintainer(reviewer)) { core.info(`Reviewer @${reviewer} is not a maintainer.`); return; } const comments = await github.paginate(github.rest.issues.listComments, { issue_number: pull_request.number, per_page: 100, owner, repo, }); const nudgeMarker = ""; if (comments.some(c => (c.body || "").includes(nudgeMarker))) { core.info("Reviewer has already been nudged."); return; }; const body = `Hey @martinvonz! Please ` + `make sure to add ${author} to jj-vcs/contributors so they ` + "can merge their pull request. Feel free to also drop a note " + "in Discord so they can get their contributor role. Thanks!"; await github.rest.issues.createComment({ issue_number: pull_request.number, body: nudgeMarker + body, owner, repo, }); core.notice(`Pinged martinvonz to add ${author} to jj-vcs/contributors.`); jj-vcs-jj-b8f7c45/.github/workflows/scorecards.yml000066400000000000000000000031271521031206200221760ustar00rootroot00000000000000name: Scorecards supply-chain security on: # Only the default branch is supported. branch_protection_rule: schedule: - cron: '17 3 * * 1' push: branches: [ main ] # No default permissions permissions: {} jobs: analysis: name: Scorecards analysis runs-on: ubuntu-24.04 permissions: # Needed to upload the results to code-scanning dashboard. security-events: write id-token: write steps: - name: "Checkout code" uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a with: results_file: results.sarif results_format: sarif # Publish the results to enable scorecard badges. For more details, see # https://github.com/ossf/scorecard-action#publishing-results. # For private repositories, `publish_results` will automatically be set to `false`, # regardless of the value entered here. publish_results: true # Upload the results as artifacts (optional). - name: "Upload artifact" uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@87557b9c84dde89fdd9b10e88954ac2f4248e463 with: sarif_file: results.sarif jj-vcs-jj-b8f7c45/.gitignore000066400000000000000000000010311521031206200157060ustar00rootroot00000000000000/target/ /rendered-docs .direnv .envrc # Generated by nix build, nix-build result # Generated by the insta crate *.pending-snap *.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 # Per user mise config mise.local.toml # mkdocs /.venv /.python-version # Editor specific ignores .idea .vscode .zed # Generated by setting `JJ_TRACE` environment variable. jj-trace-*.json # To make working on buck2 easier (#1997, #4413) /buck-out/ jj-vcs-jj-b8f7c45/.watchmanconfig000066400000000000000000000002221521031206200167100ustar00rootroot00000000000000{ "enforce_root_files": true, "root_files": [".watchmanconfig"], "ignore_dirs": ["target"], "ignore_vcs": [".git", ".sl", ".jj", ".hg"] } jj-vcs-jj-b8f7c45/AUTHORS000066400000000000000000000004531521031206200147750ustar00rootroot00000000000000# This is the list of Jujutsu's significant contributors. # # This does not necessarily list everyone who has contributed code, # especially since many employees of one corporation may be contributing. # To see the full list of contributors, see the revision history in # source control. Google LLC jj-vcs-jj-b8f7c45/CHANGELOG.md000066400000000000000000005767051521031206200155600ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Release highlights ### Breaking changes ### Deprecations ### New features ### Fixed bugs ## [0.42.0] - 2026-06-04 ### Release highlights * Switched to the mimalloc memory allocator for better multi-threaded performance. ### Breaking changes * The following deprecated command options have been removed: - `jj commit --reset-author`/`--author` - `jj describe --no-edit`/`--edit`/`--reset-author`/`--author` - `jj git push --allow-new` - `jj metaedit --update-committer-timestamp` * The following deprecated config options have been removed: - `git.auto-local-bookmark` - `git.push-new-bookmarks` ### Deprecations * `jj evolog` no longer supports legacy commit predecessors recorded in `jj` < 0.30. ### New features * Shell completions now surface descriptions for custom aliases, revset-aliases, template-aliases, and fileset-aliases. Descriptions are extracted from the `.doc` field of the alias definition if it is a table with `.doc` and `.definition` properties. * `jj show` now accepts multiple revisions, showing all of them one after the other, behaving closer to `git show`. * `jj git fetch` now generates evolution history based on change IDs. If change IDs are preserved by the remote, local descendant revisions will be rebased onto the rewritten parents. * Added `jj util backend name` command that prints the commit backend being used in the current repo. * Added `edit-invocation-mode` config option for diff editors (e.g. `jj diffedit`, `jj split`). When set to `"file-by-file"`, the editor is launched once per changed file, making it possible to use per-file tools like `vimdiff` for editing. ### Fixed bugs * `jj git remote add` now reports an error instead of panicking when the remote name is empty or contains whitespace. [#9099](https://github.com/jj-vcs/jj/issues/9099) * Color-words diffs are now shown as separate before and after lines when color output is disabled, making piped or redirected diffs readable. [#5894](https://github.com/jj-vcs/jj/issues/5894) * `jj bookmark forget` no longer prints `Forgot N local bookmarks.` when no local bookmarks were actually forgotten (e.g. when only an untracked remote bookmark matched). [#9181](https://github.com/jj-vcs/jj/issues/9181). * The `builtin_log_redacted` template now also redacts workspace names. ### Contributors Thanks to the people who made this release happen! * Alex Jaspersen (@ajaspers) * Archer (@archer-321) * ase (@adamse) * Austin Seipp (@thoughtpolice) * David Rieber (@drieber) * Eyüp Can Akman (@eyupcanakman) * Jakub Stasiak (@jstasiak) * James Dixon (@lemonase) * Joseph Lou (@josephlou5) * Laurynas Keturakis (@laulauland) * Luna Schwalbe (@lunagl) * Martin von Zweigbergk (@martinvonz) * Niko Savola (@nikosavola) * OlshaMB (@OlshaMB) * Sergey Kasmy (@SergeyKasmy) * truffle (@truffle-dev) * Vincent Ging Ho Yim (@cenviity) * Yuya Nishihara (@yuja) ## [0.41.0] - 2026-05-06 ### Release highlights * `jj fix` now supports formatting specific line ranges (allowing you to format only modified lines); see the configuration manual and notes below for more. * The new global flag `--no-integrate-operation` will let you run a command without impacting the repo state or the working copy, which is useful when automated tools may create snapshots in the background. ### Breaking changes * The `--pattern` flag for `file search` now defaults to `regex:` instead of `glob:`. * `jj git push --all`/`--tracked`/`-r REVSETS` no longer fails when revisions to push are private or have conflicts. Bookmarks which aren't eligible to push will be skipped. * Branch/bookmark patterns passed to `jj git clone` are now saved to jj's repo settings file instead of `.git/config`. Git fetch refspecs are set to the default value. ### Deprecations * In the templating language, the `Operation` type's `.tags()` function has been deprecated in favor of `.attributes()`. ### New features * The `--pattern` flag for `file search` now accepts various pattern kinds through `kind:pattern` syntax. * A new global flag `--no-integrate-operation` lets you run a command without impacting the repo state or the working copy. * A new config option `diff.git.show-path-prefix` can be used to suppress the `a/` and `b/` path prefixes in the `diff --git` output. * `jj fix` now supports line range-limited formatting via the `fix.tools..line-range-arg` and `run-tool-if-zero-line-ranges` configs. This allows running tools only on modified lines and fine-grained control over when the tool is run. If you have set the `line-range-arg` config, use `--all-lines` to match the previous behavior of formatting the entire file. * A new `replace(pattern, content, replacement)` template function is added which supports replacement of content in templates, using a lambda to format replacement text. It supports all string patterns, including regexes with capture groups (e.g. `replace(regex:'(\w+) (\w+)', "hello world", |c| c.get(1) ++ " " ++ c.get(2))`). * New `ByteString` template type for things like file content. * `jj gerrit upload` now supports the new options `--message` (`-m`), `--edit` and `--merged`. You can now also pass multiple hashtags by repeating the `--hashtag` option. * New `remotes..fetch-bookmarks`/`fetch-tags` options to [configure default fetch targets.](docs/config.md#default-bookmarks-and-tags-to-fetch) * `JJ_PAGER` can now override the `ui.pager` config, matching `JJ_EDITOR` for callers that need a jj-specific environment override. ### Fixed bugs * Improving consistency with `git` handling of `.gitignore`, including `/` after entries and `\r\r\n` for MacOS files. * `jj status` filters untracked paths by fileset [#9287](https://github.com/jj-vcs/jj/issues/9287) * Improved performance for snapshotting, visibly improving `jj status` speed for large repositories. * Pre-existing Git submodule directories are no longer considered conflicts in checkouts. [#8065](https://github.com/jj-vcs/jj/issues/8065). * Fixed a panic in `jj gerrit upload` when run without `-r` and the inferred revision was immutable. [#9398](https://github.com/jj-vcs/jj/issues/9398) * `jj status` respects path filters in working copy summaries. * `jj git remote rename`/`remove` now updates the `trunk()` alias. * Commands would sometimes incorrectly diagnose a stale working copy and suggest running `jj op integrate` when it would have no effect. This should now be much less likely to happen in practice. [#9314](https://github.com/jj-vcs/jj/issues/9314) ### Contributors Thanks to the people who made this release happen! * Adrian Freund (@freundTech) * ase (@adamse) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Björn Kautler (@Vampire) * David Higgs (@higgsd) * David Rieber (@drieber) * dzaima (@dzaima) * Federico G. Schwindt (@fgsch) * Gaëtan Lehmann (@glehmann) * hewigovens (@hewigovens) * Ilya Grigoriev (@ilyagr) * jonmeow (@jonmeow) * Joseph Lou (@josephlou5) * Josh McKinney (@joshka) * Jun Mukai (@jmuk) * Lucas Garron (@lgarron) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Maximilian Gaß (@mxey) * OlshaMB (@OlshaMB) * Philip Metzger (@PhilipMetzger) * rayaq * Remo Senekowitsch (@senekor) * rishiad (@rishiad) * Ryan Patterson (@CGamesPlay) * Sebastian Barfurth (@sbarfurth) * Thomas Axelsson (@thomasa88) * xtqqczze (@xtqqczze) * Yuya Nishihara (@yuja) ## [0.40.0] - 2026-04-01 ### Release highlights None ### Breaking changes None ### Deprecations None ### New features * New `diff_lines_added()` and `diff_lines_removed()` revset functions for matching content on only one side of a diff. * The `end` parameter in the `String.substr(start, end)` templating method is now optional. If not given, `substr()` returns from `start` to the end of the string. * `WorkspaceRef` templates now provide a `.root()` method to show the absolute path to each workspace root. * The `jj arrange` TUI now includes immediate parents and children. They are not selectable and are dimmed by default. * `jj arrange` uses the default log template (`builtin_log_compact`) instead of the shorter commit summary style. * In the `jj arrange` TUI, the "swap up/down" actions now move along graph edges even if the commit rows are not adjacent. * [Diff colors](docs/config.md#diff-colors-and-styles) can now be configured differently for each format. * `jj op log` now includes the name of the workspace the operation was created from. * The `config()` template function now accepts a `Stringify` expression instead of `LiteralString`. This allows looking up configuration values dynamically. * `jj op show`, `jj op diff`, `jj op log -p` now only show "interesting" revisions by default (defined by `revsets.op-diff-changes-in`). A new flag, `--show-changes-in`, can be used to override this. [#6083](https://github.com/jj-vcs/jj/issues/6083) ### Fixed bugs * `.gitignore` with UTF-8 BOM can now be parsed correctly. * Fix incompatibility with gpgsm 2.5.x. ### Contributors Thanks to the people who made this release happen! * Aaron Sutton (@aaronjsutton) * Adam Sandberg Eriksson (@adamse) * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Ben Warren (@warrenbhw) * Bryant Chandler (@brychanrobot) * David Higgs (@higgsd) * Filip Weiss (@fiws) * Gabriel Goller (@kaffarell) * Gaëtan Lehmann (@glehmann) * Ilya Grigoriev (@ilyagr) * Jeff Turner (@jefft) * Joseph Lou (@josephlou5) * Josh Steadmon (@steadmon) * KITAGAWA Yasutaka (@kit494way) * Liam (@terror) * Li-Wen Hsu (@lwhsu) * Martin von Zweigbergk (@martinvonz) * Philip Metzger (@PhilipMetzger) * Poliorcetics (@poliorcetics) * Remo Senekowitsch (@senekor) * Rob Pilling (@bobrippling) * Scott Taylor (@scott2000) * Shnatu * Stephen Prater (@stephenprater) * Yuya Nishihara (@yuja) * Zeyi Fan (@fanzeyi) ## [0.39.0] - 2026-03-04 ### Release highlights * `jj arrange` command brings up a TUI where you can reorder and abandon revisions. [#1531](https://github.com/jj-vcs/jj/issues/1531) * `jj bookmark advance` automatically moves bookmarks forward to a target revision (defaults to `@`) using customization points `revsets.bookmark-advance-from` and `revsets.bookmark-advance-to`. It is heavily inspired by the longstanding community alias `jj tug`. ### Breaking changes * Dropped support for legacy index files written by jj < 0.33. New index files will be created as needed. * The following deprecated config options have been removed: - `core.fsmonitor` - `core.watchman.register-snapshot-trigger` * The deprecated command `jj op undo` has been removed. Use `jj op revert` or `jj undo`/`redo` instead. ### Deprecations * `jj debug snapshot` is deprecated in favor of `jj util snapshot`. Although this was an undocumented command in the first place, it will be removed after 6 months (v0.45.0) to give people time to migrate away. ### New features * Add support for push options in `jj git push` with the `--option` flag. This allows users to pass options to the remote server when pushing commits. The short alias `-o` is also supported. * `jj new` now evaluates the `new_description` template to populate the initial commit description when no `-m` message is provided. * Templates now support `first()`, `last()`, `get(index)`, `reverse()`, `skip(count)`, and `take(count)` methods on list types for more flexible list manipulation. * New `builtin_draft_commit_description_with_diff` template that includes the diff in the commit description editor, making it easier to review changes while writing commit messages. * Revsets and templates now support `name:x` pattern aliases such as `'grep:x' = 'description(regex:x)'`. * Filesets now support [user aliases](docs/filesets.md#aliases). * `jj workspace add` now links with relative paths. This enables workspaces to work inside containers or when moved together. Existing workspaces with absolute paths will continue to work as before. * `jj undo` now also outputs what operation was undone, in addition to the operation restored to. * Bookmarks with two or more consecutive `-` characters no longer need to be quoted in revsets. For example, `jj diff -r '"foo--bar"'` can now be written as `jj diff -r foo--bar`. * New flag `--simplify-parents` on `jj rebase` to apply the same transformation as `jj simplify-parents` on the rebased commits. [#7711](https://github.com/jj-vcs/jj/issues/7711) * `jj rebase --branch` and `jj rebase --source` will no longer return an error if the given argument resolves to an empty revision set (`jj rebase --revisions` already behaved this way). Instead, a message will be printed to inform the user why nothing has changed. * Changed Git representation of conflicted commits to include files from the first side of the conflict. This should prevent unchanged files from being highlighted as "added" in editors when checking out a conflicted commit in a colocated workspace. * New template function `Timestamp::since(ts)` that returns the `TimestampRange` between two timestamps. It can be used in conjunction with `.duration()` in order to obtain a human-friendly duration between two `Timestamp`s. * Added new `jj util snapshot` command to manually or programmatically trigger a snapshot. This introduces an official alternative to the previously-undocumented `jj debug snapshot` command. The Watchman integration has also been updated to use this command instead. * Changed background snapshotting to suppress stdout and stderr to avoid long hangs. * `jj gerrit upload` now supports a variety of new flags documented in [gerrit's documentation](https://gerrit-review.googlesource.com/Documentation/user-upload.html). This includes, for example, `--reviewer=foo@example.com` and `--label=Auto-Submit`. * `jj gerrit upload` now recognizes Change-Id explicitly set via the alternative trailer `Link`, and will generate a `Link: /id/` trailer if `gerrit.review-url` option is set. * `jj gerrit upload` no longer requires the `-r` flag, and will default to uploading what you're currently working on. * Templates now support `Serialize` operations on the result of `map()` and `if()`, when supported by the underlying type. * `jj bookmark rename` now supports `--overwrite-existing` to allow renaming a bookmark even if the new name already exists, effectively replacing the existing bookmark. * Conditional configuration based on environment variables with `--when.environments`. [#8779](https://github.com/jj-vcs/jj/pull/8779) ### Fixed bugs * Windows: use native file locks (`LockFileEx`) instead of polling with file creation, fixing issues with "pending delete" semantics leaving lock files stuck. * `jj` now safely detaches the `HEAD` of alternate Git worktrees if their checked-out branch is moved or deleted during Git export. * `jj file track --include-ignored` now works when `fsmonitor.backend="watchman"`. [#8427](https://github.com/jj-vcs/jj/issues/8427) ### Contributors Thanks to the people who made this release happen! * Aaron Christiansen (@AaronC81) * Andy Brenneke (@abrenneke) * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Bram Geron (@bgeron) * Bryce Berger (@bryceberger) * Caleb White (@calebdw) * countskm (@countdigi) * David Higgs (@higgsd) * Evan Simmons (@estk) * Fedor Sheremetyev (@sheremetyev) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * Hubert Lefevre (@Paluche) * Ian (@chronologos) * Ilya Grigoriev (@ilyagr) * Jaen (@jaens) * Joseph Lou (@josephlou5) * Josh Steadmon (@steadmon) * Martin von Zweigbergk (@martinvonz) * Matt Kulukundis (@fowles) * Matt Stark (@matts1) * max (@pr2502) * Nika Layzell (@mystor) * Philip Metzger (@PhilipMetzger) * Richard Smith (@zygoloid) * Scott Taylor (@scott2000) * Steve Klabnik (@steveklabnik) * Theodore Dubois (@tbodt) * William Phetsinorath (@shikanime) * xtqqczze (@xtqqczze) * Yuya Nishihara (@yuja) ## [0.38.0] - 2026-02-04 ### Release highlights * Per-repo and per-workspace config is now stored outside the repo, for security reasons. This is not a breaking change because we automatically migrate legacy repos to this new format. `.jj/repo/config.toml` and `.jj/workspace-config.toml` should no longer be used. ### Breaking changes * The minimum supported `git` command version is now 2.41.0. macOS users will need to either upgrade "Developer Tools" to 26 or install Git from e.g. Homebrew. * Deprecated `ui.always-allow-large-revsets` setting and `all:` revset modifier have been removed. * `@` revset symbols can also be resolved to remote tags. Tags are prioritized ahead of bookmarks. * Legacy placeholder support used for unset `user.name` or `user.email` has been removed. Commits containing these values will now be pushed with `jj git push` without producing an error. * If any side of a conflicted file is missing a terminating newline, then the materialized file in the working copy will no longer be terminated by a newline. ### Deprecations * The revset function `diff_contains()` has been renamed to `diff_lines()`. ### New features * `jj git fetch` now shows details of abandoned commits (change IDs and descriptions) by default, matching the `jj abandon` output format. [#3081](https://github.com/jj-vcs/jj/issues/3081) * `jj workspace root` now accepts an optional `--name` argument to show the root path of the specified workspace (defaults to the current one). When given a workspace that was created before this release, it errors out. * `jj git push --bookmark ` will now automatically track the bookmark if it isn't tracked with any remote already. * Add `git_web_url([remote])` template function that converts a git remote URL to a web URL, suitable for opening in a browser. Defaults to the "origin" remote. * New `divergent()` revset function for divergent changes. * String pattern values in revsets and templates can now be substituted by aliases. For example, `grep(x) = description(regex:x)` now works. * A new config option `remotes..auto-track-created-bookmarks` behaves similarly to `auto-track-bookmarks`, but it only applies to bookmarks created locally. Setting it to `"*"` is now the closest replacement for the deprecated `git.push-new-bookmarks` option. * `jj tag list` can now be filtered by revset. * Conflict markers will use LF or CRLF as the line ending according to the contents of the file. [#7376](https://github.com/jj-vcs/jj/issues/7376) * New *experimental* `jj git fetch --tag` flag to fetch tags in the same way as bookmarks. If specified, tags won't be fetched implicitly, and only tags matching the pattern will be fetched as `@` tags. The fetched remote tags will be tracked by the local tags of the same name. * New `remote_tags()` revset function to query remote tags. * New builtin `hyperlink()` template function that gracefully falls back to text when outputting to a non-terminal, instead of emitting raw OSC 8 escape codes. [#7592](https://github.com/jj-vcs/jj/issues/7592) ### Fixed bugs * `jj git init --colocate` now refuses to run inside a Git worktree, providing a helpful error message with alternatives. [#8052](https://github.com/jj-vcs/jj/issues/8052) * `jj git push` now ensures that tracked remote bookmarks are updated even if there are no mappings in the Git fetch refspecs. [#5115](https://github.com/jj-vcs/jj/issues/5115) * `jj git fetch`/`push` now forwards most of `git` stderr outputs such as authentication requests. [#5760](https://github.com/jj-vcs/jj/issues/5760) * Conflicted bookmarks and tags in `trunk()` will no longer generate verbose warnings. The configured `trunk()` alias will temporarily be disabled. [#8501](https://github.com/jj-vcs/jj/issues/8501) * Dynamic shell completion for `jj config unset` now only completes configuration options which are set. [#7774](https://github.com/jj-vcs/jj/issues/7774) * Dynamic shell completion no longer attempts to resolve aliases at the completion position. This previously prevented a fully-typed alias from being accepted on some shells and replaced it entirely with its expansion on bash. Now, the completion will only resolve the alias, and suggest candidates accordingly, after the cursor has been advanced to the next position. [#7773](https://github.com/jj-vcs/jj/issues/7773) * Setting the editor via `ui.editor`, `$EDITOR`, or `JJ_EDITOR` now respects shell quoting. * `jj gerrit upload` will no longer swallow errors and surface if changes fail to get pushed to gerrit. [#8568](https://github.com/jj-vcs/jj/issues/8568) * Conflict labels are now preserved correctly when restoring files from commits with different conflict labels. * The empty tree is now always written when the working copy is empty. [#8480](https://github.com/jj-vcs/jj/issues/8480) * When using the Watchman filesystem monitor, changes to .gitignore now trigger a scan of the affected subtree so newly unignored files are discovered. [#8427](https://github.com/jj-vcs/jj/issues/8427) * `jj file track --include-ignored` no longer shows spurious "large file" warnings after successfully tracking the file. [#8826](https://github.com/jj-vcs/jj/issues/8826) * `--quiet` now hides progress bars. ### Contributors Thanks to the people who made this release happen! * Benjamin Davies (@Benjamin-Davies) * Bryce Berger (@bryceberger) * Chris Rose (@offbyone) * Daniel Morsing (@DanielMorsing) * David Fröhlingsdorf (@2079884FDavid) * David Higgs (@higgsd) * David Rieber (@drieber) * Federico G. Schwindt (@fgsch) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * itstrivial * Jeff Turner (@jefft) * Jonas Greitemann (@jgreitemann) * Jonas Helgemo (@jonashelgemo) * Joseph Lou (@josephlou5) * Kaiyi Li (@06393993) * Lukas Wirth (@Veykril) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Paul Smith (@paulsmith) * Pavan Kumar Sunkara (@pksunkara) * Philip Metzger (@PhilipMetzger) * Remo Senekowitsch (@senekor) * Sami Jawhar (@sjawhar) * Scott Taylor (@scott2000) * Simone Cattaneo (@simonecattaneo91) * Steve Klabnik (@steveklabnik) * tom (@lecafard) * Vincent Ging Ho Yim (@cenviity) * \_WD\_ (@0WD0) * xtqqczze (@xtqqczze) * Yuya Nishihara (@yuja) * yz (@yzheng453) ## [0.37.0] - 2026-01-07 ### Release highlights * A new syntax for referring to hidden and divergent change IDs is available: `xyz/n` where `n` is a number. For instance, `xyz/0` refers to the latest version of `xyz`, while `xyz/1` refers to the previous version of `xyz`. This allows you to perform actions like `jj restore --from xyz/1 --to xyz` to restore `xyz` to its previous contents, if you made a mistake. For divergent changes, the numeric suffix will always be shown in the log, allowing you to disambiguate them in a similar manner. ### Breaking changes * [String patterns](docs/revsets.md#string-patterns) in revsets, command arguments, and configuration are now parsed as globs by default. Use `substring:` or `exact:` prefix as needed. * `remotes..auto-track-bookmarks` is now parsed the same way they are in revsets and can be combined with logical operators. * `jj bookmark track`/`untrack` now accepts `--remote` argument. If omitted, all remote bookmarks matching the bookmark names will be tracked/untracked. The old `@` syntax is deprecated in favor of ` --remote=`. * On Windows, symlinks that point to a path with `/` won't be supported. This path is [invalid on Windows](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions). * The template alias `format_short_change_id_with_hidden_and_divergent_info(commit)` has been replaced by `format_short_change_id_with_change_offset(commit)`. * The following deprecated config options have been removed: - `git.push-bookmark-prefix` - `ui.default-description` - `ui.diff.format` - `ui.diff.tool` * The deprecated `commit_id.normal_hex()` template method has been removed. * Template expansion that did not produce a terminating newline will not be fixed up to provide one by `jj log`, `jj evolog`, or `jj op log`. * The `diff` conflict marker style can now use `\\\\\\\` markers to indicate the continuation of a conflict label from the previous line. ### Deprecations * The `git_head()` and `git_refs()` functions will be removed from revsets and templates. `git_head()` should point to the `first_parent(@)` revision in colocated repositories. `git_refs()` can be approximated as `remote_bookmarks(remote=glob:*) | tags()`. ### New features * The `config()` template function now returns `Option` instead of failing if the config value is not found. This allows checking if a config exists (e.g. `if(config("user.email"), ...)`). * Updated the executable bit representation in the local working copy to allow ignoring executable bit changes on Unix. By default we try to detect the filesystem's behavior, but this can be overridden manually by setting `working-copy.exec-bit-change = "respect" | "ignore"`. * `jj workspace add` now also works for empty destination directories. * `jj git remote` family of commands now supports different fetch and push URLs. * `[colors]` table now supports `dim = true` attribute. * In color-words diffs, context line numbers are now rendered with decreased intensity. * Hidden and divergent commits can now be unambiguously selected using their change ID combined with a numeric suffix. For instance, if there are two commits with change ID `xyz`, then one can be referred to as `xyz/0` and the other can be referred to as `xyz/1`. These suffixes are shown in the log when necessary to make a change ID unambiguous. * `jj util gc` now prunes unreachable files in `.jj/repo/store/extra` to save disk space. * Early version of a `jj file search` command for searching for a pattern in files (like `git grep`). * Conflict labels now contain information about where the sides of a conflict came from (e.g. `nlqwxzwn 7dd24e73 "first line of description"`). * `--insert-before` now accepts a revset that resolves to an empty set when used with `--insert-after`. The behavior is similar to `--onto`. * `jj tag list` now supports `--sort` option. * `TreeDiffEntry` type now has a `display_diff_path()` method that formats renames/copies appropriately. * `TreeDiffEntry` now has a `status_char()` method that returns single-character status codes (M/A/D/C/R). * `CommitEvolutionEntry` type now has a `predecessors()` method which returns the predecessor commits (previous versions) of the entry's commit. * `CommitEvolutionEntry` type now has a `inter_diff()` method which returns a `TreeDiff` between the entry's commit and its predecessor version. Optionally accepts a fileset literal to limit the diff. * `jj file annotate` now reports an error for non-files instead of succeeding and displaying no content. * `jj workspace forget` now warns about unknown workspaces instead of failing. ### Fixed bugs * Broken symlink on Windows. [#6934](https://github.com/jj-vcs/jj/issues/6934). * Fixed failure on exporting moved/deleted annotated tags to Git. Moved tags are exported as lightweight tags. * `jj gerrit upload` now correctly handles mixed explicit and implicit Change-Ids in chains of commits ([#8219](https://github.com/jj-vcs/jj/pull/8219)) * `jj git push` now updates partially-pushed remote bookmarks accordingly. [#6787](https://github.com/jj-vcs/jj/issues/6787) * Fixed problem of loading large Git packfiles. https://github.com/GitoxideLabs/gitoxide/issues/2265 * The builtin pager won't get stuck when stdin is redirected. * `jj workspace add` now prevents creating an empty workspace name. * Fixed checkout of symlinks pointing to themselves or `.git`/`.jj` on Unix. The problem would still remain on Windows if symlinks are enabled. [#8348](https://github.com/jj-vcs/jj/issues/8348) * Fixed a bug where jj would fail to read git delta objects from pack files. https://github.com/GitoxideLabs/gitoxide/issues/2344 ### Contributors Thanks to the people who made this release happen! * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Bryce Berger (@bryceberger) * Carlos Knippschild (@chuim) * Cole Helbling (@cole-h) * David Higgs (@higgsd) * Eekle (@Eekle) * Gaëtan Lehmann (@glehmann) * Ian Wrzesinski (@isuffix) * Ilya Grigoriev (@ilyagr) * Julian Howes (@jlnhws) * Kaiyi Li (@06393993) * Lukas Krejci (@metlos) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Ori Avtalion (@salty-horse) * Scott Taylor (@scott2000) * Shaoxuan (Max) Yuan (@ffyuanda) * Stephen Jennings (@jennings) * Steve Fink (@hotsphink) * Steve Klabnik (@steveklabnik) * Theo Buehler (@botovq) * Thomas Castiglione (@gulbanana) * Vincent Ging Ho Yim (@cenviity) * xtqqczze (@xtqqczze) * Yuantao Wang (@0WD0) * Yuya Nishihara (@yuja) ## [0.36.0] - 2025-12-03 ### Release highlights * The documentation has moved from to . 301 redirects are being issued towards the new domain, so any existing links should not be broken. * Fixed race condition that could cause divergent operations when running concurrent `jj` commands in colocated repositories. It is now safe to continuously run e.g. `jj log` without `--ignore-working-copy` in one terminal while you're running other commands in another terminal. [#6830](https://github.com/jj-vcs/jj/issues/6830) * `jj` now ignores `$PAGER` set in the environment and uses `less -FRX` on most platforms (`:builtin` on Windows). See [the docs](docs/config.md#pager) for more information, and [#3502](https://github.com/jj-vcs/jj/issues/3502) for motivation. ### Breaking changes * In [filesets or path patterns](docs/filesets.md#file-patterns), glob matching is enabled by default. You can use `cwd:"path"` to match literal paths. * In the following commands, [string pattern arguments](docs/revsets.md#string-patterns) are now parsed the same way they are in revsets and can be combined with logical operators: `jj bookmark delete`/`forget`/`list`/`move`, `jj tag delete`/`list`, `jj git clone`/`fetch`/`push` * In the following commands, unmatched bookmark/tag names is no longer an error. A warning will be printed instead: `jj bookmark delete`/`forget`/`move`/`track`/`untrack`, `jj tag delete`, `jj git clone`/`push` * The default string pattern syntax in revsets will be changed to `glob:` in a future release. You can opt in to the new default by setting `ui.revsets-use-glob-by-default=true`. * Upgraded `scm-record` from v0.8.0 to v0.9.0. See release notes at . * The minimum supported Rust version (MSRV) is now 1.89. * On macOS, the deprecated config directory `~/Library/Application Support/jj` is not read anymore. Use `$XDG_CONFIG_HOME/jj` instead (defaults to `~/.config/jj`). * Sub-repos are no longer tracked. Any directory containing `.jj` or `.git` is ignored. Note that Git submodules are unaffected by this. ### Deprecations * The `--destination`/`-d` arguments for `jj rebase`, `jj split`, `jj revert`, etc. were renamed to `--onto`/`-o`. The reasoning is that `--onto`, `--insert-before`, and `--insert-after` are all destination arguments, so calling one of them `--destination` was confusing and unclear. The old names will be removed at some point in the future, but we realize that they are deep in muscle memory, so you can expect an unusually long deprecation period. * `jj describe --edit` is deprecated in favor of `--editor`. * The config options `git.auto-local-bookmark` and `git.push-new-bookmarks` are deprecated in favor of `remotes..auto-track-bookmarks`. For example: ```toml [remotes.origin] auto-track-bookmarks = "glob:*" ``` For more details, refer to [the docs](docs/config.md#automatic-tracking-of-bookmarks). * The flag `--allow-new` on `jj git push` is deprecated. In order to push new bookmarks, please track them with `jj bookmark track`. Alternatively, consider setting up an auto-tracking configuration to avoid the chore of tracking bookmarks manually. For example: ```toml [remotes.origin] auto-track-bookmarks = "glob:*" ``` For more details, refer to [the docs](docs/config.md#automatic-tracking-of-bookmarks). ### New features * `jj commit`, `jj describe`, `jj squash`, and `jj split` now accept `--editor`, which ensures an editor will be opened with the commit description even if one was provided via `--message`/`-m`. * All `jj` commands show a warning when the provided `fileset` expression doesn't match any files. * Added `files()` template function to `DiffStats`. This supports per-file stats like `lines_added()` and `lines_removed()` * Added `join()` template function. This is different from `separate()` in that it adds a separator between all arguments, even if empty. * `RepoPath` template type now has a `absolute() -> String` method that returns the absolute path as a string. * Added `format_path(path)` template alias that controls how file paths are printed with `jj file list`. * New built-in revset aliases `visible()` and `hidden()`. * Unquoted `*` is now allowed in revsets. `bookmarks(glob:foo*)` no longer needs quoting. * `jj prev/next --no-edit` now generates an error if the working-copy has some children. * A new config option `remotes..auto-track-bookmarks` can be set to a string pattern. New bookmarks matching it will be automatically tracked for the specified remote. See [the docs](docs/config.md#automatic-tracking-of-bookmarks). * `jj log` now supports a `--count` flag to print the number of commits instead of displaying them. * `Commit` type now has a `conflicted_files()` method that returns a list of files with merge conflicts. * `TreeEntry` type now has a `conflict_side_count()` method that returns the number of sides in a merge conflict (1 for non-conflicted files, 2 or more for conflicts). ### Fixed bugs * `jj fix` now prints a warning if a tool failed to run on a file. [#7971](https://github.com/jj-vcs/jj/issues/7971) * Shell completion now works with non‑normalized paths, fixing the previous panic and allowing prefixes containing `.` or `..` to be completed correctly. [#6861](https://github.com/jj-vcs/jj/issues/6861) * Shell completion now always uses forward slashes to complete paths, even on Windows. This renders completion results viable when using jj in Git Bash. [#7024](https://github.com/jj-vcs/jj/issues/7024) * Unexpected keyword arguments now return a parse failure for the `coalesce()` and `concat()` templating functions. * Nushell completion script documentation add `-f` option, to keep it up to date. [#8007](https://github.com/jj-vcs/jj/issues/8007) * Ensured that with Git submodules, remnants of your submodules do not show up in the working copy after running `jj new`. [#4349](https://github.com/jj-vcs/jj/issues/4349) ### Contributors Thanks to the people who made this release happen! * abgox (@abgox) * ase (@adamse) * Björn Kautler (@Vampire) * Bryce Berger (@bryceberger) * Chase Naples (@cnaples79) * David Higgs (@higgsd) * edef (@edef1c) * Evan Mesterhazy (@emesterhazy) * Fedor (@sheremetyev) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * Hubert Lefevre (@Paluche) * Ilya Grigoriev (@ilyagr) * Jonas Greitemann (@jgreitemann) * Joseph Lou (@josephlou5) * Julia DeMille (@judemille) * Kaiyi Li (@06393993) * Kyle Lippincott (@spectral54) * Lander Brandt (@landaire) * Lucio Franco (@LucioFranco) * Luke Randall (@lukerandall) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Mitchell Skaggs (@magneticflux-) * Peter Schilling (@schpet) * Philip Metzger (@PhilipMetzger) * QingyaoLin (@QingyaoLin) * Remo Senekowitsch (@senekor) * Scott Taylor (@scott2000) * Stephen Jennings (@jennings) * Steve Klabnik (@steveklabnik) * Tejas Sanap (@whereistejas) * Tommi Virtanen (@tv42) * Velociraptor115 (@Velociraptor115) * Vincent Ging Ho Yim (@cenviity) * Yuya Nishihara (@yuja) ## [0.35.0] - 2025-11-05 ### Release highlights * Workspaces can now have their own separate configuration. For instance, you can use `jj config set --workspace` to update a configuration option only in the current workspace. * After creating a local bookmark, it is now possible to use `jj bookmark track` to associate the bookmark with a specific remote before pushing it. When pushing a tracked bookmark, it is not necessary to use `--allow-new`. * The new `jj git colocation enable` and `jj git colocation disable` commands allow converting between colocated and non-colocated workspaces. ### Breaking changes * The `remote_bookmarks(remote=pattern)` revset now includes Git-tracking bookmarks if the specified `pattern` matches `git`. The default is `remote=~exact:"git"` as before. * The deprecated flag `--summary` of `jj abandon` has been removed. * The deprecated command `jj backout` has been removed, use `jj revert` instead. * The following deprecated config options have been removed: - `signing.sign-all` - `core.watchman.register_snapshot_trigger` - `diff.format` ### Deprecations * `jj bisect run --command ` is deprecated in favor of `jj bisect run -- `. * `jj metaedit --update-committer-timestamp` was renamed to `jj metaedit --force-rewrite` since the old name (and help text) incorrectly suggested that the committer name and email would _not_ be updated. ### New features * Workspaces may have an additional layered configuration, located at `.jj/workspace-config.toml`. `jj config` subcommands which took layer options like `--repo` now also support `--workspace`. * `jj bookmark track` can now associate new local bookmarks with remote. Tracked bookmarks can be pushed without `--allow-new`. [#7072](https://github.com/jj-vcs/jj/issues/7072) * The new `jj git colocation` command provides sub-commands to show the colocation state (`status`), to convert a non-colocated workspace into a colocated workspace (`enable`), and vice-versa (`disable`). * New `jj tag set`/`delete` commands to create/update/delete tags locally. Created/updated tags are currently always exported to Git as lightweight tags. If you would prefer them to be exported as annotated tags, please give us feedback on [#7908](https://github.com/jj-vcs/jj/issues/7908). * Templates now support a `.split(separator, [limit])` method on strings to split a string into a list of substrings. * `-G` is now available as a short form of `--no-graph` in `jj log`, `jj evolog`, `jj op log`, `jj op show` and `jj op diff`. * `jj metaedit` now accepts `-m`/`--message` option to non-interactively update the change description. * The `CryptographicSignature.key()` template method now also works for SSH signatures and returns the corresponding public key fingerprint. * Added `template-aliases.empty_commit_marker`. Users can override this value in their config to change the "(empty)" label on empty commits. * Add support for `--when.workspaces` config scopes. * Add support for `--when.hostnames` config scopes. This allows configuration to be conditionally applied based on the hostname set in `operation.hostname`. * `jj bisect run` accepts the command and arguments to pass to the command directly as positional arguments, such as `jj bisect run --range=..main -- cargo check --all-targets`. * Divergent changes are no longer marked red in immutable revisions. Since the revision is immutable, the user shouldn't take any action, so the red color was unnecessarily alarming. * New commit template keywords `local`/`remote_tags` to show only local/remote tags. These keywords may be useful in non-colocated Git repositories where local and exported `@git` tags can point to different revisions. * `jj git clone` now supports the `--branch` option to specify the branch(es) to fetch during clone. If present, the first matching branch is used as the working-copy parent. * Revsets now support logical operators in string patterns. * `jj file track` now accepts an `--include-ignored` flag to track files that are ignored by `.gitignore` or exceed the `snapshot.max-new-file-size` limit. [#2837](https://github.com/jj-vcs/jj/issues/2837) ### Fixed bugs * `jj metaedit --author-timestamp` twice with the same value no longer edits the change twice in some cases. * `jj squash`: fixed improper revision rebase when both `--insert-after` and `--insert-before` were used. * `jj undo` can now revert "fetch"/"import" operation that involves tag updates. [#6325](https://github.com/jj-vcs/jj/issues/6325) * Fixed parsing of `files(expr)` revset expression including parentheses. [#7747](https://github.com/jj-vcs/jj/issues/7747) * Fixed `jj describe --stdin` to append a final newline character. ### Contributors Thanks to the people who made this release happen! * Alpha Chen (@kejadlen) * Angel Ezquerra (@AngelEzquerra) * ase (@adamse) * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * bipul (@bipulmgr) * Brian Schroeder (@bts) * Bryce Berger (@bryceberger) * Cole Helbling (@cole-h) * Daniel Luz (@mernen) * David Higgs (@higgsd) * Defelo (@Defelo) * Fedor (@sheremetyev) * Gabriel Goller (@kaffarell) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * Ilya Grigoriev (@ilyagr) * Isaac Corbrey (@icorbrey) * James Coman (@jamescoman) * Joseph Lou (@josephlou5) * Lander Brandt (@landaire) * Martin von Zweigbergk (@martinvonz) * Michael Chirico (@MichaelChirico) * Owen Brooks (@owenbrooks) * Peter Schilling (@schpet) * Philip Metzger (@PhilipMetzger) * Remo Senekowitsch (@senekor) * Ross Smyth (@RossSmyth) * Scott Taylor (@scott2000) * Steve Fink (@hotsphink) * Steve Klabnik (@steveklabnik) * Theo Buehler (@botovq) * Theodore Dubois (@tbodt) * Theodore Keloglou (@sirodoht) * Yuya Nishihara (@yuja) ## [0.34.0] - 2025-10-01 ### Release highlights * Support for uploading changes to Gerrit Code Review with `jj gerrit upload`. This lets you submit trees or multiple stacks of work at once. Support for fetching changes, submitting changes, and other operations is not yet implemented. This should be considered experimental, and we welcome feedback on using it. * Support for automated bisection using `jj bisect run`; this will continuously bisect a commit range until a given commit is found to trigger a bug. ### Breaking changes * Git-based repositories are now colocated by default. Configure `git.colocate = false` to keep the previous behavior. * Conflicts written by jj < 0.11 are no longer supported. They will now appear as regular files with a `.jjconflict` suffix and JSON contents. * The minimum supported Rust version (MSRV) is now 1.88. ### Deprecations * Various flags on `jj describe` and `jj commit` have been deprecated in favor of `jj metaedit`. They are: * `describe`: `--author`, `--reset-author`, `--no-edit` * `commit`: `--author`, `--reset-author` * The storage format of remote bookmarks and tags has changed. Remote bookmarks will be written in both old and new formats to retain forward compatibility. Git-tracking tags are also recorded for future native tagging support. This change should be transparent, but let us know if you see anything odd. ### New features * The new command `jj bisect run` uses binary search to find a commit that introduced a bug. * The default editor on Unix is now `nano` instead of `pico`. * New config option `merge.hunk-level = "word"` to enable word-level merging. * New config option `merge.same-change = "keep"` to disable lossy resolution rule for same-change conflicts. [#6369](https://github.com/jj-vcs/jj/issues/6369) * Templates now support a `replace()` method on strings for pattern-based string replacement with optional limits. Supports all string patterns, including regex with capture groups (e.g. `"hello world".replace(regex:'(\w+) (\w+)', "$2 $1")`). * A new builtin `hyperlink(url, text)` template alias creates clickable hyperlinks using [OSC8 escape sequences](https://github.com/Alhadis/OSC8-Adoption) for terminals that support them. * Added a new conditional configuration `--when.platforms` to include settings only on certain platforms. * External diff commands now support substitution variable `$width` for the number of available terminal columns. * The new `jj gerrit upload` command allows you to upload given revisions to an instance of Gerrit Code Review. * `jj bookmark create/set/move` use the working copy as a default again and no longer require an explicit revision argument. This walks back a deprecation from `jj 0.26`, as the community feedback was mostly negative. Instead, bookmarking an empty revision now produces a warning, to help you catch the case where you meant to bookmark the parent revision. * The revset function `exactly(x, n)` will now evaluate `x` and error if it does not have exactly `n` elements. * `jj util exec` now matches the exit status of the program it runs, and doesn't print anything. * `jj config get` now supports displaying array and table config values. * `jj util exec` sets the environment variable `JJ_WORKSPACE_ROOT` ### Fixed bugs * Fetching repositories that have submodules no longer errors even if `submodule.recurse=true` is set in `.gitconfig` (but jj still isn't able to fetch the submodules or to operate on them). ### Contributors Thanks to the people who made this release happen! * Angel Ezquerra (@AngelEzquerra) * Anton Älgmyr (@algmyr) * Antonin Delpeuch (@wetneb) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Conner Petzold (@ConnerPetzold) * Daniel Luz (@mernen) * Daniele Sassoli (@DanieleSassoli) * David Barsky (@davidbarsky) * Dinu Blanovschi (@dnbln) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * Ian Wrzesinski (@isuffix) * Ilya Grigoriev (@ilyagr) * Isaac Corbrey (@icorbrey) * Ivan Petkov (@ipetkov) * Jonas Fierlings (@PigeonF) * loudgolem (@phanirithvij) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Matt T. Proud (@matttproud) * Michael Pratt (@prattmic) * MochikoNyan (@MochikoNyan) * Philip Metzger (@PhilipMetzger) * Reilly Brogan (@ReillyBrogan) * Remo Senekowitsch (@senekor) * Reuven Lazarus (@rlazarus) * Scott Taylor (@scott2000) * Stephen Jennings (@jennings) * Steven Sherry (@Steven0351) * Theo Buehler (@botovq) * Yuya Nishihara (@yuja) ## [0.33.0] - 2025-09-03 ### Release highlights * `jj undo` is now *sequential*: invoking it multiple times in sequence repeatedly undoes actions in the operation log. Previously, `jj undo` would only undo *the most recent* operation in the operation log. As a result, a new `jj redo` command has been added. * Experimental support for improving query performance over filesets and file queries (like `jj log path/to/file.txt`) has been added. This is not enabled by default. To enable this, you must use the `jj debug index-changed-paths` command. ### Breaking changes * `jj evolog` templates now accept `CommitEvolutionEntry` as context type. To get `Commit` properties, use `commit.()`. To customize the default output, set `templates.evolog` instead of `templates.log`. * `jj op show` now uses `templates.op_show` configuration for its default template instead of `templates.op_log`. * The deprecated config option `git.auto-local-branch` has been removed. Use `git.auto-local-bookmark` instead. * The deprecated `Signature.username()` template method has been removed. Use `Signature.email().local()` instead. * The deprecated `--config-toml` flag has been removed. Use `--config=NAME=VALUE` or `--config-file=PATH` instead. * `jj undo` can now undo multiple operations progressively by calling it repeatedly, whereas previously, running `jj undo` twice was previously a no-op (it only undid the last change). * `jj git fetch` will now only fetch the refspec patterns configured on remotes when the `--branch` option is omitted. Only simple refspec patterns are currently supported, and anything else (like refspecs which rename branches) will be ignored. * The `conflict` label used for coloring log graph nodes was renamed to `conflicted`. ### Deprecations * The on-disk index format has changed. `jj` will write index files in both old and new formats, so old `jj` versions should be able to read these index files. This compatibility layer will be removed in a future release. * `jj op undo` is deprecated in favor of `jj op revert`. (`jj undo` is still available, but with new semantics. See also the breaking changes above.) * The argument `` of `jj undo` is deprecated in favor of `jj op revert `. * The `--what` flag on `jj undo` is deprecated. Consider using `jj op restore --what` instead. ### New features * The new command `jj redo` can progressively redo operations that were previously undone by multiple calls to `jj undo`. * Templates now support `any()` and `all()` methods on lists to check whether any or all elements satisfy a predicate. Example: `parents.any(|c| c.mine())` returns true if any parent commit is authored by the user. * Add experimental support for indexing changed paths, which will speed up `jj log PATH` query, `jj file annotate`, etc. The changed-path index can be enabled by `jj debug index-changed-paths` command. Indexing may take tens of minutes depending on the number of merge commits. The indexing command UI is subject to change. [#4674](https://github.com/jj-vcs/jj/issues/4674) * `jj config list` now supports `-T'json(self) ++ "\n"'` serialization output. * `jj file show` now accepts `-T`/`--template` option to insert file metadata. * The template language now allows arbitrary whitespace between any operators. * The new configuration option `git.colocate=boolean` controls whether or not Git repositories are colocated by default. * Both `jj git clone` and `jj git init` now take a `--no-colocate` flag to disable colocation (in case `git.colocate` is set to `true`.) * `jj git remote add` and `jj git clone` now support `--fetch-tags` to control when tags are fetched for all subsequent fetches. * `jj git fetch` now supports `--tracked` to fetch only tracked bookmarks. * `jj diff --stat` now shows the change in size to binary files. * `jj interdiff`, `jj evolog -p`, and `jj op log -p` now show diff of commit descriptions. * `jj log` and `jj op log` output can now be anonymized with the `builtin_log_redacted` and `builtin_op_log_redacted` templates. * `jj git init` now checks for an `upstream` remote in addition to `origin` when setting the repository-level `trunk()` alias. The `upstream` remote takes precedence over `origin` if both exist. * Add the `jj metaedit` command, which modifies a revision's metadata. This can be used to generate a new change-id, which may help resolve some divergences. It also has options to modify author name, email and timestamp, as well as to modify committer timestamp. * Filesets now support case-insensitive glob patterns with the `glob-i:`, `cwd-glob-i:`, and `root-glob-i:` pattern kinds. For example, `glob-i:"*.rs"` will match both `file.rs` and `FILE.RS`. * `jj op show` now accepts `-T`/`--template` option to customize the operation output using template expressions, similar to `jj op log`. Also added `--no-op-diff` flag to suppress the operation diff. * A nearly identical string pattern system as revsets is now supported in the template language, and is exposed as `string.match(pattern)`. * Merge tools can use the `$path` argument to learn where the file they are merging will end up in the repository. ### Fixed bugs * `jj git clone` now correctly fetches all tags, unless `--fetch-tags` is explicitly specified, in which case the specified option will apply for both the initial clone and subsequent fetches. * Operation and working-copy state files are now synchronized to disk on save. This will mitigate data corruption on system crash. [#4423](https://github.com/jj-vcs/jj/issues/4423) ### Packaging changes * The test suite no longer optionally uses Taplo CLI or jq, and packagers can remove them as dependencies if present. ### Contributors * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Christian Hufnagel (@OvidiusCicero) * Clément (@drawbu) * Daniel Luz (@mernen) * Emily (@emilazy) * Evan Martin (@evmar) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * Graham Christensen (@grahamc) * Hegui Dai (@Natural-selection1) * Ian Wrzesinski (@isuffix) * Ilya Grigoriev (@ilyagr) * Isaac Corbrey (@icorbrey) * Ivan Petkov (@ipetkov) * Joaquín Triñanes (@JoaquinTrinanes) * Kaiyi Li (@06393993) * Martin von Zweigbergk (@martinvonz) * Nigthknight (@nigthknight) * Nikhil Marathe (@nikhilm) * Remo Senekowitsch (@senekor) * Tijs-B (@Tijs-B) * Yuya Nishihara (@yuja) ## [0.32.0] - 2025-08-06 ### Breaking changes * In revsets, symbol expressions (such as change ID prefix) no longer resolve to multiple revisions, and error out if resolved to more than one revisions. Use `change_id(prefix)` or `bookmarks(exact:name)` to query divergent changes or conflicted bookmarks. Commands like `jj rebase` no longer require `all:` to specify multiple destination revisions. * `jj op abandon` now discards previous versions of a change (or predecessors) if they become unreachable from the operation history. The evolution history is truncated accordingly. Once `jj op abandon` and `jj util gc` are run in a repository, old versions of `jj` might get "commit not found" error on `jj evolog`. * `commit.working_copies()` template method now returns `List` * The previously predefined `amend` alias has been removed. You can restore it by setting the config `aliases.amend = ["squash"]`. ### Deprecations * The `all:` revset modifier and `ui.always-allow-large-revsets` setting is planned to be removed in a future release. [#6016](https://github.com/jj-vcs/jj/issues/6016) * Rename the `core.fsmonitor` and `core.watchman` settings to `fsmonitor.backend`, and `fsmonitor.watchman` respectively. ### New features * `jj workspace list` now accepts `-T`/`--template` option to customize its output via templates. * Added `templates.workspace_list` template to customize the output of `jj workspace list`. * `jj fix` now buffers the standard error stream from subprocesses and emits the output from each all at once. The file name is printed before the output. * `jj status` now collapses fully untracked directories into one line. It still fully traverses them while snapshotting but they won't clutter up the output with all of their contents. * Add the `working-copy.eol-conversion` config which is similar to the git `core.autocrlf` config. A heuristics is used to detect if a file is a binary file to prevent the EOL conversion from changing binary files unexpectedly. * Add a `.parents()` method to the [`Operation`](docs/templates.md#operation-type) type in the templating language. * Merge tools config can now explicitly forbid using them as diff editors or diff formatters. Built-in tools that do not function well as diff editing tools or as diff formatters will now report an error when used as such. * `jj diffedit` now accepts filesets to edit only the specified paths. * AnnotationLine objects in templates now have a `original_line_number() -> Integer` method. * Commit templates now support `.files()` to list all existing files at that revision. * Glob patterns now support `{foo,bar}` syntax. There may be subtle behavior changes as we use the [globset](https://crates.io/crates/globset) library now. * The new `bisect(x)` revset function can help bisect a range of commits to find when a bug was introduced. * New `first_parent()` and `first_ancestors()` revset functions which are similar to `parents()` and `ancestors()`, but only traverse the first parent of each commit (similar to Git's `--first-parent` option). * New `signing.backends.ssh.revocation-list` config for specifying a list of revoked public keys for commit signature verification. * `jj fix` commands now replace `$root` with the workspace's root path. This is useful for tools stored inside the workspace. ### Fixed bugs * Fixed an error in `jj util gc` caused by the empty blob being missing from the Git store. [#7062](https://github.com/jj-vcs/jj/issues/7062) * `jj op diff -p` and `jj op log -p` now show content diffs from the first predecessor only. [#7090](https://github.com/jj-vcs/jj/issues/7090) * `jj git fetch` no longer shows `NaN%` progress when connecting to slow remotes. [#7155](https://github.com/jj-vcs/jj/issues/7155) ### Contributors Thanks to the people who made this release happen! * adamnemecek (@adamnemecek) * Alexander Kobjolke (@jakalx) * Apromixately (@Apromixately) * Austin Seipp (@thoughtpolice) * Bryce Berger (@bryceberger) * Daniel Danner (@dnnr) * Daniel Luz (@mernen) * Evan Martin (@evmar) * George Christou (@gechr) * George Elliott-Hunter (@george-palmsens) * Hubert Głuchowski (@afishhh) * Ilya Grigoriev (@ilyagr) * Jade Lovelace (@lf-) * Jake Martin (@jake-m-commits) * Jan Klass (@Kissaki) * Joaquín Triñanes (@JoaquinTrinanes) * Josh Steadmon (@steadmon) * Kaiyi Li (@06393993) * Martin von Zweigbergk (@martinvonz) * Nigthknight (@nigthknight) * Ori Avtalion (@salty-horse) * Pablo Brasero (@pablobm) * Pavan Kumar Sunkara (@pksunkara) * Philip Metzger (@PhilipMetzger) * phoebe (@phoreverpheebs) * Remo Senekowitsch (@senekor) * Scott Taylor (@scott2000) * Stephen Jennings (@jennings) * Theo Buehler (@botovq) * Tyarel8 (@Tyarel8) * Yuya Nishihara (@yuja) ## [0.31.0] - 2025-07-02 ### Breaking changes * Revset expressions like `hidden_id | description(x)` now [search the specified hidden revision and its ancestors](docs/revsets.md#hidden-revisions) as well as all visible revisions. * Commit templates no longer normalize `description` by appending final newline character. Use `description.trim_end() ++ "\n"` if needed. ### Deprecations * The `git.push-bookmark-prefix` setting is deprecated in favor of `templates.git_push_bookmark`, which supports templating. The old setting can be expressed in template as `"" ++ change_id.short()`. ### New features * New `change_id(prefix)`/`commit_id(prefix)` revset functions to explicitly query commits by change/commit ID prefix. * The `parents()` and `children()` revset functions now accept an optional `depth` argument. For instance, `parents(x, 3)` is equivalent to `x---`, and `children(x, 3)` is equivalent to `x+++`. * `jj evolog` can now follow changes from multiple revisions such as divergent revisions. * `jj diff` now accepts `-T`/`--template` option to customize summary output. * Log node templates are now specified in toml rather than hardcoded. * Templates now support `json(x)` function to serialize values in JSON format. * The ANSI 256-color palette can be used when configuring colors. For example, `colors."diff removed token" = { bg = "ansi-color-52", underline = false }` will apply a dark red background on removed words in diffs. ### Fixed bugs * `jj file annotate` can now process files at a hidden revision. * `jj op log --op-diff` no longer fails at displaying "reconcile divergent operations." [#4465](https://github.com/jj-vcs/jj/issues/4465) * `jj util gc --expire=now` now passes the corresponding flag to `git gc`. * `change_id`/`commit_id.shortest()` template functions now take conflicting bookmark and tag names into account. [#2416](https://github.com/jj-vcs/jj/issues/2416) * Fixed lockfile issue on stale file handles observed with NFS. ### Packaging changes * `aarch64-windows` builds (release binaries and `main` snapshots) are now provided. ### Contributors Thanks to the people who made this release happen! * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * Cyril Plisko (@imp) * Daniel Luz (@mernen) * Gaëtan Lehmann (@glehmann) * Gilad Woloch (@giladwo) * Greg Morenz (@gmorenz) * Igor Velkov (@iav) * Ilya Grigoriev (@ilyagr) * Jade Lovelace (@lf-) * Jonas Greitemann (@jgreitemann) * Josh Steadmon (@steadmon) * juemrami (@juemrami) * Kaiyi Li (@06393993) * Lars Francke (@lfrancke) * Martin von Zweigbergk (@martinvonz) * Osama Qarem (@osamaqarem) * Philip Metzger (@PhilipMetzger) * raylu (@raylu) * Scott Taylor (@scott2000) * Vincent Ging Ho Yim (@cenviity) * Yuya Nishihara (@yuja) ## [0.30.0] - 2025-06-04 ### Release highlights * The experimental support from release 0.29.0 for transferring the change ID to/from Git remotes has been enabled by default. The change ID is stored in the Git commit itself (in a commit header called `change-id`), which means it will be transferred by regular `git push` etc. Please let us know if you run into any problems with it. You can disable it setting `git.write-change-id-header`. Note that some Git remotes (e.g GitLab) and some Git commands (e.g. `git rebase`) do not preserve the change ids when they rewrite commits. * `jj rebase` now automatically abandons divergent commits if another commit with the same change ID is already present in the destination with identical changes. * `jj split` has gained `--message`, `--insert-before`, `--insert-after`, and `--destination` options. * `jj evolog` can show associated operations for commits created by new jj versions. ### Breaking changes * The old `libgit2` code path for fetches and pushes has been removed, and the `git.subprocess` setting along with it. * In templates, bookmark/tag/remote names are now formatted in revset symbol notation. The type of `bookmark.remote()` is changed to `Option<_>`. `bookmark.remote() == "foo"` still works, but `bookmark.remote().()` might need `if(bookmark.remote(), ..)` to suppress error. * `jj rebase` now automatically abandons divergent commits if another commit with the same change ID is already present in the destination with identical changes. To keep these divergent commits, use the `--keep-divergent` flag. * The deprecated `--skip-empty` flag for `jj rebase` has been removed. Use the `--skip-emptied` flag instead. * The deprecated `jj branch` subcommands have been removed. Use the `jj bookmark` subcommands instead. * `jj util completion` now requires the name of the shell as a positional argument and no longer produces Bash completions by default. The deprecated optional arguments for different shells have been removed. * External diff tools are now run in the temporary directory containing the before (`left`) and after (`right`) directories, making diffs appear more pleasing for tools that display file paths prominently. Users can opt out of this by setting `merge-tools..diff-do-chdir = false`, but this will likely be removed in a future release. Please report any issues you run into. * The minimum supported Rust version (MSRV) is now 1.85.0. ### Deprecations * The `ui.diff.format` and `ui.diff.tool` config options have been merged as `ui.diff-formatter`. The builtin format can be specified as `:` (e.g. `ui.diff-formatter=":git"` for Git diffs.) * The `.normal_hex()` method will be removed from the `CommitId` template type. It's useful only for the `ChangeId` type. ### New features * `jj split` has gained a `--message` option to set the description of the commit with the selected changes. * `jj split` has gained the ability to place the revision with the selected changes anywhere in the revision tree with the `--insert-before`, `--insert-after` and `--destination` command line flags. * Added `git.track-default-bookmark-on-clone` setting to control whether to track the default remote bookmark on `jj git clone`. * Templates can now do arithmetic on integers with the `+`, `-`, `*`, `/`, and `%` infix operators. * Evolution history is now stored in the operation log. `jj evolog` can show associated operations for commits created by new jj versions. ### Fixed bugs * Work around a git issue that could cause subprocess operations to hang if the `core.fsmonitor` gitconfig is set in the global or system gitconfigs. [#6440](https://github.com/jj-vcs/jj/issues/6440) * `jj parallelize` can now parallelize groups of changes that _start_ with an immutable change, but do not contain any other immutable changes. * `jj` will no longer warn about deprecated paths on macOS if the configured XDG directory is the deprecated one (~/Library/Application Support). * The builtin diff editor now correctly handles splitting changes where a file is replaced by a directory of the same name. [#5189](https://github.com/jj-vcs/jj/issues/5189) ### Packaging changes * Due to the removal of the `libgit2` code path, packagers should remove any dependencies on `libgit2`, `libssh2`, Zlib, OpenSSL, and `pkg-config`, and ensure they are not setting the Cargo `git2` or `vendored-openssl` features. ### Contributors Thanks to the people who made this release happen! * Alper Cugun (@alper) * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * Benjamin Tan (@bnjmnt4n) * Bryce Berger (@bryceberger) * Colin Nelson (@orthros) * Doug Stephen (@dljsjr) * Emily (@emilazy) * Eyvind Bernhardsen (@eyvind) * Felix Geisendörfer (@felixge) * Gaëtan Lehmann (@glehmann) * Ilya Grigoriev (@ilyagr) * Isaac Corbrey (@icorbrey) * Jonas Greitemann (@jgreitemann) * Josep Mengual (@truita) * kkoang (@kkoang) * Manuel Mendez (@mmlb) * Marshall Bowers (@maxdeviant) * Martin von Zweigbergk (@martinvonz) * Mateus Auler (@mateusauler) * Michael Pratt (@prattmic) * Nicole Patricia Mazzuca (@strega-nil) * Philip Metzger (@PhilipMetzger) * Scott Taylor (@scott2000) * T6 (@tjjfvi) * Vincent Ging Ho Yim (@cenviity) * Winter (@winterqt) * Yuya Nishihara (@yuja) ## [0.29.0] - 2025-05-07 ### Release highlights * Experimental support for transferring the change ID to/from Git remotes behind configuration setting `git.write-change-id-header`. If this is enabled, the change ID will be stored in the Git commit itself (in a commit header called `change-id`), which means it will be transferred by regular `git push` etc. This is an evolving feature that currently defaults to "false". This default will likely change in the future as we gain confidence with forge support and user expectations. ### Breaking changes * `jj git push -c`/`--change` no longer moves existing local bookmarks. * The `editor-*.jjdescription` files passed to your editor by e.g. `jj describe` are now written to your system's temporary directory instead of `.jj/repo/`. ### Deprecations * `git.subprocess = false` has been deprecated, and the old `libgit2` code path for fetches and pushes will be removed entirely in 0.30. Please report any remaining issues you have with the Git subprocessing path. * `ui.default-description` has been deprecated, and will be migrated to `template-aliases.default_commit_description`. Please also consider using [`templates.draft_commit_description`](docs/config.md#default-description), and/or [`templates.commit_trailers`](docs/config.md#commit-trailers). * On macOS, config.toml files in `~/Library/Application Support/jj` are deprecated; one should instead use `$XDG_CONFIG_HOME/jj` (defaults to `~/.config/jj`) ### New features * Color-words diff has gained [an option to compare conflict pairs without materializing](docs/config.md#color-words-diff-options). * `jj show` patches can now be suppressed with `--no-patch`. * Added `ui.bookmark-list-sort-keys` setting to configure default sort keys for the `jj bookmark list` command. * New `signed` revset function to filter for cryptographically signed commits. * `jj describe`, `jj commit`, `jj new`, `jj squash` and `jj split` add the commit trailers, configured in the `commit_trailers` template, to the commit description. Use cases include DCO Sign Off and Gerrit Change Id. * Added `duplicate_description` template, which allows [customizing the descriptions of the commits `jj duplicate` creates](docs/config.md#duplicate-commit-description). * `jj absorb` can now squash a deleted file if it was added by one of the destination revisions. * Added `ui.streampager.show-ruler` setting to configure whether the ruler should be shown when the builtin pager starts up. * `jj git fetch` now warns instead of erroring for unknown `git.fetch` remotes if other remotes are available. * Commit objects in templates now have `trailers() -> List`, the Trailer objects have `key() -> String` and `value() -> String`. * `jj config edit` will now roll back to previous version if a syntax error has been introduced in the new config. * When using dynamic command-line completion, revision names will be completed in more complex expressions. For example, typing `jj log -r first-bookmark..sec` and then pressing Tab could complete the expression to `first-bookmark..second-bookmark`. ### Fixed bugs * Fixed crash on change-delete conflict resolution. [#6250](https://github.com/jj-vcs/jj/issues/6250) * The builtin diff editor now tries to preserve unresolved conflicts. [#4963](https://github.com/jj-vcs/jj/issues/4963) * Fixed bash and zsh shell completion when completing aliases of multiple arguments. [#5377](https://github.com/jj-vcs/jj/issues/5377) ### Packaging changes * Jujutsu now uses [`zlib-rs`](https://github.com/trifectatechfoundation/zlib-rs), a fast compression library written in Rust. Packagers should remove any dependency on CMake and drop the `packaging` Cargo feature. ### Contributors Thanks to the people who made this release happen! * Aleksey Kuznetsov (@zummenix) * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * Benjamin Tan (@bnjmnt4n) * Caleb White (@calebdw) * Daniel Luz (@mernen) * Emily (@emilazy) * Emily (@neongreen) * Gaëtan Lehmann (@glehmann) * George Christou (@gechr) * Ilya Grigoriev (@ilyagr) * Jacob Hayes (@JacobHayes) * Jonas Greitemann (@jgreitemann) * Josh Steadmon (@steadmon) * Martin von Zweigbergk (@martinvonz) * Mateus Auler (@mateusauler) * Nicole Patricia Mazzuca (@strega-nil) * Nils Koch (@nilskch) * Philip Metzger (@PhilipMetzger) * Remo Senekowitsch (@senekor) * Sam (@Samasaur1) * Steve Fink (@hotsphink) * Théo Daron (@tdaron) * TimerErTim (@TimerErTim) * Vincent Ging Ho Yim (@cenviity) * Winter (@winterqt) * Yuya Nishihara (@yuja) ## [0.28.2] - 2025-04-07 ### Fixed bugs * Fixed problem that old commits could be re-imported from Git. https://github.com/GitoxideLabs/gitoxide/issues/1928 ## [0.28.1] - 2025-04-04 ### Security fixes * Fixed SHA-1 collision attacks not being detected. ([GHSA-794x-2rpg-rfgr](https://github.com/jj-vcs/jj/security/advisories/GHSA-794x-2rpg-rfgr)) ### Fixed bugs * Resolved some potential build issues for packagers. [#6232](https://github.com/jj-vcs/jj/pull/6232) * Fix a bug with `:ours` and `:theirs` merge tools involving conflicted trees with more than two sides. [#6227](https://github.com/jj-vcs/jj/pull/6227) ### Contributors Thanks to the people who made this release happen! * Emily (@emilazy) * Ilya Grigoriev (@ilyagr) * Nicole Patricia Mazzuca (@strega-nil) * Scott Taylor (@scott2000) * Yuya Nishihara (@yuja) ## [0.28.0] - 2025-04-02 ### Release highlights * jj's configuration can now be split into multiple files more easily. * `jj resolve` now accepts built-in tools `:ours` and `:theirs`. * In colocated repos, newly-created files will now appear in `git diff`. * A long-standing bug relating to empty files in the built-in diff editor was fixed. [#3702](https://github.com/jj-vcs/jj/issues/3702) ### Breaking changes * The minimum supported Rust version (MSRV) is now 1.84.0. * The `git.push-branch-prefix` config has been removed in favor of `git.push-bookmark-prefix`. * `jj abandon` no longer supports `--summary` to suppress the list of abandoned commits. The list won't show more than 10 commits to not clutter the console. * `jj unsquash` has been removed in favor of `jj squash` and `jj diffedit --restore-descendants`. * The `jj untrack` subcommand has been removed in favor of `jj file untrack`. * The following deprecated revset functions have been removed: - `branches()`, `remote_branches()`, `tracked_remote_branches()`, and `untracked_remote_branches()`, which were renamed to "bookmarks". - `file()` and `conflict()`, which were renamed to plural forms. - `files(x, y, ..)` with multiple patterns. Use `files(x|y|..)` instead. * The following deprecated template functions have been removed: - `branches()`, `local_branches()`, and `remote_branches()`, which were renamed to "bookmarks". * The flags `--all` and `--tracked` on `jj git push` by themself do not cause deleted bookmarks to be pushed anymore, as an additional safety measure. They can now be combined with `--deleted` instead. ### Deprecations * `core.watchman.register_snapshot_trigger` has been renamed to `core.watchman.register-snapshot-trigger` for consistency with other configuration options. * `jj backout` is deprecated in favor of `jj revert`. ### New features * `jj sign` can now sign with PKCS#12 certificates through the `gpgsm` backend. * `jj sign` will automatically use the gpg key associated with the author's email in the absence of a `signing.key` configuration. * 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` * The `JJ_CONFIG` environment variable can now contain multiple paths separated by a colon (or semicolon on Windows). * The command `jj config list` now supports showing the origin of each variable via the `builtin_config_list_detailed` template. * `jj config {edit,set,unset}` now prompt when multiple config files are found. * `jj diff -r` now allows multiple revisions (as long as there are no gaps in the revset), such as `jj diff -r 'mutable()'`. * `jj git push` now accepts a `--named NAME=REVISION` argument to create a named bookmark and immediately push it. * The 'how to resolve conflicts' hint that is shown when conflicts appear can be hidden by setting `hints.resolving-conflicts = false`. * `jj op diff` and `jj op log --op-diff` now show changes to which commits correspond to working copies. * `jj op log -d` is now an alias for `jj op log --op-diff`. * `jj bookmark move --to/--from` can now be abbreviated to `jj bookmark move -t/-f` * `jj bookmark list` now supports `--sort` option. Similar to `git branch --sort`. See `jj bookmark list --help` for more details. * A new command `jj revert` is added, which is similar to `jj backout` but adds the `--destination`, `--insert-after`, and `--insert-before` options to customize the location of reverted commits. * A new command `jj git root` is added, which prints the location of the Git directory of a repository using the Git backend. * In colocated repos, any files that jj considers added in the working copy will now show up in `git diff` (as if you had run `git add --intent-to-add` on them). * Reversing colors is now supported. For example, to highlight words by reversing colors rather than underlining, you can set `colors."diff token"={ underline = false, reverse = true }` in your config. * Added `revsets.log-graph-prioritize`, which can be used to configure which branch in the `jj log` graph is displayed on the left instead of `@` (e.g. `coalesce(description("megamerge\n"), trunk())`) * `jj resolve` now accepts new built-in merge tools `:ours` and `:theirs`. These merge tools accept side #1 and side #2 of the conflict respectively. ### Fixed bugs * `jj log -p --stat` now shows diff stats as well as the default color-words/git diff output. [#5986](https://github.com/jj-vcs/jj/issues/5986) * The built-in diff editor now correctly handles deleted files. [#3702](https://github.com/jj-vcs/jj/issues/3702) * The built-in diff editor now correctly retains the executable bit on newly added files when splitting. [#3846](https://github.com/jj-vcs/jj/issues/3846) * `jj config set`/`--config` value parsing rule is relaxed in a way that unquoted apostrophes are allowed. [#5748](https://github.com/jj-vcs/jj/issues/5748) * `jj fix` could previously create new conflicts when a descendant of a fixed revision was already correctly formatted. ### Contributors Thanks to the people who made this release happen! * Aleksey Kuznetsov (@zummenix) * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Baltasar Dinis (@bsdinis) * Benjamin Tan (@bnjmnt4n) * Brandon Hall (@tenkabuto) * Caleb White (@calebdw) * Daniel Luz (@mernen) * David Rieber (@drieber) * demize (@demize) * Emily (@emilazy) * Evan Mesterhazy (@emesterhazy) * Fedor Sheremetyev (@sheremetyev) * George Christou (@gechr) * Ilya Grigoriev (@ilyagr) * Jakob Hellermann (@jakobhellermann) * Jo Liss (@joliss) * Joachim Desroches (@jedesroches) * Johannes Altmanninger (@krobelus) * Jonathan Gilchrist (@jgilchrist) * Kenyon Ralph (@kenyon) * Lucas Garron (@lgarron) * Martin von Zweigbergk (@martinvonz) * Nick Pupko (@npupko) * Philip Metzger (@PhilipMetzger) * Raphael Borun Das Gupta (@das-g) * Remo Senekowitsch (@senekor) * Robin Stocker (@robinst) * Scott Taylor (@scott2000) * Siva Mahadevan (@svmhdvn) * Vincent Ging Ho Yim (@cenviity) * Yuya Nishihara (@yuja) ## [0.27.0] - 2025-03-05 ### Release highlights * `git.subprocess` is now enabled by default, improving compatibility with Git fetches and pushes by spawning an external `git` process. Users can opt out of this by setting `git.subprocess = false`, but this will likely be removed in a future release. Please report any issues you run into. ### Breaking changes * Bookmark name to be created/updated is now parsed as [a revset symbol](docs/revsets.md#symbols). Quotation may be needed in addition to shell quotes. Example: `jj bookmark create -r@- "'name with space'"` * `jj bookmark create`, `jj bookmark set` and `jj bookmark move` onto a hidden commit make it visible. * `jj bookmark forget` now untracks any corresponding remote bookmarks instead of forgetting them, since forgetting a remote bookmark can be unintuitive. The old behavior is still available with the new `--include-remotes` flag. * `jj fix` now always sets the working directory of invoked tools to be the workspace root, instead of the working directory of the `jj fix`. * The `ui.allow-filesets` configuration option has been removed. [The "fileset" language](docs/filesets.md) has been enabled by default since v0.20. * `templates.annotate_commit_summary` is renamed to `templates.file_annotate`, and now has an implicit `self` parameter of type `AnnotationLine`, instead of `Commit`. All methods on `Commit` can be accessed with `commit.method()`, or `self.commit().method()`. ### Deprecations * This release takes the first steps to make target revision required in `bookmark create`, `bookmark move` and `bookmark set`. Those commands will display a warning if the user does not specify target revision explicitly. In the near future those commands will fail if target revision is not specified. * The `signing.sign-all` config option has been deprecated in favor of `signing.behavior`. The new option accepts `drop` (never sign), `keep` (preserve existing signatures), `own` (sign own commits), or `force` (sign all commits). Existing `signing.sign-all = true` translates to `signing.behavior = "own"`, and `false` translates to `"keep"`. Invalid configuration is now an error. ### New features * The new `jj sign` and `jj unsign` commands allow for signing/unsigning commits. `jj sign` supports configuring the default revset through `revsets.sign` when no `--revisions` arguments are provided. * `jj git fetch` now supports [string pattern syntax](docs/revsets.md#string-patterns) on `--remote` option and `git.fetch` configuration. * Template functions `truncate_start()` and `truncate_end()` gained an optional `ellipsis` parameter; passing this prepends or appends the ellipsis to the content if it is truncated to fit the maximum width. * Templates now support `stringify(x)` function and string method `.escape_json()`. The latter serializes the string in JSON format. It is useful for making machine-readable templates by escaping problematic characters like `\n`. * Templates now support `trim()`, `trim_start()` and `trim_end()` methods which remove whitespace from the start and end of a `String` type. * The description of commits backed out by `jj backout` can now be configured using `templates.backout_description`. * New `AnnotationLine` templater type. Used in `templates.file_annotate`. Provides `self.commit()`, `.content()`, `.line_number()`, and `.first_line_in_hunk()`. * Templates now have `format_short_operation_id(id)` function for users to customize the default operation id representation. * The `jj init`/`jj revert` stubs that print errors can now be overridden with aliases. All of `jj clone/init/revert` add a hint to a generic error. * Help text is now colored (when stdout is a terminal). * Commands that used to suggest `--ignore-immutable` now print the number of immutable commits that would be rewritten if used and a link to the docs. * `jj undo` now shows a hint when undoing an undo operation that the user may be looking for `jj op restore` instead. ### Fixed bugs * `jj status` now shows untracked files under untracked directories. [#5389](https://github.com/jj-vcs/jj/issues/5389) * Added workaround for the bug that untracked files are ignored when watchman is enabled. [#5728](https://github.com/jj-vcs/jj/issues/5728) * The `signing.backends.ssh.allowed-signers` configuration option will now expand `~/` to `$HOME/`. [#5626](https://github.com/jj-vcs/jj/pull/5626) * `config-schema.json` now allows arrays of strings for the settings `ui.editor` and `ui.diff.tool`. * `config-schema.json` now allows an array of strings or nested table for the `ui.pager` setting. ### Contributors Thanks to the people who made this release happen! * Alain Leufroy (@aleufroy) * Aleksey Kuznetsov (@zummenix) * Alexander Mikhailov (@AM5800) * Andrew Gilbert (@andyg0808) * Antoine Martin (@alarsyo) * Anton Bulakh (@necauqua) * Austin Seipp (@thoughtpolice) * Baltasar Dinis (@bsdinis) * Benjamin Tan (@bnjmnt4n) * Bryce Berger (@bryceberger) * Burak Varlı (@unexge) * David Rieber (@drieber) * Emily (@emilazy) * Evan Mesterhazy (@emesterhazy) * George Christou (@gechr) * HKalbasi (@HKalbasi) * Ilya Grigoriev (@ilyagr) * Jacob Hayes (@JacobHayes) * Jonathan Frere (@MrJohz) * Jonathan Tan (@jonathantanmy) * Josh Steadmon (@steadmon) * maan2003 (@maan2003) * Martin von Zweigbergk (@martinvonz) * Matthew Davidson (@KingMob) * Philip Metzger (@PhilipMetzger) * Philipp Albrecht (@pylbrecht) * Roman Timushev (@rtimush) * Samuel Tardieu (@samueltardieu) * Scott Taylor (@scott2000) * Stephan Hügel (@urschrei) * Vincent Ging Ho Yim (@cenviity) * Yuya Nishihara (@yuja) ## [0.26.0] - 2025-02-05 ### Release highlights * Improved Git push/fetch compatibility by spawning an external `git` process. This can be enabled by the `git.subprocess=true` config knob, and will be the default in a future release. * `jj log` can now show cryptographic commit signatures. The output can be controlled by the `ui.show-cryptographic-signatures=true` config knob. ### Breaking changes * `jj abandon` now deletes bookmarks pointing to the revisions to be abandoned. Use `--retain-bookmarks` to move bookmarks backwards. If deleted bookmarks were tracking remote bookmarks, the associated bookmarks (or branches) will be deleted from the remote on `jj git push --all`. [#3505](https://github.com/jj-vcs/jj/issues/3505) * `jj init --git` and `jj init --git-repo` have been removed. They were deprecated in early 2024. Use `jj git init` instead. * The following deprecated commands have been removed: - `jj cat` is replaced by `jj file show`. - `jj chmod` is replaced by `jj file chmod`. - `jj files` is replaced by `jj file list`. * The deprecated `-l` short alias for `--limit` in `jj log`, `jj op log` and `jj obslog` has been removed. The `-n` short alias can be used instead. * The deprecated `--siblings` options for `jj split` has been removed. `jj split --parallel` can be used instead. * The deprecated `fix.tool-command` config option has been removed. * In colocated repos, the Git index now contains the changes from all parents of the working copy instead of just the first parent (`HEAD`). 2-sided conflicts from the merged parents are now added to the Git index as conflicts as well. * The following change introduced in 0.25.0 is reverted: - `jj config list` now prints inline tables `{ key = value, .. }` literally. Inner items of inline tables are no longer merged across configuration files. * `jj resolve` will now attempt to resolve all conflicted files instead of resolving the first conflicted file. To resolve a single file, pass a file path to `jj resolve`. * `jj util mangen` is replaced with `jj util install-man-pages`, which can install man pages for all `jj` subcommands to a given path. * In `jj config list` template, `value` is now typed as `ConfigValue`, not as `String` serialized in TOML syntax. * `jj git remote add`/`set-url` now converts relative Git remote path to absolute path. * `jj log`/`op log` now applies `-n`/`--limit` *before* the items are reversed. Rationale: It's more useful to see the N most recent commits/operations, and is more performant. The old behavior can be achieved by `jj log .. | head`. [#5403](https://github.com/jj-vcs/jj/issues/5403) * Upgraded `scm-record` from v0.4.0 to v0.5.0. See release notes at . * The builtin pager is switched to [streampager](https://github.com/markbt/streampager/). It can handle large inputs better and can be configured. * Conflicts materialized in the working copy before `jj 0.19.0` may no longer be parsed correctly. If you are using version 0.18.0 or earlier, check out a non-conflicted commit before upgrading to prevent issues. ### Deprecations ### New features * `jj git {push,clone,fetch}` can now spawn an external `git` subprocess, via the `git.subprocess = true` config knob. This provides an alternative that, when turned on, fixes SSH bugs when interacting with Git remotes due to `libgit2`s limitations [#4979](https://github.com/jj-vcs/jj/issues/4979). * `jj describe` now accepts `--edit`. * `jj evolog` and `jj op log` now accept `--reversed`. * `jj restore` now supports `-i`/`--interactive` selection. * `jj file list` now supports templating. * There is a new `builtin_op_log_oneline` template alias you can pass to `jj op log -T` for a more compact output. You can use `format_operation_oneline` and `format_snapshot_operation_oneline` to customize parts of it. * New template function `config(name)` to access to configuration variable from template. * New template function `pad_centered()` to center content within a minimum width. * Templater now supports `list.filter(|x| ..)` method. * The `diff` commit template keyword now supports custom formatting via `diff.files()`. For example, `diff.files().map(|e| e.path().display())` prints changed file paths. * The `diff.stat()` template method now provides methods to get summary values. * `jj log` can now show cryptographic commit signatures. The output can be controlled by the `ui.show-cryptographic-signatures=true` config knob. The signature template can be customized using `format_detailed_cryptographic_signature(signature)` and `format_short_cryptographic_signature(signature)`. * New `git.sign-on-push` config option to automatically sign commits which are being pushed to a Git remote. * New `git.push-new-bookmarks` config option to push new bookmarks without `--allow-new`. * `jj status` now shows untracked files when they reside directly under a tracked directory. There's still an issue that files under untracked directories aren't listed. [#5389](https://github.com/jj-vcs/jj/issues/5389) * New `merge-tools..diff-expected-exit-codes` config option to suppress warnings from tools exiting with non-zero exit codes. * New `fix.tools.TOOL.enabled` config option to enable/disable tools. This is useful for defining disabled tools in user configuration that can be enabled in individual repositories with one config setting. * Added `--into` flag to `jj restore`, similarly to `jj squash` and `jj absorb`. It is equivalent to `--to`, but `--into` is the recommended name. * Italic text is now supported. You can set e.g. `colors.error = { fg = "red", italic = true }` in your config. * New `author_name`/`author_email`/`committer_name`/`committer_email(pattern)` revset functions to match either name or email field explicitly. * New `subject(pattern)` revset function that matches first line of commit descriptions. * Conditional configuration now supports `--when.commands` to change configuration based on subcommand. * The Jujutsu documentation site now publishes a schema for the official configuration file, which can be integrated into your editor for autocomplete, inline errors, and more. Please [see the documentation](docs/config.md#json-schema-support) for more on this. ### Fixed bugs * `jj git fetch` with multiple remotes will now fetch from all remotes before importing refs into the jj repo. This fixes a race condition where the treatment of a commit that is found in multiple fetch remotes depended on the order the remotes were specified. * Fixed diff selection by external tools with `jj split`/`commit -i FILESETS`. [#5252](https://github.com/jj-vcs/jj/issues/5252) * Conditional configuration now applies when initializing new repository. [#5144](https://github.com/jj-vcs/jj/issues/5144) * `[diff.]` configuration now applies to `.diff().()` commit template methods. * Conflicts at the end of files which don't end with a newline character are now materialized in a way that can be parsed correctly. [#3968](https://github.com/jj-vcs/jj/issues/3968) * Bookmark and remote names written by `jj git clone` to `revset-aliases.'trunk()'` are now escaped if necessary. [#5359](https://github.com/jj-vcs/jj/issues/5359) ### Contributors Thanks to the people who made this release happen! * Angel Ezquerra (@AngelEzquerra) * Antoine Martin (@alarsyo) * Anton Bulakh (@necauqua) * Austin Seipp (@thoughtpolice) * Baltasar Dinis (@bsdinis) * Benjamin Tan (@bnjmnt4n) * blinry (@blinry) * Bryce Berger (@bryceberger) * Charlie-83 (@Charlie-83) * Christian Stoitner (@cstoitner) * Evan Martin (@evmar) * George Christou (@gechr) * Ilya Grigoriev (@ilyagr) * Jakob Hellermann (@jakobhellermann) * JDSeiler (@JDSeiler) * Jonathan Frere (@MrJohz) * Jonathan Gilchrist (@jgilchrist) * Josh Steadmon (@steadmon) * Martin von Zweigbergk (@martinvonz) * Matt Kulukundis (@fowles) * Ollivier Robert (@keltia) * Philip Metzger (@PhilipMetzger) * Philipp Albrecht (@pylbrecht) * Robert Jackson (@rwjblue) * Samuel Tardieu (@samueltardieu) * Scott Taylor (@scott2000) * Stephen Jennings (@jennings) * Valentin Gatien-Baron (@v-gb) * Vincent Ging Ho Yim (@cenviity) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.25.0] - 2025-01-01 ### Release highlights It's the holidays, and this release was overall pretty quiet, without many major changes. Two select improvements: * Improvements to configuration management, including support for [conditional variables](docs/config.md#conditional-variables) in config files. * Large files in the working copy will no longer cause commands to fail; instead the large files will remain intact but untracked in the working copy. ### Breaking changes * Configuration variables are no longer "stringly" typed. For example, `true` is not converted to a string `"true"`, and vice versa. * The following configuration variables are now parsed strictly: `colors.`, `git.abandon-unreachable-commits`, `git.auto-local-bookmark`, `git.push-bookmark-prefix`, `revsets.log`, `revsets.short-prefixes`, `signing.backend`, `operation.hostname`, `operation.username`, `ui.allow-init-native`, `ui.color`, `ui.default-description`, `ui.progress-indicator`, `ui.quiet`, `user.email`, `user.name` * `jj config list` now prints inline tables `{ key = value, .. }` literally. Inner items of inline tables are no longer merged across configuration files. See [the table syntax documentation](docs/config.md#dotted-style-and-headings) for details. * `jj config edit --user` now opens a file even if `$JJ_CONFIG` points to a directory. If there are multiple config files, the command will fail. * `jj config set` no longer accepts a bare string value that looks like a TOML expression. For example, `jj config set NAME '[foo]'` must be quoted as `jj config set NAME '"[foo]"'`. * The deprecated `[alias]` config section is no longer respected. Move command aliases to the `[aliases]` section. * `jj absorb` now abandons the source commit if it becomes empty and has no description. ### Deprecations * `--config-toml=TOML` is deprecated in favor of `--config=NAME=VALUE` and `--config-file=PATH`. * The `Signature.username()` template method is deprecated for `Signature.email().local()`. ### New features * `jj` command no longer fails due to new working-copy files larger than the `snapshot.max-new-file-size` config option. It will print a warning and large files will be left untracked. * Configuration files now support [conditional variables](docs/config.md#conditional-variables). * New command options `--config=NAME=VALUE` and `--config-file=PATH` to set string value without quoting and to load additional configuration from files. * Templates now support the `>=`, `>`, `<=`, and `<` relational operators for `Integer` types. * A new Email template type is added. `Signature.email()` now returns an Email template type instead of a String. * Adds a new template alias `commit_timestamp(commit)` which defaults to the committer date. * Conflict markers are now allowed to be longer than 7 characters, allowing conflicts to be materialized and parsed correctly in files which already contain lines that look like conflict markers. * New `$marker_length` variable to allow merge tools to support longer conflict markers (equivalent to "%L" for Git merge drivers). * `jj describe` now accepts a `JJ: ignore-rest` line that ignores everything below it, similar to a "scissor line" in git. When editing multiple commits, only ignore until the next `JJ: describe` line. ### Fixed bugs * The `$NO_COLOR` environment variable must now be non-empty to be respected. * Fixed incompatible rendering of empty hunks in git/unified diffs. [#5049](https://github.com/jj-vcs/jj/issues/5049) * Fixed performance of progress bar rendering when fetching from Git remote. [#5057](https://github.com/jj-vcs/jj/issues/5057) * `jj config path --user` no longer creates new file at the default config path. * On Windows, workspace paths (printed by `jj root`) no longer use UNC-style `\\?\` paths unless necessary. * On Windows, `jj git clone` now converts local Git remote path to slash-separated path. * `jj resolve` no longer removes the executable bit on resolved files when using an external merge tool. ### Contributors Thanks to the people who made this release happen! * Alex Stefanov (@umnikos) * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Bryce Berger (@bryceberger) * Daniel Ploch (@torquestomp) * David Crespo (@david-crespo) * George Tsiamasiotis (@gtsiam) * Jochen Kupperschmidt (@homeworkprod) * Keane Nguyen (@keanemind) * Martin von Zweigbergk (@martinvonz) * Matt Kulukundis (@fowles) * Milo Moisson (@mrnossiom) * petricavalry (@petricavalry) * Philip Metzger (@PhilipMetzger) * Remo Senekowitsch (@senekor) * Scott Taylor (@scott2000) * Shane Sveller (@shanesveller) * Stephen Jennings (@jennings) * Tim Janik (@tim-janik) * Vamsi Avula (@avamsi) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.24.0] - 2024-12-04 ### Release highlights * New [`jj absorb`](https://jj-vcs.github.io/jj/latest/cli-reference/#jj-absorb) command automatically squashes changes from the current commit into relevant ancestor commits. * Experimental dynamic shell completions have been added; see [the docs](https://jj-vcs.github.io/jj/latest/install-and-setup/#command-line-completion) for configuration. * [`jj duplicate`](https://jj-vcs.github.io/jj/latest/cli-reference/#jj-duplicate) now accepts `--destination`/`--insert-before`/`--insert-after`. * Some deprecated commands have been removed (`jj move`, `jj checkout`, `jj merge`). ### Breaking changes * `jj move` has been removed. It was deprecated in 0.16.0. * `jj checkout` and the built-in alias `jj co` have been removed. It was deprecated in 0.14.0. * `jj merge` has been removed. It was deprecated in 0.14.0. * `jj git push` no longer pushes new bookmarks by default. Use `--allow-new` to bypass this restriction. * Lines prefixed with "JJ:" in commit descriptions and in sparse patterns (from `jj sparse edit`) are now stripped even if they are not immediately followed by a space. [#5004](https://github.com/jj-vcs/jj/issues/5004) ### Deprecations ### New features * Templates now support the `==` and `!=` logical operators for `Boolean`, `Integer`, and `String` types. * New command `jj absorb` that moves changes to stack of mutable revisions. * New command `jj util exec` that can be used for arbitrary aliases. * `jj rebase -b` can now be used with the `--insert-after` and `--insert-before` options, like `jj rebase -r` and `jj rebase -s`. * A preview of improved shell completions was added. Please refer to the [documentation](https://jj-vcs.github.io/jj/latest/install-and-setup/#command-line-completion) to activate them. They additionally complete context-dependent, dynamic values like bookmarks, aliases, revisions, operations and files. * Added the config setting `snapshot.auto-update-stale` for automatically running `jj workspace update-stale` when applicable. * `jj duplicate` now accepts `--destination`, `--insert-after` and `--insert-before` options to customize the location of the duplicated revisions. * `jj log` now displays the working-copy branch first. * New `fork_point()` revset function can be used to obtain the fork point of multiple commits. * The `tags()` revset function now takes an optional `pattern` argument, mirroring that of `bookmarks()`. * Several commands now support `-f/-t` shorthands for `--from/--to`: - `diff` - `diffedit` - `interdiff` - `op diff` - `restore` * New `ui.conflict-marker-style` config option to change how conflicts are materialized in the working copy. The default option ("diff") renders conflicts as a snapshot with a list of diffs to apply to the snapshot. The new "snapshot" option renders conflicts as a series of snapshots, showing each side and base of the conflict. The new "git" option replicates Git's "diff3" conflict style, meaning it is more likely to work with external tools, but it doesn't support conflicts with more than 2 sides. * New `merge-tools..conflict-marker-style` config option to override the conflict marker style used for a specific merge tool. * New `merge-tools..merge-conflict-exit-codes` config option to allow a merge tool to exit with a non-zero code to indicate that not all conflicts were resolved. * `jj simplify-parents` now supports configuring the default revset when no `--source` or `--revisions` arguments are provided with the `revsets.simplify-parents` config. ### Fixed bugs * `jj config unset ` no longer removes a table (such as `[ui]`.) ### Contributors Thanks to the people who made this release happen! * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Daniel Ploch (@torquestomp) * Emily (@neongreen) * Essien Ita Essien (@essiene) * Herman J. Radtke III (@hjr3) * Ilya Grigoriev (@ilyagr) * Joaquín Triñanes (@JoaquinTrinanes) * Lars Francke (@lfrancke) * Luke Randall (@lukerandall) * Martin von Zweigbergk (@martinvonz) * Nathanael Huffman (@nathanaelhuffman) * Philip Metzger (@PhilipMetzger) * Remo Senekowitsch (@senekor) * Robin Stocker (@robinst) * Scott Taylor (@scott2000) * Shane Sveller (@shanesveller) * Tim Janik (@tim-janik) * Yuya Nishihara (@yuja) ## [0.23.0] - 2024-11-06 ### Security fixes * Fixed path traversal by cloning/checking out crafted Git repository containing `..`, `.jj`, `.git` paths. ([GHSA-88h5-6w7m-5w56](https://github.com/jj-vcs/jj/security/advisories/GHSA-88h5-6w7m-5w56);CVE-2024-51990) ### Breaking changes * Revset function names can no longer start with a number. * Evaluation error of `revsets.short-prefixes` configuration is now reported. * The `HEAD@git` symbol no longer resolves to the Git HEAD revision. Use `git_head()` or `@-` revset expression instead. The `git_head` template keyword now returns a boolean. * Help command doesn't work recursively anymore, i.e. `jj workspace help root` doesn't work anymore. * The color label `op_log` from the `[colors]` config section now **only** applies to the op log and not to the other places operations are displayed. In almost all cases, if you configured `op_log` before, you should use the new `operation` label instead. * Default operation log template now shows end times of operations instead of start times. ### Deprecations * `git.auto-local-bookmark` replaces `git.auto-local-branch`. The latter remains supported for now (at lower precedence than the former). ### New features * Added diff options to ignore whitespace when comparing lines. Whitespace changes are still highlighted. * New command `jj simplify-parents` will remove redundant parent edges. * `jj squash` now supports `-f/-t` shorthands for `--from/--[in]to`. * Initial support for shallow Git repositories has been implemented. However, deepening the history of a shallow repository is not yet supported. * `jj git clone` now accepts a `--depth ` option, which allows to clone the repository with a given depth. * New command `jj file annotate` that annotates files line by line. This is similar in functionality to `git blame`. Invoke the command with `jj file annotate `. The output can be customized via the `templates.annotate_commit_summary` config variable. * `jj bookmark list` gained a `--remote REMOTE` option to display bookmarks belonging to a remote. This option can be combined with `--tracked` or `--conflicted`. * New command `jj config unset` that unsets config values. For example, `jj config unset --user user.name`. * `jj help` now has the flag `--keyword` (shorthand `-k`), which can give help for some keywords (e.g. `jj help -k revsets`). To see a list of the available keywords you can do `jj help --help`. * New `at_operation(op, expr)` revset can be used in order to query revisions based on historical state. * String literals in filesets, revsets and templates now support hex bytes (with `\e` as escape / shorthand for `\x1b`). * New `coalesce(revsets...)` revset which returns commits in the first revset in the `revsets` list that does not evaluate to `none()`. * New template function `raw_escape_sequence(...)` preserves escape sequences. * Timestamp objects in templates now have `after(date) -> Boolean` and `before(date) -> Boolean` methods for comparing timestamps to other dates. * New template functions `pad_start()`, `pad_end()`, `truncate_start()`, and `truncate_end()` are added. * Add a new template alias `builtin_log_compact_full_description()`. * Added the config settings `diff.color-words.context` and `diff.git.context` to control the default number of lines of context shown. ### Fixed bugs * Error on `trunk()` revset resolution is now handled gracefully. [#4616](https://github.com/jj-vcs/jj/issues/4616) * Updated the built-in diff editor `scm-record` to version [0.4.0](https://github.com/arxanas/scm-record/releases/tag/v0.4.0), which includes multiple fixes. ### Contributors Thanks to the people who made this release happen! * Alec Snyder (@allonsy) * Arthur Grillo (Grillo-0) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Dave Townsend (@Mossop) * Daniel Ploch (@torquestomp) * Emily (@neongreen) * Essien Ita Essien (@essiene) * Fedor Sheremetyev (@sheremetyev) * Ilya Grigoriev (@ilyagr) * Jakub Okoński (@farnoy) * Jcparkyn (@Jcparkyn) * Joaquín Triñanes (@JoaquinTrinanes) * Lukas Wirth (@Veykril) * Marco Neumann (@crepererum) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Philip Metzger (@PhilipMetzger) * Philipp Albrecht (@pylbrecht) * Remo Senekowitsch (@senekor) * Richard Macklin (@rmacklin) * Robin Stocker (@robinst) * Samuel Tardieu (@samueltardieu) * Sora (@SoraTenshi) * Stephen Jennings (@jennings) * Theodore Ehrenborg (@TheodoreEhrenborg) * Vamsi Avula (@avamsi) * Vincent Ging Ho Yim (@cenviity) * Yuya Nishihara (@yuja) ## [0.22.0] - 2024-10-02 ### Breaking changes * Fixing [#4239](https://github.com/jj-vcs/jj/issues/4239) means the ordering of some messages have changed. * Invalid `ui.graph.style` configuration is now an error. * The builtin template `branch_list` has been renamed to `bookmark_list` as part of the `jj branch` deprecation. ### Deprecations * `jj branch` has been deprecated in favor of `jj bookmark`. **Rationale:** Jujutsu's branches don't behave like Git branches, which a confused many newcomers, as they expected a similar behavior given the name. We've renamed them to "bookmarks" to match the actual behavior, as we think that describes them better, and they also behave similar to Mercurial's bookmarks. * `jj obslog` is now called `jj evolution-log`/`jj evolog`. `jj obslog` remains as an alias. * `jj unsquash` has been deprecated in favor of `jj squash` and `jj diffedit --restore-descendants`. **Rationale:** `jj squash` can be used in interactive mode to pull changes from one commit to another, including from a parent commit to a child commit. For fine-grained dependent diffs, such as when the parent and the child commits must successively modify the same location in a file, `jj diffedit --restore-descendants` can be used to set the parent commit to the desired content without altering the content of the child commit. * The `git.push-branch-prefix` config has been deprecated in favor of `git.push-bookmark-prefix`. * `conflict()` and `file()` revsets have been renamed to `conflicts()` and `files()` respectively. The old names are still around and will be removed in a future release. ### New features * The new config option `snapshot.auto-track` lets you automatically track only the specified paths (all paths by default). Use the new `jj file track` command to manually tracks path that were not automatically tracked. There is no way to list untracked files yet. Use `git status` in a colocated workspace as a workaround. [#323](https://github.com/jj-vcs/jj/issues/323) * `jj fix` now allows fixing unchanged files with the `--include-unchanged-files` flag. This can be used to more easily introduce automatic formatting changes in a new commit separate from other changes. * `jj workspace add` now accepts a `--sparse-patterns=` option, which allows control of the sparse patterns for a newly created workspace: `copy` (inherit from parent; default), `full` (full working copy), or `empty` (the empty working copy). * New command `jj workspace rename` that can rename the current workspace. * `jj op log` gained an option to include operation diffs. * `jj git clone` now accepts a `--remote ` option, which allows to set a name for the remote instead of using the default `origin`. * `jj op undo` now reports information on the operation that has been undone. * `jj squash`: the `-k` flag can be used as a shorthand for `--keep-emptied`. * CommitId / ChangeId template types now support `.normal_hex()`. * `jj commit` and `jj describe` now accept `--author` option allowing to quickly change author of given commit. * `jj diffedit`, `jj abandon`, and `jj restore` now accept a `--restore-descendants` flag. When used, descendants of the edited or deleted commits will keep their original content. * `jj git fetch -b ` will now warn if the branch(es) can not be found in any of the specified/configured remotes. * `jj split` now lets the user select all changes in interactive mode. This may be used to keeping all changes into the first commit while keeping the current commit description for the second commit (the newly created empty one). * Author and committer names are now yellow by default. ### Fixed bugs * Update working copy before reporting changes. This prevents errors during reporting from leaving the working copy in a stale state. * Fixed panic when parsing invalid conflict markers of a particular form. ([#2611](https://github.com/jj-vcs/jj/pull/2611)) * Editing a hidden commit now makes it visible. * The `present()` revset now suppresses missing working copy error. For example, `present(@)` evaluates to `none()` if the current workspace has no working-copy commit. ### Contributors Thanks to the people who made this release happen! * Austin Seipp (@thoughtpolice) * Danny Hooper (@hooper) * Emily Shaffer (@nasamuffin) * Essien Ita Essien (@essiene) * Ethan Brierley (@eopb) * Ilya Grigoriev (@ilyagr) * Kevin Liao (@kevincliao) * Lukas Wirth (@Veykril) * Martin von Zweigbergk (@martinvonz) * Mateusz Mikuła (@mati865) * mlcui (@mlcui-corp) * Philip Metzger (@PhilipMetzger) * Samuel Tardieu (@samueltardieu) * Stephen Jennings (@jennings) * Tyler Goffinet (@qubitz) * Vamsi Avula (@avamsi) * Yuya Nishihara (@yuja) ## [0.21.0] - 2024-09-04 ### Breaking changes * `next/prev` will no longer infer when to go into edit mode when moving from commit to commit. It now either follows the flags `--edit|--no-edit` or it gets the mode from `ui.movement.edit`. ### Deprecations * `jj untrack` has been renamed to `jj file untrack`. ### New features * Add new boolean config knob, `ui.movement.edit` for controlling the behavior of `prev/next`. The flag turns `edit` mode `on` and `off` permanently when set respectively to `true` or `false`. * All diff formats except `--name-only` now include information about copies and moves. So do external diff tools in file-by-file mode. `jj status` also includes information about copies and moves. * Color-words diff has gained [an option to display complex changes as separate lines](docs/config.md#color-words-diff-options). It's enabled by default. To restore the old behavior, set `diff.color-words.max-inline-alternation = -1`. * A tilde (`~`) at the start of the path will now be expanded to the user's home directory when configuring a `signing.key` for SSH commit signing. * When reconfiguring the author, warn that the working copy won't be updated * `jj rebase -s` can now be used with the `--insert-after` and `--insert-before` options, like `jj rebase -r`. ### Fixed bugs * Release binaries for Intel Macs have been restored. They were previously broken due to using a sunset version of GitHub's macOS runners (but nobody had previously complained.) ### Contributors Thanks to the people who made this release happen! * Aaron Bull Schaefer (@elasticdog) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Raniz Daniel Raneland (@Raniz85) * Daniel Ploch (@torquestomp) * Essien Ita Essien (@essiene) * Ilya Grigoriev (@ilyagr) * Kaleb Pace (@kalebpace) * Marie (@NyCodeGHG) * Marijan Smetko (@InCogNiTo124) * Martin von Zweigbergk (@martinvonz) * Matt Kulukundis (@fowles) * Scott Taylor (@scott2000) * Stephen Jennings (@jennings) * tingerrr (@tingerrr) * Yuya Nishihara (@yuja) ## [0.20.0] - 2024-08-07 ### Note to packagers * `jj` now links `libgit2` statically by default. To use dynamic linking, you need to set the environment variable `LIBGIT2_NO_VENDOR=1` while compiling. ([#4163](https://github.com/jj-vcs/jj/pull/4163)) ### Breaking changes * `jj rebase --skip-empty` has been renamed to `jj rebase --skip-emptied` * `jj backout --revision` has been renamed to `jj backout --revisions`. The short alias `-r` is still supported. * [The default `immutable_heads()` set](docs/config.md#set-of-immutable-commits) now includes `untracked_remote_branches()` with the assumption that untracked branches aren't managed by you. Therefore, untracked branches are no longer displayed in `jj log` by default. * Updated defaults for graph node symbol templates `templates.log_node` and `templates.op_log_node`. * [The "fileset" language](docs/filesets.md) is now enabled by default. It can still be disabled by setting `ui.allow-filesets=false`. * On `jj git fetch`/`import`, commits referred to by `HEAD@git` are no longer preserved. If a checked-out named branch gets deleted locally or remotely, the corresponding commits will be abandoned. * `jj --at-op=@` no longer merges concurrent operations if explicitly specified. * `jj obslog -p` no longer shows diffs at non-partial squash operations. Previously, it showed the same diffs as the second predecessor. ### Deprecations * The original configuration syntax for `jj fix` is now deprecated in favor of one that allows defining multiple tools that can affect different filesets. These can be used in combination for now. See `jj help fix` for details. ### New features * Define `immutable_heads()` revset alias in terms of a new `builtin_immutable_heads()`. This enables users to redefine `immutable_heads()` as they wish, but still have `builtin_immutable_heads()` which should not be redefined. * External diff tools can now be configured to invoke the tool on each file individually instead of being passed a directory by setting `merge-tools.$TOOL.diff-invocation-mode="file-by-file"` in config.toml. * In git diffs, word-level hunks are now highlighted with underline. See [diff colors and styles](docs/config.md#diff-colors-and-styles) for customization. * New `.diff().()` commit template methods are added. They can be used in order to show diffs conditionally. For example, `if(current_working_copy, diff.summary())`. * `jj git clone` and `jj git init` with an existing git repository adds the default branch of the remote as repository settings for `revset-aliases."trunk()"`.` * `jj workspace forget` now abandons the workspace's working-copy commit if it was empty. * `jj backout` now includes the backed out commit's subject in the new commit message. * `jj backout` can now back out multiple commits at once. * `jj git clone some/nested/path` now creates the full directory tree for nested destination paths if they don't exist. * String patterns now support case‐insensitive matching by suffixing any pattern kind with `-i`. `mine()` uses case‐insensitive matching on your email address unconditionally. Only ASCII case folding is currently implemented, but this will likely change in the future. * String patterns now support `regex:"pattern"`. * New `tracked_remote_branches()` and `untracked_remote_branches()` revset functions can be used to select tracked/untracked remote branches. * The `file()` revset function now accepts fileset as argument. * New `diff_contains()` revset function can be used to search diffs. * New command `jj operation diff` that can compare changes made between two operations. * New command `jj operation show` that can show the changes made in a single operation. * New config setting `git.private-commits` to prevent commits from being pushed. * [The default commit description template](docs/config.md#default-description) can now be configured by `templates.draft_commit_description`. * `jj fix` can now be configured to run different tools on different filesets. This simplifies the use case of configuring code formatters for specific file types. See `jj help fix` for details. * Added revset functions `author_date` and `committer_date`. * `jj describe` can now update the description of multiple commits. ### Fixed bugs * `jj status` will show different messages in a conflicted tree, depending on the state of the working commit. In particular, if a child commit fixes a conflict in the parent, this will be reflected in the hint provided by `jj status` * `jj diff --git` no longer shows the contents of binary files. * Windows binaries no longer require `vcruntime140.dll` to be installed (normally through Visual Studio.) * On quit, the builtin pager no longer waits for all outputs to be discarded. * `jj branch rename` no longer shows a warning in colocated repos. ### Contributors Thanks to the people who made this release happen! * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Daniel Ploch (@torquestomp) * Danny Hooper (@hooper) * Emily (@emilazy) * Essien Ita Essien (@essiene) * Fedor Sheremetyev (@sheremetyev) * Ilya Grigoriev (@ilyagr) * Jonathan Tan (@jonathantanmy) * Julien Vincent (@julienvincent) * Martin von Zweigbergk (@martinvonz) * Matt Kulukundis (@fowles) * Matt Stark (@matts1) * mlcui (@mlcui-corp) * Philip Metzger (@PhilipMetzger) * Scott Taylor (@scott2000) * Skyler Grey (@Minion3665) * Stephen Jennings (@jennings) * Tim Janik (@tim-janik) * Vincent Ging Ho Yim (@cenviity) * Vladimír Čunát (@vcunat) * Vladimir (@0xdeafbeef) * Yuya Nishihara (@yuja) ## [0.19.0] - 2024-07-03 ### Breaking changes * In revset aliases, top-level `kind:pattern` expression is now parsed as modifier. Surround with parentheses if it should be parsed as string/file pattern. * Dropped support for automatic upgrade of repo formats used by versions before 0.12.0. * `jj fix` now defaults to the broader revset `-s reachable(@, mutable())` instead of `-s @`. * Dropped support for deprecated `jj branch delete`/`forget` `--glob` option. * `jj branch set` now creates new branch if it doesn't exist. Use `jj branch move` to ensure that the target branch already exists. [#3584](https://github.com/jj-vcs/jj/issues/3584) ### Deprecations * Replacing `-l` shorthand for `--limit` with `-n` in `jj log`, `jj op log` and `jj obslog`. * `jj split --siblings` is deprecated in favor of `jj split --parallel` (to match `jj parallelize`). * A new `jj file` subcommand now replaces several existing uncategorized commands, which are deprecated. - `jj file show` replaces `jj cat`. - `jj file chmod` replaces `jj chmod`. - `jj file list` replaces `jj files`. ### New features * Support background filesystem monitoring via watchman triggers enabled with the `core.watchman.register_snapshot_trigger = true` config. * Show paths to config files when configuration errors occur. * `jj fix` now supports configuring the default revset for `-s` using the `revsets.fix` config. * The `descendants()` revset function now accepts an optional `depth` argument; like the `ancestors()` depth argument, it limits the depth of the set. * Revset/template aliases now support function overloading. [#2966](https://github.com/jj-vcs/jj/issues/2966) * Conflicted files are individually simplified before being materialized. * The `jj file` subcommand now contains several existing file utilities. - `jj file show`, replacing `jj cat`. - `jj file chmod` replacing `jj chmod`. - `jj file list` replacing `jj files`. * New command `jj branch move` let you update branches by name pattern or source revision. * New diff option `jj diff --name-only` allows for easier shell scripting. * In color-words diffs, hunks are now highlighted with underline. See [diff colors and styles](docs/config.md#diff-colors-and-styles) for customization. * `jj git push -c ` can now accept revsets that resolve to multiple revisions. This means that `jj git push -c xyz -c abc` is now equivalent to `jj git push -c 'all:(xyz | abc)'`. * `jj prev` and `jj next` have gained a `--conflict` flag which moves you to the next conflict in a child commit. * New command `jj git remote set-url` that sets the url of a git remote. * Author timestamp is now reset when rewriting discardable commits (empty commits with no description) if authored by the current user. [#2000](https://github.com/jj-vcs/jj/issues/2000) * `jj commit` now accepts `--reset-author` option to match `jj describe`. * `jj squash` now accepts a `--keep-emptied` option to keep the source commit. ### Fixed bugs * `jj git push` now ignores immutable commits when checking whether a to-be-pushed commit has conflicts, or has no description / committer / author set. [#3029](https://github.com/jj-vcs/jj/issues/3029) * `jj` will look for divergent changes outside the short prefix set even if it finds the change id inside the short prefix set. [#2476](https://github.com/jj-vcs/jj/issues/2476) ### Contributors Thanks to the people who made this release happen! * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Daniel Ploch (@torquestomp) * Danny Hooper (@hooper) * Ilya Grigoriev (@ilyagr) * James Sully (@sullyj3) * Jonathan Tan (@jonathantanmy) * Kyle J Strand (@BatmanAoD) * Manuel Caldeira (@KiitoX) * Martin von Zweigbergk (@martinvonz) * Matt Kulukundis (@fowles) * Matt Stark (@matts1) * mlcui (@mlcui-corp) * Philip Metzger (@PhilipMetzger) * Scott Taylor (@scott2000) * Simon Wollwage (@Kintaro) * Tal Pressman (@tp-woven) * Yuya Nishihara (@yuja) ## [0.18.0] - 2024-06-05 ### Breaking changes * Dropped support for `ui.default-revset` config (replaced by `revsets.log` in 0.8.0). * The `commit_summary_no_branches` template is superseded by `templates.branch_list`. * `jj split` will now refuse to split an empty commit. * `jj config list` now uses multi-line strings and single-quoted strings in the output when appropriate. * `jj config get`/`list`/`set` now parse `name` argument as [TOML key](https://toml.io/en/v1.0.0#keys). Quote meta characters as needed. Example: `jj config get "revset-aliases.'trunk()'"` * When updating the working copy away from an empty and undescribed commit, it is now abandoned even if it is a merge commit. * If a new working-copy commit is created because the old one was abandoned, and the old commit was merge, then the new commit will now also be. [#2859](https://github.com/jj-vcs/jj/issues/2859) * `jj new`'s `--insert-before`/`--insert-after` options must now be set for each commit the new commit will be inserted before/after. Previously, those options were global flags and specifying them once would insert the new commit before/ after all the specified commits. ### Deprecations * Attempting to alias a built-in command now gives a warning, rather than being silently ignored. ### New features * `jj branch list`/`tag list` now accept `-T`/`--template` option. The tag list prints commit summary along with the tag name by default. * Conflict markers now include an explanation of what each part of the conflict represents. * `ui.color = "debug"` prints active labels alongside the regular colored output. * `jj branch track` now show conflicts if there are some. * A new revset `reachable(srcs, domain)` will return all commits that are reachable from `srcs` within `domain`. * There are now prebuilt binaries for `aarch64-linux-unknown-musl`. Note, these are cross compiled and currently untested. We plan on providing fully tested builds later once our CI system allows it. * Added new revsets `mutable()` and `immutable()`. * Upgraded `scm-record` from v0.2.0 to v0.3.0. See release notes at * New command `jj fix` that can be configured to update commits by running code formatters (or similar tools) on changed files. The configuration schema and flags are minimal for now, with a number of improvements planned (for example, [#3800](https://github.com/jj-vcs/jj/issues/3800) and [#3801](https://github.com/jj-vcs/jj/issues/3801)). * `jj new`'s `--insert-before` and `--insert-after` options can now be used simultaneously. * `jj git push` now can push commits with empty descriptions with the `--allow-empty-description` flag ### Fixed bugs * Previously, `jj git push` only made sure that the branch is in the expected location on the remote server when pushing a branch forward (as opposed to sideways or backwards). Now, `jj git push` makes a safety check in all cases and fails whenever `jj git fetch` would have introduced a conflict. In other words, previously branches that moved sideways or backward were pushed similarly to Git's `git push --force`; now they have protections similar to `git push --force-with-lease` (though not identical to it, to match the behavior of `jj git fetch`). Note also that because of the way `jj git fetch` works, `jj` does not suffer from the same problems as Git's `git push --force-with-lease` in situations when `git fetch` is run in the background. * When the working copy commit becomes immutable, a new one is automatically created on top of it to avoid letting the user edit the immutable one. * `jj config list` now properly escapes TOML keys (#1322). * Files with conflicts are now checked out as executable if all sides of the conflict are executable. * The progress bar (visible when using e.g. `jj git clone`) clears the remainder of the cursor row after drawing rather than clearing the entire row before drawing, eliminating the "flicker" effect seen on some terminals. ### Contributors Thanks to the people who made this release happen! * Alexander Potashev (@aspotashev) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Charles Crete (@Cretezy) * Daniel Ploch (@torquestomp) * Danny Hooper (@hooper) * Eidolon (@HybridEidolon) * Glen Choo (@chooglen) * Gregory Anders (@gpanders) * Ilya Grigoriev (@ilyagr) * jyn (@jyn514) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Matthew Davidson (@KingMob) * Michael Gattozzi (@mgattozzi) * mlcui (@mlcui-corp) * Philip Metzger (@PhilipMetzger) * Remo Senekowitsch (@senekor) * Thomas Castiglione (@gulbanana) * Théo Daron (@tdaron) * tinger (@tingerrr) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.17.1] - 2024-05-07 ### Fixed bugs * `jj status` no longer scans through the entire history to look for ancestors with conflicts. ## [0.17.0] - 2024-05-01 ### Breaking changes * The default template aliases were replaced as follows: * `builtin_op_log_root(op_id: OperationId)` -> `format_root_operation(root: Operation)` * `builtin_log_root(change_id: ChangeId, commit_id: CommitId)` -> `format_root_commit(root: Commit)` * `builtin_change_id_with_hidden_and_divergent_info` -> `format_short_change_id_with_hidden_and_divergent_info(commit: Commit)` * The `--revision` option of `jj rebase` is renamed to `--revisions`. The short alias `-r` is still supported. ### New features * The list of conflicted paths is printed whenever the working copy changes. This can be disabled with the `--quiet` option. * Commit objects in templates now have a `mine() -> Boolean` method analog to the same function in revsets. It evaluates to true if the email of the commit author matches the current `user.email`. * Commit objects in templates now have a `contained_in(revset: String) -> Boolean` method. * Operation objects in templates now have a `snapshot() -> Boolean` method that evaluates to true if the operation was a snapshot created by a non-mutating command (e.g. `jj log`). * Revsets and templates now support single-quoted raw string literals. * A new config option `ui.always-allow-large-revsets` has been added to allow large revsets expressions in some commands, without the `all:` prefix. * A new config option `ui.allow-filesets` has been added to enable ["fileset" expressions](docs/filesets.md). Note that filesets are currently experimental, but will be enabled by default in a future release. * A new global flag `--ignore-immutable` lets you rewrite immutable commits. * New command `jj parallelize` that rebases a set of revisions into siblings. * `jj status` now supports filtering by paths. For example, `jj status .` will only list changed files that are descendants of the current directory. * `jj prev` and `jj next` now work when the working copy revision is a merge. * `jj squash` now accepts a `--use-destination-message/-u` option that uses the description of the destination for the new squashed revision and discards the descriptions of the source revisions. * You can check whether Watchman fsmonitor is enabled or installed with the new `jj debug watchman status` command. * `jj rebase` now accepts revsets resolving to multiple revisions with the `--revisions`/`-r` option. * `jj rebase -r` now accepts `--insert-after` and `--insert-before` options to customize the location of the rebased revisions. ### Fixed bugs * Revsets now support `\`-escapes in string literal. * The builtin diff editor now allows empty files to be selected during `jj split`. * Fixed a bug with `jj split` introduced in 0.16.0 that caused it to incorrectly rebase the children of the revision being split if they had other parents (i.e. if the child was a merge). * The `snapshot.max-new-file-size` option can now handle raw integer literals, interpreted as a number of bytes, where previously it could only handle string literals. This means that `snapshot.max-new-file-size="1"` and `snapshot.max-new-file-size=1` are now equivalent. * `jj squash ` is now a no-op if the path argument didn't match any paths (it used to create new commits with bumped timestamp). [#3334](https://github.com/jj-vcs/jj/issues/3334) ### Contributors Thanks to the people who made this release happen! * Anton Älgmyr (@algmyr) * Anton Bulakh (@necauqua) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Cretezy (@Cretezy) * Daniel Ploch (@torquestomp) * Evan Mesterhazy (@emesterhazy) * Ilya Grigoriev (@ilyagr) * Martin von Zweigbergk (@martinvonz) * Noah Mayr (@noahmayr) * Jeremy O'Brien (@neutralinsomniac) * Jonathan Lorimer (@JonathanLorimer) * Philip Metzger (@PhilipMetzger) * Poliorcetics (@poliorcetics) * Rowan Walsh (@rowan-walsh) * Scott Olson (@solson) * Théo Daron (@tdaron) * Yuya Nishihara (@yuja) ## [0.16.0] - 2024-04-03 ### Deprecations * `jj move` was deprecated in favor of `jj squash`. ### Breaking changes * The `git_head` template keyword now returns an optional value instead of a list of 0 or 1 element. * The `jj sparse set --edit`/`--reset` flags were split up into `jj sparse edit`/`reset` subcommands respectively. * The `jj sparse` subcommands now parse and print patterns as workspace-relative paths. * The `jj log` command no longer uses the default revset when a path is specified. ### New features * Config now supports rgb hex colors (in the form `#rrggbb`) wherever existing color names are supported. * `ui.default-command` now accepts multiple string arguments, for more complex default `jj` commands. * Graph node symbols are now configurable via templates * `templates.log_node` * `templates.op_log_node` * `jj log` now includes synthetic nodes in the graph where some revisions were elided. * `jj squash` now accepts `--from` and `--into` (also aliased as `--to`) if `-r` is not specified. It can now be used for all use cases where `jj move` could previously be used. The `--from` argument accepts a revset that resolves to more than one revision. * Commit templates now support `immutable` keyword. * New template function `coalesce(content, ..)` is added. * Timestamps are now shown in local timezone and without milliseconds and timezone offset by default. * `jj git push` now prints messages from the remote. * `jj branch list` now supports a `--conflicted/-c` option to show only conflicted branches. * `jj duplicate` and `jj abandon` can now take more than a single `-r` argument, for consistency with other commands. * `jj branch list` now allows combining `-r REVISIONS`/`NAMES` and `-a` options. * `--all` is now named `--all-remotes` for `jj branch list` * There is a new global `--quiet` flag to silence commands' non-primary output. * `jj split` now supports a `--siblings/-s` option that splits the target revision into siblings with the same parents and children. * New function `working_copies()` for revsets to show the working copy commits of all workspaces. ### Fixed bugs None. ### Contributors Thanks to the people who made this release happen! * Aleksey Kuznetsov (@zummenix) * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Tan (@bnjmnt4n) * Chris Krycho (@chriskrycho) * Christoph Koehler (@ckoehler) * Daniel Ploch (@torquestomp) * Evan Mesterhazy (@emesterhazy) * Ilya Grigoriev (@ilyagr) * Khionu Sybiern (@khionu) * Martin von Zweigbergk (@martinvonz) * Matthew Davidson (@KingMob) * mrstanwell (@mrstanwell) * Noah Mayr (@noahmayr) * Patric Stout (@TrueBrain) * Poliorcetics (@poliorcetics) * Simon Wollwage (@Kintaro) * Steve Klabnik (@steveklabnik) * Tom Ward (@tomafro) * TrashCan (@TrashCan69420) * Yuya Nishihara (@yuja) ## [0.15.1] - 2024-03-06 No code changes (fixing Rust `Cargo.toml` stuff). ## [0.15.0] - 2024-03-06 ### Breaking changes * The minimum supported Rust version (MSRV) is now 1.76.0. * The on-disk index format changed. New index files will be created automatically, but it can fail if the repository is colocated and predates Git GC issues [#815](https://github.com/jj-vcs/jj/issues/815). If reindexing failed, you'll need to clean up corrupted operation history by `jj op abandon ..`. * Dropped support for the "legacy" graph-drawing style. Use "ascii" for a very similar result. * The default log output no longer lists all tagged heads. Set `revsets.log = "@ | ancestors(immutable_heads().., 2) | heads(immutable_heads())"` to restore the old behavior. * Dropped support for the deprecated `:` revset operator. Use `::` instead. * `jj rebase --skip-empty` no longer abandons commits that were already empty before the rebase. ### New features * Partial support for commit signing. Currently you can configure jj to "keep" commit signatures by making new ones for rewritten commits, and to sign new commits when they are created. This comes with out-of-the-box support for the following backends: * GnuPG * SSH Signature verification and an explicit sign command will hopefully come soon. * Templates now support logical operators: `||`, `&&`, `!` * Templates now support the `self` keyword, which is the current commit in `jj log`/`obslog` templates. * `jj show` now accepts `-T`/`--template` option to render its output using template * `jj config list` now accepts `-T`/`--template` option. * `jj git fetch` now accepts `-b` as a shorthand for `--branch`, making it more consistent with other commands that accept a branch * In the templating language, Timestamps now have a `.local()` method for converting to the local timezone. * `jj next/prev` now infer `--edit` when you're already editing a non-head commit (a commit with children). * A new built-in pager named `:builtin` is available on all platforms, implemented with [minus](https://github.com/arijit79/minus/) * Set config `ui.log-synthetic-elided-nodes = true` to make `jj log` include synthetic nodes in the graph where some revisions were elided ([#1252](https://github.com/jj-vcs/jj/issues/1252), [#2971](https://github.com/jj-vcs/jj/issues/2971)). This may become the default depending on feedback. * When creating a new workspace, the sparse patterns are now copied over from the current workspace. * `jj git init --colocate` can now import an existing Git repository. This is equivalent to `jj git init --git-repo=.`. * `jj git fetch` now automatically prints new remote branches and tags by default. * `--verbose/-v` is now `--debug` (no short option since it's not intended to be used often) * `jj move --from/--to` can now be abbreviated to `jj move -f/-t` * `jj commit`/`diffedit`/`move`/`resolve`/`split`/`squash`/`unsquash` now accept `--tool=` option to override the default. [#2575](https://github.com/jj-vcs/jj/issues/2575) * Added completions for [Nushell](https://nushell.sh) to `jj util completion` * `jj branch list` now supports a `--tracked/-t` option which can be used to show tracked branches only. Omits local Git-tracking branches by default. * Commands producing diffs now accept a `--context` flag for the number of lines of context to show. * `jj` commands with the `-T`/`--template` option now provide a hint containing defined template names when no argument is given, assisting the user in making a selection. ### Fixed bugs * On Windows, symlinks in the repo are now supported when Developer Mode is enabled. When symlink support is unavailable, they will be materialized as regular files in the working copy (instead of resulting in a crash). [#2](https://github.com/jj-vcs/jj/issues/2) * On Windows, the `:builtin` pager is now used by default, rather than being disabled entirely. * Auto-rebase now preserves the shape of history even for merge commits where one parent is an ancestor of another. [#2600](https://github.com/jj-vcs/jj/issues/2600) ### Contributors Thanks to the people who made this release happen! * Aleksey Kuznetsov (@zummenix) * Anton Bulakh (@necauqua) * Anton Älgmyr (@algmyr) * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * Benjamin Tan (@bnjmnt4n) * Daehyeok Mun (@daehyeok) * Daniel Ploch (@torquestomp) * Evan Mesterhazy (@emesterhazy) * gulbanana (@gulbanana) * Ilya Grigoriev (@ilyagr) * Jonathan Tan (@jonathantanmy) * Julien Vincent (@julienvincent) * jyn (@jyn514) * Martin von Zweigbergk (@martinvonz) * Paulo Coelho (@prscoelho) * Philip Metzger (@PhilipMetzger) * Poliorcetics (@poliorcetics) * Stephen Jennings (@jennings) * Vladimir (@0xdeafbeef) * Yuya Nishihara (@yuja) ## [0.14.0] - 2024-02-07 ### Deprecations * `jj checkout` and `jj merge` are both deprecated; use `jj new` instead to replace both of these commands in all instances. **Rationale**: `jj checkout` and `jj merge` both implement identical functionality, which is a subset of `jj new`. `checkout` creates a new working copy commit on top of a single specified revision, i.e. with one parent. `merge` creates a new working copy commit on top of *at least* two specified revisions, i.e. with two or more parents. The only difference between these commands and `jj new`, which *also* creates a new working copy commit, is that `new` can create a working copy commit on top of any arbitrary number of revisions, so it can handle both the previous cases at once. The only actual difference between these three commands is the command syntax and their name. These names were chosen to be familiar to users of other version control systems, but we instead encourage all users to adopt `jj new` instead; it is more general and easier to remember than both of these. `jj checkout` and `jj merge` will no longer be shown as part of `jj help`, but will still function for now, emitting a warning about their deprecation. **Deadline**: `jj checkout` and `jj merge` will be deleted and are expected become a **hard error later in 2024**. * `jj init --git` and `jj init --git-repo` are now deprecated and will be removed in the near future. Use `jj git init` instead. ### Breaking changes * (Minor) Diff summaries (e.g. `jj diff -s`) now use `D` for "Deleted" instead of `R` for "Removed". @joyously pointed out that `R` could also mean "Renamed". * `jj util completion` now takes the shell as a positional argument, not a flag. the previous behavior is deprecated, but supported for now. it will be removed in the future. * `jj rebase` now preserves the shape of history even for merge commits where one parent is an ancestor of another. You can follow the `jj rebase` by `jj rebase -s -d ` if you want to linearize the history. ### New features * `jj util completion` now supports powershell and elvish. * Official binaries for macOS running on Apple Silicon (`aarch64-apple-darwin`) are now available, alongside the existing macOS x86 binaries. * New `jj op abandon` command is added to clean up the operation history. Git refs and commit objects can be further compacted by `jj util gc`. * `jj util gc` now removes unreachable operation, view, and Git objects. * `jj branch rename` will now warn if the renamed branch has a remote branch, since those will have to be manually renamed outside of `jj`. * `jj git push` gained a `--tracked` option, to push all the tracked branches. * There's now a virtual root operation, similar to the [virtual root commit](docs/glossary.md#root-commit). It appears at the end of `jj op log`. * `jj config list` gained a `--include-overridden` option to allow printing overridden config values. * `jj config list` now accepts `--user` or `--repo` option to specify config origin. * New `jj config path` command to print the config file path without launching an editor. * `jj tag list` command prints imported git tags. * `jj next` and `jj prev` now prompt in the event of the next/previous commit being ambiguous, instead of failing outright. * `jj resolve` now displays the file being resolved. * `jj workspace root` was aliased to `jj root`, for ease of discoverability * `jj diff` no longer shows the contents of binary files. * `jj git` now has an `init` command that initializes a git backed repo. * New template function `surround(prefix, suffix, content)` is added. ### Fixed bugs * Fixed snapshots of symlinks in `gitignore`-d directory. [#2878](https://github.com/jj-vcs/jj/issues/2878) * Fixed data loss in dirty working copy when checked-out branch is rebased or abandoned by Git. [#2876](https://github.com/jj-vcs/jj/issues/2876) ### Contributors Thanks to the people who made this release happen! * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * Chris Krycho (@chriskrycho) * Daehyeok Mun (@daehyeok) * Daniel Ploch (@torquestomp) * Essien Ita Essien (@essiene) * Ikko Eltociear Ashimine (@eltociear) * Ilya Grigoriev (@ilyagr) * Jonathan Tan (@jonathantanmy) * jyn (@jyn514) * Martin von Zweigbergk (@martinvonz) * Matt Stark (@matts1) * Michael Pratt (prattmic) * Philip Metzger (@PhilipMetzger) * Stephen Jennings (@jennings) * Valentin Gatien-Baron (@v-gb) * vwkd (@vwkd) * Yuya Nishihara (@yuja) ## [0.13.0] - 2024-01-03 ### Breaking changes * `jj git fetch` no longer imports new remote branches as local branches. Set `git.auto-local-branch = true` to restore the old behavior. ### New features * Information about new and resolved conflicts is now printed by every command. * `jj branch` has gained a new `rename` subcommand that allows changing a branch name atomically. `jj branch help rename` for details. ### Fixed bugs * Command aliases can now be loaded from repository config relative to the current working directory. [#2414](https://github.com/jj-vcs/jj/issues/2414) ### Contributors Thanks to the people who made this release happen! * Austin Seipp (@thoughtpolice) * Essien Ita Essien (@essiene) * Gabriel Scherer (@gasche) * Ilya Grigoriev (@ilyagr) * Martin von Zweigbergk (@martinvonz) * Philip Metzger (@PhilipMetzger) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.12.0] - 2023-12-05 ### Breaking changes * The `remote_branches()` revset no longer includes branches exported to the Git repository (so called Git-tracking branches.) * `jj branch set` no longer creates a new branch. Use `jj branch create` instead. * `jj init --git` in an existing Git repository now errors and exits rather than creating a second Git store. ### New features * `jj workspace add` can now take _multiple_ `--revision` arguments, which will create a new workspace with its working-copy commit on top of all the parents, as if you had run `jj new r1 r2 r3 ...`. * You can now set `git.abandon-unreachable-commits = false` to disable the usual behavior where commits that became unreachable in the Git repo are abandoned ([#2504](https://github.com/jj-vcs/jj/pull/2504)). * `jj new` gained a `--no-edit` option to prevent editing the newly created commit. For example, `jj new a b --no-edit -m Merge` creates a merge commit without affecting the working copy. * `jj rebase` now takes the flag `--skip-empty`, which doesn't copy over commits that would become empty after a rebase. * There is a new `jj util gc` command for cleaning up the repository storage. For now, it simply runs `git gc` on the backing Git repo (when using the Git backend). ### Fixed bugs * Fixed another file conflict resolution issue where `jj status` would disagree with the actual file content. [#2654](https://github.com/jj-vcs/jj/issues/2654) ### Contributors Thanks to the people who made this release happen! * Antoine Cezar (@AntoineCezar) * Anton Bulakh (@necauqua) * Austin Seipp (@thoughtpolice) * Benjamin Saunders (@Ralith) * Carlos Precioso (@cprecioso) * Chris Krycho (@chriskrycho) * Ilya Grigoriev (@ilyagr) * Jason R. Coombs (@jaraco) * Jesse Somerville (@jessesomerville) * Łukasz Kurowski (@crackcomm) * Martin von Zweigbergk (@martinvonz) * mlcui (@mlcui-corp) * Philip Metzger (@PhilipMetzger) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.11.0] - 2023-11-01 ### Breaking changes * Conflicts are now stored in a different way. Commits written by a new `jj` binary will not be read correctly by older `jj` binaries. The new model solves some performance problems with the old model. For example, `jj log` should be noticeably faster on large repos. You may need to create a new clone to see the full speedup. * The `remote_branches()` revset now includes branches exported to the Git repository (so called Git-tracking branches.) *This change will be reverted in 0.12.0.* * Status messages are now printed to stderr. * `jj config set` now interprets the value as TOML also if it's a valid TOML array or table. For example, `jj config set --user 'aliases.n' '["new"]'` * Remote branches now have tracking or non-tracking flags. The `git.auto-local-branch` setting is applied only to newly fetched remote branches. Existing remote branches are migrated as follows: * If local branch exists, the corresponding remote branches are considered tracking branches. * Otherwise, the remote branches are non-tracking branches. If the deduced tracking flags are wrong, use `jj branch track`/`untrack` commands to fix them up. * Non-tracking remote branches aren't listed by default. Use `jj branch list --all` to show all local and remote branches. * It's not allowed to push branches if non-tracking remote branches of the same name exist. * Pushing deleted/moved branches no longer abandons the local commits referenced by the remote branches. * `jj git fetch --branch` now requires `glob:` prefix to expand `*` in branch name. ### New features * `jj`'s stable release can now be installed with [`cargo binstall jj-cli`](https://github.com/cargo-bins/cargo-binstall). * `jj workspace add` now takes a `--revision` argument. * `jj workspace forget` can now forget multiple workspaces at once. * `branches()`/`remote_branches()`/`author()`/`committer()`/`description()` revsets now support glob matching. * `jj branch delete`/`forget`/`list`, and `jj git push --branch` now support [string pattern syntax](docs/revsets.md#string-patterns). The `--glob` option is deprecated in favor of `glob:` pattern. * The `branches`/`tags`/`git_refs`/`git_head` template keywords now return a list of `RefName`s. They were previously pre-formatted strings. * The new template keywords `local_branches`/`remote_branches` are added to show only local/remote branches. * `jj workspace add` now preserves all parents of the old working-copy commit instead of just the first one. * `jj rebase -r` gained the ability to rebase a revision `A` onto a descendant of `A`. ### Fixed bugs * Updating the working copy to a commit where a file that's currently ignored in the working copy no longer leads to a crash ([#976](https://github.com/jj-vcs/jj/issues/976)). * Conflicts in executable files can now be resolved just like conflicts in non-executable files ([#1279](https://github.com/jj-vcs/jj/issues/1279)). * `jj new --insert-before` and `--insert-after` now respect immutable revisions ([#2468](https://github.com/jj-vcs/jj/pull/2468)). ### Contributors Thanks to the people who made this release happen! * Antoine Cezar (@AntoineCezar) * Austin Seipp (@thoughtpolice) * Benjamin Saunders (@Ralith) * Gabriel Scherer (@gasche) * Ilya Grigoriev (@ilyagr) * Infra (@1011X) * Isabella Basso (@isinyaaa) * Martin von Zweigbergk (@martinvonz) * Tal Pressman (@talpr) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.10.0] - 2023-10-04 ### Breaking changes * A default revset-alias function `trunk()` now exists. If you previously defined your own `trunk()` alias it will continue to overwrite the built-in one. Check [revsets.toml](docs/revsets.toml) and [revsets.md](docs/revsets.md) to understand how the function can be adapted. ### New features * The `ancestors()` revset function now takes an optional `depth` argument to limit the depth of the ancestor set. For example, use `jj log -r 'ancestors(@, 5)` to view the last 5 commits. * Support for the Watchman filesystem monitor is now bundled by default. Set `core.fsmonitor = "watchman"` in your repo to enable. * You can now configure the set of immutable commits via `revset-aliases.immutable_heads()`. For example, set it to `"remote_branches() | tags()"` to prevent rewriting those those. Their ancestors are implicitly also immutable. * `jj op log` now supports `--no-graph`. * Templates now support an additional escape: `\0`. This will output a literal null byte. This may be useful for e.g. `jj log -T 'description ++ "\0"' --no-graph` to output descriptions only, but be able to tell where the boundaries are * jj now bundles a TUI tool to use as the default diff and merge editors. (The previous default was `meld`.) * `jj split` supports the `--interactive` flag. (This is already the default if no paths are provided.) * `jj commit` accepts an optional list of paths indicating a subset of files to include in the first commit * `jj commit` accepts the `--interactive` flag. ### Fixed bugs ### Contributors Thanks to the people who made this release happen! * Austin Seipp (@thoughtpolice) * Emily Kyle Fox (@emilykfox) * glencbz (@glencbz) * Hong Shin (@honglooker) * Ilya Grigoriev (@ilyagr) * James Sully (@sullyj3) * Martin von Zweigbergk (@martinvonz) * Philip Metzger (@PhilipMetzger) * Ruben Slabbert (@rslabbert) * Vamsi Avula (@avamsi) * Waleed Khan (@arxanas) * Willian Mori (@wmrmrx)) * Yuya Nishihara (@yuja) * Zachary Dremann (@Dr-Emann) ## [0.9.0] - 2023-09-06 ### Breaking changes * The minimum supported Rust version (MSRV) is now 1.71.0. * The storage format of branches, tags, and git refs has changed. Newly-stored repository data will no longer be loadable by older binaries. * The `:` revset operator is deprecated. Use `::` instead. We plan to delete the `:` form in jj 0.15+. * The `--allow-large-revsets` flag for `jj rebase` and `jj new` was replaced by a `all:` before the revset. For example, use `jj rebase -d 'all:foo-'` instead of `jj rebase --allow-large-revsets -d 'foo-'`. * The `--allow-large-revsets` flag for `jj rebase` and `jj new` can no longer be used for allowing duplicate destinations. Include the potential duplicates in a single expression instead (e.g. `jj new 'all:x|y'`). * The `push.branch-prefix` option was renamed to `git.push-branch-prefix`. * The default editor on Windows is now `Notepad` instead of `pico`. * `jj` will fail attempts to snapshot new files larger than 1MiB by default. This behavior can be customized with the `snapshot.max-new-file-size` config option. * Author and committer signatures now use empty strings to represent unset names and email addresses. The `author`/`committer` template keywords and methods also return empty strings. Older binaries may not warn user when attempting to `git push` commits with such signatures. * In revsets, the working-copy or remote symbols (such as `@`, `workspace_id@`, and `branch@remote`) can no longer be quoted as a unit. If a workspace or branch name contains whitespace, quote the name like `"branch name"@remote`. Also, these symbols will not be resolved as revset aliases or function parameters. For example, `author(foo@)` is now an error, and the revset alias `'revset-aliases.foo@' = '@'` will be failed to parse. * The `root` revset symbol has been converted to function `root()`. * The `..x` revset is now evaluated to `root()..x`, which means the root commit is no longer included. * `jj git push` will now push all branches in the range `remote_branches()..@` instead of only branches pointing to `@` or `@-`. * It's no longer allowed to create a Git remote named "git". Use `jj git remote rename` to rename the existing remote. [#1690](https://github.com/jj-vcs/jj/issues/1690) * Revset expression like `origin/main` will no longer resolve to a remote-tracking branch. Use `main@origin` instead. ### New features * Default template for `jj log` now does not show irrelevant information (timestamp, empty, message placeholder etc.) about the root commit. * Commit templates now support the `root` keyword, which is `true` for the root commit and `false` for every other commit. * `jj init --git-repo` now works with bare repositories. * `jj config edit --user` and `jj config set --user` will now pick a default config location if no existing file is found, potentially creating parent directories. * `jj log` output is now topologically grouped. [#242](https://github.com/jj-vcs/jj/issues/242) * `jj git clone` now supports the `--colocate` flag to create the git repo in the same directory as the jj repo. * `jj restore` gained a new option `--changes-in` to restore files from a merge revision's parents. This undoes the changes that `jj diff -r` would show. * `jj diff`/`log` now supports `--tool ` option to generate diffs by external program. For configuration, see [the documentation](docs/config.md). [#1886](https://github.com/jj-vcs/jj/issues/1886) * A new experimental diff editor `meld-3` is introduced that sets up Meld to allow you to see both sides of the original diff while editing. This can be used with `jj split`, `jj move -i`, etc. * `jj log`/`obslog`/`op log` now supports `--limit N` option to show the first `N` entries. * Added the `ui.paginate` option to enable/disable pager usage in commands * `jj checkout`/`jj describe`/`jj commit`/`jj new`/`jj squash` can take repeated `-m/--message` arguments. Each passed message will be combined into paragraphs (separated by a blank line) * It is now possible to set a default description using the new `ui.default-description` option, to use when describing changes with an empty description. * `jj split` will now leave the description empty on the second part if the description was empty on the input commit. * `branches()`/`remote_branches()`/`author()`/`committer()`/`description()` revsets now support exact matching. For example, `branch(exact:main)` selects the branch named "main", but not "maint". `description(exact:"")` selects commits whose description is empty. * Revsets gained a new function `mine()` that aliases `author(exact:"your_email")`. * Added support for `::` and `..` revset operators with both left and right operands omitted. These expressions are equivalent to `all()` and `~root()` respectively. * `jj log` timestamp format now accepts `.utc()` to convert a timestamp to UTC. * templates now support additional string methods `.starts_with(x)`, `.ends_with(x)` `.remove_prefix(x)`, `.remove_suffix(x)`, and `.substr(start, end)`. * `jj next` and `jj prev` are added, these allow you to traverse the history in a linear style. For people coming from Sapling and `git-branchles` see [#2126](https://github.com/jj-vcs/jj/issues/2126) for further pending improvements. * `jj diff --stat` has been implemented. It shows a histogram of the changes, same as `git diff --stat`. Fixes [#2066](https://github.com/jj-vcs/jj/issues/2066) * `jj git fetch --all-remotes` has been implemented. It fetches all remotes instead of just the default remote ### Fixed bugs * Fix issues related to .gitignore handling of untracked directories [#2051](https://github.com/jj-vcs/jj/issues/2051). * `jj config set --user` and `jj config edit --user` can now be used outside of any repository. * SSH authentication could hang when ssh-agent couldn't be reached [#1970](https://github.com/jj-vcs/jj/issues/1970) * SSH authentication can now use ed25519 and ed25519-sk keys. They still need to be password-less. * Git repository managed by the repo tool can now be detected as a "colocated" repository. [#2011](https://github.com/jj-vcs/jj/issues/2011) ### Contributors Thanks to the people who made this release happen! * Alexander Potashev (@aspotashev) * Anton Bulakh (@necauqua) * Austin Seipp (@thoughtpolice) * Benjamin Brittain (@benbrittain) * Benjamin Saunders (@Ralith) * Christophe Poucet (@poucet) * Emily Kyle Fox (@emilykfox) * Glen Choo (@chooglen) * Ilya Grigoriev (@ilyagr) * Kevin Liao (@kevincliao) * Linus Arver (@listx) * Martin Clausen (@maacl) * Martin von Zweigbergk (@martinvonz) * Matt Freitas-Stavola (@mbStavola) * Oscar Bonilla (@ob) * Philip Metzger (@PhilipMetzger) * Piotr Kufel (@qfel) * Preston Van Loon (@prestonvanloon) * Tal Pressman (@talpr) * Vamsi Avula (@avamsi) * Vincent Breitmoser (@Valodim) * Vladimir (@0xdeafbeef) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) * Zachary Dremann (@Dr-Emann) ## [0.8.0] - 2023-07-09 ### Breaking changes * The `jujutsu` and `jujutsu-lib` crates were renamed to `jj-cli` and `jj-lib`, respectively. * The `ui.oplog-relative-timestamps` option has been removed. Use the `format_time_range()` template alias instead. For details, see [the documentation](docs/config.md). * Implicit concatenation of template expressions has been disabled. Use `++` operator, `concat()`, or `separate()` function instead. Example: `description ++ "\n"` * `jj git push` will consider pushing the parent commit only when the current commit has no content and no description, such as right after a `jj squash`. * The minimum supported Rust version (MSRV) is now 1.64.0. * The `heads()` revset function was split up into two functions. `heads()` without arguments is now called `visible_heads()`. `heads()` with one argument is unchanged. * The `ui.default-revset` config was renamed to `revsets.log`. * The `jj sparse` command was split up into `jj sparse list` and `jj sparse set`. * `jj hide` (alias for `jj abandon`) is no longer available. Use `jj abandon` instead. * `jj debug completion`, `jj debug mangen` and `jj debug config-schema` have been moved from `jj debug` to `jj util`. * `jj` will no longer parse `br` as a git_ref `refs/heads/br` when a branch `br` does not exist but the git_ref does (this is rare). Use `br@git` instead. * `jj git fetch` will no longer import unrelated branches from the underlying Git repo. ### New features * `jj git push --deleted` will remove all locally deleted branches from the remote. * `jj restore` without `--from` works correctly even if `@` is a merge commit. * `jj rebase` now accepts multiple `-s` and `-b` arguments. Revsets with multiple commits are allowed with `--allow-large-revsets`. * `jj git fetch` now supports a `--branch` argument to fetch some of the branches only. * `jj config get` command allows retrieving config values for use in scripting. * `jj config set` command allows simple config edits like `jj config set --repo user.email "somebody@example.com"` * Added `ui.log-word-wrap` option to wrap `jj log`/`obslog`/`op log` content based on terminal width. [#1043](https://github.com/jj-vcs/jj/issues/1043) * Nodes in the (text-based) graphical log output now use a `◉` symbol instead of the letter `o`. The ASCII-based graph styles still use `o`. * Commands that accept a diff format (`jj diff`, `jj interdiff`, `jj show`, `jj log`, and `jj obslog`) now accept `--types` to show only the type of file before and after. * `jj describe` now supports `--reset-author` for resetting a commit's author to the configured user. `jj describe` also gained a `--no-edit` option to avoid opening the editor. * Added `latest(x[, n])` revset function to select the latest `n` commits. * Added `conflict()` revset function to select commits with conflicts. * `jj squash` AKA `jj amend` now accepts a `--message` option to set the description of the squashed commit on the command-line. * The progress display on `jj git clone/fetch` now includes the downloaded size. * The formatter now supports a "default" color that can override another color defined by a parent style. * `jj obslog` and `jj log` now show abandoned commits as hidden. * `jj git fetch` and `jj git push` will now use the single defined remote even if it is not named "origin". * `jj git push` now accepts `--branch` and `--change` arguments together. * `jj git push` now accepts a `-r/--revisions` flag to specify revisions to push. All branches pointing to any of the specified revisions will be pushed. The flag can be used together with `--branch` and `--change`. * `jj` with no subcommand now defaults to `jj log` instead of showing help. This command can be overridden by setting `ui.default-command`. * Description tempfiles created via `jj describe` now have the file extension `.jjdescription` to help external tooling detect a unique filetype. * The shortest unique change ID prefixes and commit ID prefixes in `jj log` are now shorter within the default log revset. You can override the default by setting the `revsets.short-prefixes` config to a different revset. * The last seen state of branches in the underlying git repo is now presented by `jj branch list`/`jj log` as a remote called `git` (e.g. `main@git`). They can also be referenced in revsets. Such branches exist in colocated repos or if you use `jj git export`. * The new `jj chmod` command allows setting or removing the executable bit on paths. Unlike the POSIX `chmod`, it works on Windows, on conflicted files, and on arbitrary revisions. Bits other than the executable bit are not planned to be supported. * `jj sparse set` now accepts an `--edit` flag which brings up the `$EDITOR` to edit sparse patterns. * `jj branch list` can now be filtered by revset. * Initial support for the Watchman filesystem monitor. Set `core.fsmonitor = "watchman"` in your repo to enable. ### Fixed bugs * Modify/delete conflicts now include context lines [#1244](https://github.com/jj-vcs/jj/issues/1244). * It is now possible to modify either side of a modify/delete conflict (any change used to be considered a resolution). * Fixed a bug that could get partially resolved conflicts to be interpreted incorrectly. * `jj git fetch`: when re-adding a remote repository that had been previously removed, in some situations the remote branches were not recreated. * `jj git remote rename`: the git remote references were not rewritten with the new name. If a new remote with the old name and containing the same branches was added, the remote branches may not be recreated in some cases. * `jj workspace update-stale` now snapshots the working-copy changes before updating to the new working-copy commit. * It is no longer allowed to create branches at the root commit. * `git checkout` (without using `jj`) in colocated repo no longer abandons the previously checked-out anonymous branch. [#1042](https://github.com/jj-vcs/jj/issues/1042). * `jj git fetch` in a colocated repo now abandons branches deleted on the remote, just like in a non-colocated repo. [#864](https://github.com/jj-vcs/jj/issues/864) * `jj git fetch` can now fetch forgotten branches even if they didn't move on the remote. [#1714](https://github.com/jj-vcs/jj/pull/1714) [#1771](https://github.com/jj-vcs/jj/pull/1771) * It is now possible to `jj branch forget` deleted branches. [#1537](https://github.com/jj-vcs/jj/issues/1537) * Fixed race condition when assigning change id to Git commit. If you've already had unreachable change ids, run `jj debug reindex`. [#924](https://github.com/jj-vcs/jj/issues/924) * Fixed false divergence on racy working-copy snapshots. [#697](https://github.com/jj-vcs/jj/issues/697), [#1608](https://github.com/jj-vcs/jj/issues/1608) * In colocated repos, a bug causing conflicts when undoing branch moves (#922) has been fixed. Some surprising behaviors related to undoing `jj git push` or `jj git fetch` remain. ### Contributors Thanks to the people who made this release happen! * Aaron Bull Schaefer (@elasticdog) * Anton Bulakh (@necauqua) * Austin Seipp (@thoughtpolice) * Benjamin Saunders (@Ralith) * B Wilson (@xelxebar) * Christophe Poucet (@poucet) * David Barnett (@dbarnett) * Glen Choo (@chooglen) * Grégoire Geis (@71) * Ilya Grigoriev (@ilyagr) * Isabella Basso (@isinyaaa) * Kevin Liao (@kevincliao) * Martin von Zweigbergk (@martinvonz) * mlcui (@mlcui-corp) * Samuel Tardieu (@samueltardieu) * Tal Pressman (@talpr) * Vamsi Avula (@avamsi) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.7.0] - 2023-02-16 ### Breaking changes * The minimum supported Rust version (MSRV) is now 1.61.0. * The `jj touchup` command was renamed to `jj diffedit`. * The `-i` option to `jj restore` was removed in favor of new `--from`/`--to` options to `jj diffedit`. * To report the situation when a change id corresponds to multiple visible commits, `jj log` now prints the change id in red and puts `??` after it. Previously, it printed the word "divergent". * `jj log` prefixes commit descriptions with "(empty)" when they contain no change compared to their parents. * The `author`/`committer` templates now display both name and email. Use `author.name()`/`committer.name()` to extract the name. * Storage of the "HEAD@git" reference changed and can now have conflicts. Operations written by a new `jj` binary will have a "HEAD@git" reference that is not visible to older binaries. * The `description` template keyword is now empty if no description set. Use `if(description, description, "(no description set)\n")` to get back the previous behavior. * The `template.log.graph` and `template.commit_summary` config keys were renamed to `templates.log` and `templates.commit_summary` respectively. * If a custom `templates.log` template is set, working-copy commit will no longer be highlighted automatically. Wrap your template with `label(if(current_working_copy, "working_copy"), ...)` to label the working-copy entry. * The `ui.relative-timestamps` option has been removed. Use the `format_timestamp()` template alias instead. For details on showing relative timestamps in `jj log` and `jj show`, see [the documentation](docs/config.md). * `jj op log` now shows relative timestamps by default. To disable, set `ui.oplog-relative-timestamps` to `false`. * The global `--no-commit-working-copy` is now called `--ignore-working-copy`. * The `diff.format` config option is now called `ui.diff.format`. The old name is still supported for now. * `merge-tools..edit-args` now requires `$left`/`$right` parameters. The default is `edit-args = ["$left", "$right"]`. * The builtin `jj update` and `jj up` aliases for `jj checkout` have been deleted. * Change IDs are now rendered using letters from the end of the alphabet (from 'z' through 'k') instead of the usual hex digits ('0' through '9' and 'a' through 'f'). This is to clarify the distinction between change IDs and commit IDs, and to allow more efficient lookup of unique prefixes. This change doesn't affect the storage format; existing repositories will remain usable. ### New features * The default log format now uses the committer timestamp instead of the author timestamp. * `jj log --summary --patch` now shows both summary and diff outputs. * `jj git push` now accepts multiple `--branch`/`--change` arguments * `jj config list` command prints values from config and `config edit` opens the config in an editor. * `jj debug config-schema` command prints out JSON schema for the jj TOML config file format. * `jj resolve --list` can now describe the complexity of conflicts. * `jj resolve` now notifies the user of remaining conflicts, if any, on success. This can be prevented by the new `--quiet` option. * Per-repository configuration is now read from `.jj/repo/config.toml`. * Background colors, bold text, and underlining are now supported. You can set e.g. `colors.error = { bg = "red", bold = true, underline = true }` in your `~/.jjconfig.toml`. * The `empty` condition in templates is true when the commit makes no change to the three compared to its parents. * `branches([needle])` revset function now takes `needle` as an optional argument and matches just the branches whose name contains `needle`. * `remote_branches([branch_needle[, remote_needle]])` now takes `branch_needle` and `remote_needle` as optional arguments and matches just the branches whose name contains `branch_needle` and remote contains `remote_needle`. * `jj git fetch` accepts repeated `--remote` arguments. * Default remotes can be configured for the `jj git fetch` and `jj git push` operations ("origin" by default) using the `git.fetch` and `git.push` configuration entries. `git.fetch` can be a list if multiple remotes must be fetched from. * `jj duplicate` can now duplicate multiple changes in one go. This preserves any parent-child relationships between them. For example, the entire tree of descendants of `abc` can be duplicated with `jj duplicate abc:`. * `jj log` now highlights the shortest unique prefix of every commit and change id and shows the rest in gray. To customize the length and style, use the `format_short_id()` template alias. For details, see [the documentation](docs/config.md). * `jj print` was renamed to `jj cat`. `jj print` remains as an alias. * In content that goes to the terminal, the ANSI escape byte (0x1b) is replaced by a "␛" character. That prevents them from interfering with the ANSI escapes jj itself writes. * `jj workspace root` prints the root path of the current workspace. * The `[alias]` config section was renamed to `[aliases]`. The old name is still accepted for backwards compatibility for some time. * Commands that draw an ASCII graph (`jj log`, `jj op log`, `jj obslog`) now have different styles available by setting e.g. `ui.graph.style = "curved"`. * `jj split` accepts creating empty commits when given a path. `jj split .` inserts an empty commit between the target commit and its children if any, and `jj split any-non-existent-path` inserts an empty commit between the target commit and its parents. * Command arguments to `ui.diff-editor`/`ui.merge-editor` can now be specified inline without referring to `[merge-tools]` table. * `jj rebase` now accepts a new `--allow-large-revsets` argument that allows the revset in the `-d` argument to expand to several revisions. For example, `jj rebase -s B -d B- -d C` now works even if `B` is a merge commit. * `jj new` now also accepts a `--allow-large-revsets` argument that behaves similarly to `jj rebase --allow-large-revsets`. * `jj new --insert-before` inserts the new commit between the target commit and its parents. * `jj new --insert-after` inserts the new commit between the target commit and its children. * `author`/`committer` templates now support `.username()`, which leaves out the domain information of `.email()`. * It is now possible to change the author format of `jj log` with the `format_short_signature()` template alias. For details, see [the documentation](docs/config.md). * Added support for template aliases. New symbols and functions can be configured by `template-aliases. = `. Be aware that the template syntax isn't documented yet and is likely to change. * The `ui.diff-instructions` config setting can be set to `false` to inhibit the creation of the `JJ-INSTRUCTIONS` file as part of diff editing. ### Fixed bugs * When sharing the working copy with a Git repo, we used to forget to export branches to Git when only the working copy had changed. That's now fixed. * Commit description set by `-m`/`--message` is now terminated with a newline character, just like descriptions set by editor are. * The `-R`/`--repository` path must be a valid workspace directory. Its ancestor directories are no longer searched. * Fixed a crash when trying to access a commit that's never been imported into the jj repo from a Git repo. They will now be considered as non-existent if referenced explicitly instead of crashing. * Fixed handling of escaped characters in .gitignore (only keep trailing spaces if escaped properly). * `jj undo` now works after `jj duplicate`. * `jj duplicate` followed by `jj rebase` of a tree containing both the original and duplicate commit no longer crashes. The fix should also resolve any remaining instances of https://github.com/jj-vcs/jj/issues/27. * Fix the output of `jj debug completion --help` by reversing fish and zsh text. * Fixed edge case in `jj git fetch` when a pruned branch is a prefix of another branch. ### Contributors Thanks to the people who made this release happen! * Aleksandr Mikhailov (@AM5800) * Augie Fackler (@durin42) * Benjamin Saunders (@Ralith) * Daniel Ploch (@torquestomp) * Danny Hooper (@hooper) * David Barnett (@dbarnett) * Glen Choo (@chooglen) * Herby Gillot (@herbygillot) * Ilya Grigoriev (@ilyagr) * Luke Granger-Brown (@lukegb) * Martin von Zweigbergk (@martinvonz) * Michael Forster (@MForster) * Philip Metzger (@PhilipMetzger) * Ruben Slabbert (@rslabbert) * Samuel Tardieu (@samueltardieu) * Tal Pressman (@talpr) * Vamsi Avula (@avamsi) * Waleed Khan (@arxanas) * Yuya Nishihara (@yuja) ## [0.6.1] - 2022-12-05 No changes, only changed to a released version of the `thrift` crate dependency. ## [0.6.0] - 2022-12-05 ### Breaking changes * Dropped candidates set argument from `description(needle)`, `author(needle)`, `committer(needle)`, `merges()` revsets. Use `x & description(needle)` instead. * Adjusted precedence of revset union/intersection/difference operators. `x | y & z` is now equivalent to `x | (y & z)`. * Support for open commits has been dropped. The `ui.enable-open-commits` config that was added in 0.5.0 is no longer respected. The `jj open/close` commands have been deleted. * `jj commit` is now a separate command from `jj close` (which no longer exists). The behavior has changed slightly. It now always asks for a description, even if there already was a description set. It now also only works on the working-copy commit (there's no `-r` argument). * If a workspace's working-copy commit has been updated from another workspace, most commands in that workspace will now fail. Use the new `jj workspace update-stale` command to update the workspace to the new working-copy commit. (The old behavior was to automatically update the workspace.) ### New features * Commands with long output are paginated. [#9](https://github.com/jj-vcs/jj/issues/9) * The new `jj git remote rename` command allows git remotes to be renamed in-place. * The new `jj resolve` command allows resolving simple conflicts with an external 3-way-merge tool. * `jj git push` will search `@-` for branches to push if `@` has none. * The new revset function `file(pattern..)` finds commits modifying the paths specified by the `pattern..`. * The new revset function `empty()` finds commits modifying no files. * Added support for revset aliases. New symbols and functions can be configured by `revset-aliases. = `. * It is now possible to specify configuration options on the command line with the new `--config-toml` global option. * `jj git` subcommands will prompt for credentials when required for HTTPS remotes rather than failing. [#469](https://github.com/jj-vcs/jj/issues/469) * Branches that have a different target on some remote than they do locally are now indicated by an asterisk suffix (e.g. `main*`) in `jj log`. [#254](https://github.com/jj-vcs/jj/issues/254) * The commit ID was moved from first on the line in `jj log` output to close to the end. The goal is to encourage users to use the change ID instead, since that is generally more convenient, and it reduces the risk of creating divergent commits. * The username and hostname that appear in the operation log are now configurable via config options `operation.username` and `operation.hostname`. * `jj git` subcommands now support credential helpers. * `jj log` will warn if it appears that the provided path was meant to be a revset. * The new global flag `-v/--verbose` will turn on debug logging to give some additional insight into what is happening behind the scenes. Note: This is not comprehensively supported by all operations yet. * `jj log`, `jj show`, and `jj obslog` now all support showing relative timestamps by setting `ui.relative-timestamps = true` in the config file. ### Fixed bugs * A bug in the export of branches to Git caused spurious conflicted branches. This typically occurred when running in a working copy colocated with Git (created by running `jj init --git-dir=.`). [#463](https://github.com/jj-vcs/jj/issues/463) * When exporting branches to Git, we used to fail if some branches could not be exported (e.g. because Git doesn't allow a branch called `main` and another branch called `main/sub`). We now print a warning about these branches instead. [#493](https://github.com/jj-vcs/jj/issues/493) * If you had modified branches in jj and also modified branches in conflicting ways in Git, `jj git export` used to overwrite the changes you made in Git. We now print a warning about these branches instead. * `jj edit root` now fails gracefully. * `jj git import` used to abandon a commit if Git branches and tags referring to it were removed. We now keep it if a detached HEAD refers to it. * `jj git import` no longer crashes when all Git refs are removed. * Git submodules are now ignored completely. Earlier, files present in the submodule directory in the working copy would become added (tracked), and later removed if you checked out another commit. You can now use `git` to populate the submodule directory and `jj` will leave it alone. * Git's GC could remove commits that were referenced from jj in some cases. We are now better at adding Git refs to prevent that. [#815](https://github.com/jj-vcs/jj/issues/815) * When the working-copy commit was a merge, `jj status` would list only the first parent, and the diff summary would be against that parent. The output now lists all parents and the diff summary is against the auto-merged parents. ### Contributors Thanks to the people who made this release happen! * Martin von Zweigbergk (@martinvonz) * Benjamin Saunders (@Ralith) * Yuya Nishihara (@yuja) * Glen Choo (@chooglen) * Ilya Grigoriev (@ilyagr) * Ruben Slabbert (@rslabbert) * Waleed Khan (@arxanas) * Sean E. Russell (@xxxserxxx) * Pranay Sashank (@pranaysashank) * Luke Granger-Brown (@lukegb) ## [0.5.1] - 2022-10-17 No changes (just trying to get automated GitHub release to work). ## [0.5.0] - 2022-10-17 ### Breaking changes * Open commits are now disabled by default. That means that `jj checkout` will always create a new change on top of the specified commit and will let you edit that in the working copy. Set `ui.enable-open-commits = true` to restore the old behavior and let us know that you did so we know how many people prefer the workflow with open commits. * `jj [op] undo` and `jj op restore` used to take the operation to undo or restore to as an argument to `-o/--operation`. It is now a positional argument instead (i.e. `jj undo -o abc123` is now written `jj undo abc123`). * An alias that is not configured as a string list (e.g. `my-status = "status"` instead of `my-status = ["status"]`) is now an error instead of a warning. * `jj log` now defaults to showing only commits that are not on any remote branches (plus their closest commit on the remote branch for context). This set of commits can be overridden by setting `ui.default-revset`. Use `jj log -r 'all()'` for the old behavior. Read more about revsets [here](https://github.com/jj-vcs/jj/blob/main/docs/revsets.md). [#250](https://github.com/jj-vcs/jj/issues/250) * `jj new` now always checks out the new commit (used to be only if the parent was `@`). * `jj merge` now checks out the new commit. The command now behaves exactly like `jj new`, except that it requires at least two arguments. * When the working-copy commit is abandoned by `jj abandon` and the parent commit is open, a new working-copy commit will be created on top (the open parent commit used to get checked out). * `jj branch` now uses subcommands like `jj branch create` and `jj branch forget` instead of options like `jj branch --forget`. [#330](https://github.com/jj-vcs/jj/issues/330) * The [`$NO_COLOR` environment variable](https://no-color.org/) no longer overrides the `ui.color` configuration if explicitly set. * `jj edit` has been renamed to `jj touchup`, and `jj edit` is now a new command with different behavior. The new `jj edit` lets you edit a commit in the working copy, even if the specified commit is closed. * `jj git push` no longer aborts if you attempt to push an open commit (but it now aborts if a commit does not have a description). * `jj git push` now pushes only branches pointing to the `@` by default. Use `--all` to push all branches. * The `checkouts` template keyword is now called `working_copies`, and `current_checkout` is called `current_working_copy`. ### New features * The new `jj interdiff` command compares the changes in commits, ignoring changes from intervening commits. * `jj rebase` now accepts a `--branch/-b ` argument, which can be used instead of `-r` or `-s` to specify which commits to rebase. It will rebase the whole branch, relative to the destination. The default mode has changed from `-r @` to `-b @`. * The new `jj print` command prints the contents of a file in a revision. * The new `jj git remotes list` command lists the configured remotes and their URLs. [#243](https://github.com/jj-vcs/jj/issues/243) * `jj move` and `jj squash` now lets you limit the set of changes to move by specifying paths on the command line (in addition to the `--interactive` mode). For example, use `jj move --to @-- foo` to move the changes to file (or directory) `foo` in the working copy to the grandparent commit. * When `jj move/squash/unsquash` abandons the source commit because it became empty and both the source and the destination commits have non-empty descriptions, it now asks for a combined description. If either description was empty, it uses the other without asking. * `jj split` now lets you specify on the CLI which paths to include in the first commit. The interactive diff-editing is not started when you do that. * Sparse checkouts are now supported. In fact, all working copies are now "sparse", only to different degrees. Use the `jj sparse` command to manage the paths included in the sparse checkout. * Configuration is now also read from `~/.jjconfig.toml`. * The `$JJ_CONFIG` environment variable can now point to a directory. If it does, all files in the directory will be read, in alphabetical order. * The `$VISUAL` environment is now respected and overrides `$EDITOR`. The new `ui.editor` config has higher priority than both of them. There is also a new `$JJ_EDITOR` environment variable, which has even higher priority than the config. * You can now use `-` and `+` in revset symbols. You used to have to quote branch names like `my-feature` in nested quotes (outer layer for your shell) like `jj co '"my-feature"'`. The quoting is no longer needed. * The new revset function `connected(x)` is the same as `x:x`. * The new revset function `roots(x)` finds commits in the set that are not descendants of other commits in the set. * ssh-agent is now detected even if `$SSH_AGENT_PID` is not set (as long as `$SSH_AUTH_SOCK` is set). This should help at least macOS users where ssh-agent is launched by default and only `$SSH_AUTH_SOCK` is set. * When importing from a git, any commits that are no longer referenced on the git side will now be abandoned on the jj side as well. That means that `jj git fetch` will now abandon unreferenced commits and rebase any local changes you had on top. * `jj git push` gained a `--change ` argument. When that's used, it will create a branch named after the revision's change ID, so you don't have to create a branch yourself. By default, the branch name will start with `push-`, but this can be overridden by the `push.branch-prefix` config setting. * `jj git push` now aborts if you attempt to push a commit without a description or with the placeholder "(no name/email configured)" values for author/committer. * Diff editor command arguments can now be specified by config file. Example: [merge-tools.kdiff3] program = "kdiff3" edit-args = ["--merge", "--cs", "CreateBakFiles=0"] * `jj branch` can accept any number of branches to update, rather than just one. * Aliases can now call other aliases. * `jj log` now accepts a `--reversed` option, which will show older commits first. * `jj log` now accepts file paths. * `jj obslog` now accepts `-p`/`--patch` option, which will show the diff compared to the previous version of the change. * The "(no name/email configured)" placeholder value for name/email will now be replaced if once you modify a commit after having configured your name/email. * Color setting can now be overridden by `--color=always|never|auto` option. * `jj checkout` now lets you specify a description with `--message/-m`. * `jj new` can now be used for creating merge commits. If you pass more than one argument to it, the new commit will have all of them as parents. ### Fixed bugs * When rebasing a conflict where one side modified a file and the other side deleted it, we no longer automatically resolve it in favor of the modified content (this was a regression from commit c0ae4b16e8c4). * Errors are now printed to stderr (they used to be printed to stdout). * Updating the working copy to a commit where a file's executable bit changed but the contents was the same used to lead to a crash. That has now been fixed. * If one side of a merge modified a directory and the other side deleted it, it used to be considered a conflict. The same was true if both sides added a directory with different files in. They are now merged as if the missing directory had been empty. * When using `jj move` to move part of a commit into an ancestor, any branches pointing to the source commit used to be left on a hidden intermediate commit. They are now correctly updated. * `jj untrack` now requires at least one path (allowing no arguments was a UX bug). * `jj rebase` now requires at least one destination (allowing no arguments was a UX bug). * `jj restore --to ` now restores from the working copy (it used to restore from the working copy's parent). * You now get a proper error message instead of a crash when `$EDITOR` doesn't exist or exits with an error. * Global arguments, such as `--at-op=`, can now be passed before an alias. * Fixed relative path to the current directory in output to be `.` instead of empty string. * When adding a new workspace, the parent of the current workspace's current checkout will be checked out. That was always the intent, but the root commit was accidentally checked out instead. * When checking out a commit, the previous commit is no longer abandoned if it has a non-empty description. * All commands now consistently snapshot the working copy (it was missing from e.g. `jj undo` and `jj merge` before). ## [0.4.0] - 2022-04-02 ### Breaking changes * Dropped support for config in `~/.jjconfig`. Your configuration is now read from `/jj/config.toml`, where `` is `${XDG_CONFIG_HOME}` or `~/.config/` on Linux, `~/Library/Application Support/` on macOS, and `~\AppData\Roaming\` on Windows. ### New features * You can now set an environment variable called `$JJ_CONFIG` to a path to a config file. That will then be read instead of your regular config file. This is mostly intended for testing and scripts. * The [standard `$NO_COLOR` environment variable](https://no-color.org/) is now respected. * `jj new` now lets you specify a description with `--message/-m`. * When you check out a commit, the old commit no longer automatically gets abandoned if it's empty and has descendants, it only gets abandoned if it's empty and does not have descendants. * When undoing an earlier operation, any new commits on top of commits from the undone operation will be rebased away. For example, let's say you rebase commit A so it becomes a new commit A', and then you create commit B on top of A'. If you now undo the rebase operation, commit B will be rebased to be on top of A instead. The same logic is used if the repo was modified by concurrent operations (so if one operation added B on top of A, and one operation rebased A as A', then B would be automatically rebased on top of A'). See #111 for more examples. [#111](https://github.com/jj-vcs/jj/issues/111) * `jj log` now accepts `-p`/`--patch` option. ### Fixed bugs * Fixed crash on `jj init --git-repo=.` (it almost always crashed). * When sharing the working copy with a Git repo, the automatic importing and exporting (sometimes?) didn't happen on Windows. ## [0.3.3] - 2022-03-16 No changes, only trying to get the automated build to work. ## [0.3.2] - 2022-03-16 No changes, only trying to get the automated build to work. ## [0.3.1] - 2022-03-13 ### Fixed bugs - Fixed crash when `core.excludesFile` pointed to nonexistent file, and made leading `~/` in that config expand to `$HOME/` [#131](https://github.com/jj-vcs/jj/issues/131) ## [0.3.0] - 2022-03-12 Last release before this changelog started. [unreleased]: https://github.com/jj-vcs/jj/compare/v0.42.0...HEAD [0.42.0]: https://github.com/jj-vcs/jj/compare/v0.41.0...v0.42.0 [0.41.0]: https://github.com/jj-vcs/jj/compare/v0.40.0...v0.41.0 [0.40.0]: https://github.com/jj-vcs/jj/compare/v0.39.0...v0.40.0 [0.39.0]: https://github.com/jj-vcs/jj/compare/v0.38.0...v0.39.0 [0.38.0]: https://github.com/jj-vcs/jj/compare/v0.37.0...v0.38.0 [0.37.0]: https://github.com/jj-vcs/jj/compare/v0.36.0...v0.37.0 [0.36.0]: https://github.com/jj-vcs/jj/compare/v0.35.0...v0.36.0 [0.35.0]: https://github.com/jj-vcs/jj/compare/v0.34.0...v0.35.0 [0.34.0]: https://github.com/jj-vcs/jj/compare/v0.33.0...v0.34.0 [0.33.0]: https://github.com/jj-vcs/jj/compare/v0.32.0...v0.33.0 [0.32.0]: https://github.com/jj-vcs/jj/compare/v0.31.0...v0.32.0 [0.31.0]: https://github.com/jj-vcs/jj/compare/v0.30.0...v0.31.0 [0.30.0]: https://github.com/jj-vcs/jj/compare/v0.29.0...v0.30.0 [0.29.0]: https://github.com/jj-vcs/jj/compare/v0.28.2...v0.29.0 [0.28.2]: https://github.com/jj-vcs/jj/compare/v0.28.1...v0.28.2 [0.28.1]: https://github.com/jj-vcs/jj/compare/v0.28.0...v0.28.1 [0.28.0]: https://github.com/jj-vcs/jj/compare/v0.27.0...v0.28.0 [0.27.0]: https://github.com/jj-vcs/jj/compare/v0.26.0...v0.27.0 [0.26.0]: https://github.com/jj-vcs/jj/compare/v0.25.0...v0.26.0 [0.25.0]: https://github.com/jj-vcs/jj/compare/v0.24.0...v0.25.0 [0.24.0]: https://github.com/jj-vcs/jj/compare/v0.23.0...v0.24.0 [0.23.0]: https://github.com/jj-vcs/jj/compare/v0.22.0...v0.23.0 [0.22.0]: https://github.com/jj-vcs/jj/compare/v0.21.0...v0.22.0 [0.21.0]: https://github.com/jj-vcs/jj/compare/v0.20.0...v0.21.0 [0.20.0]: https://github.com/jj-vcs/jj/compare/v0.19.0...v0.20.0 [0.19.0]: https://github.com/jj-vcs/jj/compare/v0.18.0...v0.19.0 [0.18.0]: https://github.com/jj-vcs/jj/compare/v0.17.1...v0.18.0 [0.17.1]: https://github.com/jj-vcs/jj/compare/v0.17.0...v0.17.1 [0.17.0]: https://github.com/jj-vcs/jj/compare/v0.16.0...v0.17.0 [0.16.0]: https://github.com/jj-vcs/jj/compare/v0.15.1...v0.16.0 [0.15.1]: https://github.com/jj-vcs/jj/compare/v0.15.0...v0.15.1 [0.15.0]: https://github.com/jj-vcs/jj/compare/v0.14.0...v0.15.0 [0.14.0]: https://github.com/jj-vcs/jj/compare/v0.13.0...v0.14.0 [0.13.0]: https://github.com/jj-vcs/jj/compare/v0.12.0...v0.13.0 [0.12.0]: https://github.com/jj-vcs/jj/compare/v0.11.0...v0.12.0 [0.11.0]: https://github.com/jj-vcs/jj/compare/v0.10.0...v0.11.0 [0.10.0]: https://github.com/jj-vcs/jj/compare/v0.9.0...v0.10.0 [0.9.0]: https://github.com/jj-vcs/jj/compare/v0.8.0...v0.9.0 [0.8.0]: https://github.com/jj-vcs/jj/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/jj-vcs/jj/compare/v0.6.1...v0.7.0 [0.6.1]: https://github.com/jj-vcs/jj/compare/v0.6.0...v0.6.1 [0.6.0]: https://github.com/jj-vcs/jj/compare/v0.5.1...v0.6.0 [0.5.1]: https://github.com/jj-vcs/jj/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/jj-vcs/jj/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/jj-vcs/jj/compare/v0.3.3...v0.4.0 [0.3.3]: https://github.com/jj-vcs/jj/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/jj-vcs/jj/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/jj-vcs/jj/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/jj-vcs/jj/releases/tag/v0.3.0 jj-vcs-jj-b8f7c45/Cargo.lock000066400000000000000000004124611521031206200156400ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "getrandom 0.3.4", "once_cell", "serde", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "alloca" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" dependencies = [ "cc", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi-to-tui" version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42366bb9d958f042bf58f0a85e1b2d091997c1257ca49bddd7e4827aadc65fd" dependencies = [ "nom 8.0.0", "ratatui-core", "simdutf8", "smallvec", "thiserror 2.0.18", ] [[package]] name = "anstream" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.61.2", ] [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" dependencies = [ "rustversion", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_cmd" version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" dependencies = [ "anstyle", "bstr", "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-trait" version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "atomic" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" dependencies = [ "bytemuck", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "beef" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec 0.6.3", ] [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec 0.8.0", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borrow-or-share" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "bstr" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecount" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] [[package]] name = "camino" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[package]] name = "cassowary" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "shlex 1.3.0", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", "rand_core 0.10.1", ] [[package]] name = "chrono" version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", "serde", "windows-link", ] [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap-markdown" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2a2617956a06d4885b490697b5307ebb09fec10b088afc18c81762d848c2339" dependencies = [ "clap", ] [[package]] name = "clap_builder" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_complete" version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap", "clap_lex", "is_executable", "shlex 1.3.0", ] [[package]] name = "clap_complete_nushell" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb9e9715d29a754b468591be588f6b926f5b0a1eb6a8b62acabeb66ff84d897" dependencies = [ "clap", "clap_complete", ] [[package]] name = "clap_derive" version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "clap_lex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clap_mangen" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82842b45bf9f6a3be090dd860095ac30728042c08e0d6261ca7259b5d850f07" dependencies = [ "clap", "roff", ] [[package]] name = "clru" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" dependencies = [ "hashbrown 0.16.1", ] [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "static_assertions", ] [[package]] name = "console" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", "windows-sys 0.61.2", ] [[package]] name = "convert_case" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "cpufeatures" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", "itertools 0.13.0", "num-traits", "oorandom", "page_size", "plotters", "rayon", "regex", "serde", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools 0.13.0", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.11.1", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", "rustix", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] [[package]] name = "csscolorparser" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" dependencies = [ "lab", "phf", ] [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.117", ] [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", "syn 2.0.117", ] [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "data-encoding" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "datatest-stable" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82" dependencies = [ "camino", "fancy-regex 0.14.0", "libtest-mimic", "walkdir", ] [[package]] name = "deltae" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] [[package]] name = "derive_more" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", "syn 2.0.117", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", "subtle", ] [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.61.2", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" dependencies = [ "serde", ] [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enum_dispatch" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" dependencies = [ "once_cell", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", "typeid", ] [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "escape8259" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" [[package]] name = "etcetera" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", "windows-sys 0.61.2", ] [[package]] name = "euclid" version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ "num-traits", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fancy-regex" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ "bit-set 0.5.3", "regex", ] [[package]] name = "fancy-regex" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set 0.8.0", "regex-automata", "regex-syntax", ] [[package]] name = "fancy-regex" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" dependencies = [ "bit-set 0.8.0", "regex-automata", "regex-syntax", ] [[package]] name = "faster-hex" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ "heapless", "serde", ] [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "filedescriptor" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" dependencies = [ "libc", "thiserror 1.0.69", "winapi", ] [[package]] name = "filetime" version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", ] [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "finl_unicode" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fixedbitset" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "fluent-uri" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" dependencies = [ "borrow-or-share", "ref-cast", "serde", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "fraction" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" dependencies = [ "lazy_static", "num", ] [[package]] name = "futures" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "futures-sink" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures 0.1.31", "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "libc", "memchr", "pin-project-lite", "slab", ] [[package]] name = "gen-protos" version = "0.42.0" dependencies = [ "prost-build", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", ] [[package]] name = "gix" version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae54ae0ebd1a5a3c3f8d95dd3b5ca6e63f4fed9bfd585e13801a97d7bde8f9ce" dependencies = [ "gix-actor", "gix-attributes", "gix-command", "gix-commitgraph", "gix-config", "gix-date", "gix-diff", "gix-dir", "gix-discover", "gix-error", "gix-features", "gix-filter", "gix-fs", "gix-glob", "gix-hash", "gix-hashtable", "gix-ignore", "gix-index", "gix-lock", "gix-object", "gix-odb", "gix-pack", "gix-path", "gix-pathspec", "gix-protocol", "gix-ref", "gix-refspec", "gix-revision", "gix-revwalk", "gix-sec", "gix-shallow", "gix-status", "gix-submodule", "gix-tempfile", "gix-trace", "gix-traverse", "gix-url", "gix-utils", "gix-validate", "gix-worktree", "gix-worktree-state", "gix-worktree-stream", "nonempty", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-actor" version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bc998b8f746dda8565450d08a63b792ced9165d8c27a1ed3f02799ec6a7820f" dependencies = [ "bstr", "gix-date", "gix-error", ] [[package]] name = "gix-attributes" version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d43f12e246d3bf7ec624c8fc15ac4a4b62b7c4c6f586cb82be6c90bf84c9d02" dependencies = [ "bstr", "gix-glob", "gix-path", "gix-quote", "gix-trace", "kstring", "smallvec", "thiserror 2.0.18", "unicode-bom", ] [[package]] name = "gix-bitmap" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ebef0c26ad305747649e727bbcd56a7b7910754eb7cea88f6dff6f93c51283" dependencies = [ "gix-error", ] [[package]] name = "gix-chunk" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9faee47943b638e58ddd5e275a4906ad3e4b6c8584f1d41bd18ab9032ec52afb" dependencies = [ "gix-error", ] [[package]] name = "gix-command" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6" dependencies = [ "bstr", "gix-path", "gix-quote", "gix-trace", "shell-words", ] [[package]] name = "gix-commitgraph" version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f675d0df484a7f6a47e64bd6f311af489d947c0323b0564f36d14f3d7762abb" dependencies = [ "bstr", "gix-chunk", "gix-error", "gix-hash", "memmap2", "nonempty", ] [[package]] name = "gix-config" version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2372d4b49ca28431e7d150cab9d25edc1890f0184bd57eb0e917c7799e63de" dependencies = [ "bstr", "gix-config-value", "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", "smallvec", "thiserror 2.0.18", "unicode-bom", ] [[package]] name = "gix-config-value" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed42168329552f6c2e5df09665c104199d45d84bedb53683738a49b57fe1baab" dependencies = [ "bitflags 2.11.1", "bstr", "gix-path", "libc", "thiserror 2.0.18", ] [[package]] name = "gix-date" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ecab64a98bbac9f8e02990a9ea5e3c974a7d49b95f2bd70ad94ad22fa6b48c" dependencies = [ "bstr", "gix-error", "itoa", "jiff", ] [[package]] name = "gix-diff" version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b6d9528f32d94cef2edf39a1ac01fe5a0fc44ddbb18d9e44099936047c3302b" dependencies = [ "bstr", "gix-attributes", "gix-command", "gix-filter", "gix-fs", "gix-hash", "gix-imara-diff", "gix-index", "gix-object", "gix-path", "gix-pathspec", "gix-tempfile", "gix-trace", "gix-traverse", "gix-worktree", "thiserror 2.0.18", ] [[package]] name = "gix-dir" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bb2a53a6fd917ec499ed0bfb5b6887de7a15bd79197dcea7c987938749a9f1" dependencies = [ "bstr", "gix-discover", "gix-fs", "gix-ignore", "gix-index", "gix-object", "gix-path", "gix-pathspec", "gix-trace", "gix-utils", "gix-worktree", "thiserror 2.0.18", ] [[package]] name = "gix-discover" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77bacdd12b7879d2178a80c58c2f319995e4654e1a7a23e3181e5c8a12b824f7" dependencies = [ "bstr", "dunce", "gix-fs", "gix-path", "gix-ref", "gix-sec", "thiserror 2.0.18", ] [[package]] name = "gix-error" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57831e199be480af90dcd7e459abed8a174c09ec9a6e2cc8f7ca6c54598b06b" dependencies = [ "bstr", ] [[package]] name = "gix-features" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1849ae154d38bc403185be14fa871e38e3c93ee606875d94e207fdb9fba52dbc" dependencies = [ "bytes", "crc32fast", "crossbeam-channel", "gix-path", "gix-trace", "gix-utils", "libc", "once_cell", "parking_lot", "prodash", "thiserror 2.0.18", "walkdir", "zlib-rs", ] [[package]] name = "gix-filter" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecf74b7d16f6694ce4a3049074c41be0c7987105743674f1671807bd6dce09fa" dependencies = [ "bstr", "encoding_rs", "gix-attributes", "gix-command", "gix-hash", "gix-object", "gix-packetline", "gix-path", "gix-quote", "gix-trace", "gix-utils", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-fs" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cdff46db8798e47e2f727d84b9379aac5add3dd3d9d0b07bb4d7d5d640771fe" dependencies = [ "bstr", "fastrand", "gix-features", "gix-path", "gix-utils", "thiserror 2.0.18", ] [[package]] name = "gix-glob" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1fcb8ef5b16bcf874abe9b68d8abb3c0493c876d367ab824151f30a0f3f3756" dependencies = [ "bitflags 2.11.1", "bstr", "gix-features", "gix-path", ] [[package]] name = "gix-hash" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0926d3819c837750b4e03c7754901e73f68b8c9b690753a6372a1bed4eedce" dependencies = [ "faster-hex", "gix-features", "sha1-checked", "thiserror 2.0.18", ] [[package]] name = "gix-hashtable" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0e30b93eea8718baf7d8153fcb938e2926175bbf18097c09f1c01b6f0be0563" dependencies = [ "gix-hash", "hashbrown 0.17.1", "parking_lot", ] [[package]] name = "gix-ignore" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d491bab9bf2c9f341dc754f425c31d5d3f63aca615312167b82e1deeaca97d8d" dependencies = [ "bstr", "gix-glob", "gix-path", "gix-trace", "unicode-bom", ] [[package]] name = "gix-imara-diff" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", "hashbrown 0.17.1", ] [[package]] name = "gix-index" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e6b28cc592dc753adb58302bb14a64e412ee591a3bec77aa4df87bff74fa80d" dependencies = [ "bitflags 2.11.1", "bstr", "filetime", "fnv", "gix-bitmap", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-traverse", "gix-utils", "gix-validate", "hashbrown 0.17.1", "itoa", "libc", "memmap2", "rustix", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-lock" version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3bc074e5723027b482dcd9ab99d95804a53742f6de812d0172fbba4a186c1" dependencies = [ "gix-tempfile", "gix-utils", "thiserror 2.0.18", ] [[package]] name = "gix-object" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5cd857e29429c7213bdef3f5aef83f8cc124774fe8ae0d27b1607d218d6d525" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", "gix-hashtable", "gix-utils", "gix-validate", "itoa", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-odb" version = "0.81.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d004c32858b1556f2d7874405edb3c97dc78fc09beaa87d57bb077ee2858a7d" dependencies = [ "arc-swap", "gix-features", "gix-fs", "gix-hash", "gix-hashtable", "gix-object", "gix-pack", "gix-path", "gix-quote", "memmap2", "parking_lot", "tempfile", "thiserror 2.0.18", ] [[package]] name = "gix-pack" version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e43626f2a27d1033674ec1a196b845614231e6bbd949d5e21c133045ff56b174" dependencies = [ "clru", "gix-chunk", "gix-error", "gix-features", "gix-hash", "gix-hashtable", "gix-object", "gix-path", "memmap2", "smallvec", "thiserror 2.0.18", "uluru", ] [[package]] name = "gix-packetline" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18337ba2830bb43367d1af43819c8c78f31337f079fc76d0f1f1750a173126" dependencies = [ "bstr", "faster-hex", "gix-trace", "thiserror 2.0.18", ] [[package]] name = "gix-path" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afa6ac14cd14939ea94a496ce7460daa6511c09f5b84757e9cfc6f9c8d0f93a6" dependencies = [ "bstr", "gix-trace", "gix-validate", "thiserror 2.0.18", ] [[package]] name = "gix-pathspec" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3050783b41ee11511e1e8fb35623df81806194f4030395f14f48ea37c2798c9f" dependencies = [ "bitflags 2.11.1", "bstr", "gix-attributes", "gix-config-value", "gix-glob", "gix-path", "thiserror 2.0.18", ] [[package]] name = "gix-protocol" version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51dea3acb390707ab868f1f9584f18449eb95d869deffae96768e47d303595ee" dependencies = [ "bstr", "gix-date", "gix-features", "gix-hash", "gix-ref", "gix-shallow", "gix-transport", "gix-utils", "maybe-async", "nonempty", "thiserror 2.0.18", ] [[package]] name = "gix-quote" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef" dependencies = [ "bstr", "gix-error", "gix-utils", ] [[package]] name = "gix-ref" version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c04f64c37eb7e6feb73c7060f8dc6f381cc5de5d53249bfd450bc48a86b2e8b" dependencies = [ "gix-actor", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-path", "gix-tempfile", "gix-utils", "gix-validate", "memmap2", "thiserror 2.0.18", ] [[package]] name = "gix-refspec" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b216ae06ec74b5f24ad0142026a997fb0a935b7410eaf9c1616fc3f0e6c5a6d3" dependencies = [ "bstr", "gix-error", "gix-glob", "gix-hash", "gix-revision", "gix-validate", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-revision" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b47c88884dd3c1a19a39da19d10211fcdea2809aadc86869b6e824a1774340f" dependencies = [ "bstr", "gix-commitgraph", "gix-date", "gix-error", "gix-hash", "gix-object", "gix-revwalk", "nonempty", ] [[package]] name = "gix-revwalk" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85f5756abffe0917827aac683b13684ed99875bc398fa1f9b8f479b0681ef9e6" dependencies = [ "gix-commitgraph", "gix-date", "gix-error", "gix-hash", "gix-hashtable", "gix-object", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-sec" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8519976e4c7e486270740a5400369f37940779b80bd1377d94cfa1125d01b3" dependencies = [ "bitflags 2.11.1", "gix-path", "libc", "windows-sys 0.61.2", ] [[package]] name = "gix-shallow" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a292fc2fe548c5dfa575479d16b445b0ddf1dd2f56f1fec6aed386f82553cd97" dependencies = [ "bstr", "gix-hash", "gix-lock", "nonempty", "thiserror 2.0.18", ] [[package]] name = "gix-status" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22042e385d28a34275e029d98f4970285045be14b9073658ca897923f2ed8700" dependencies = [ "bstr", "filetime", "gix-diff", "gix-dir", "gix-features", "gix-filter", "gix-fs", "gix-hash", "gix-index", "gix-object", "gix-path", "gix-pathspec", "gix-worktree", "portable-atomic", "thiserror 2.0.18", ] [[package]] name = "gix-submodule" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3059890ef054066c22a94bfc6a3eaba0d806aedcd630a0bc9e5783fd88884781" dependencies = [ "bstr", "gix-config", "gix-path", "gix-pathspec", "gix-refspec", "gix-url", "thiserror 2.0.18", ] [[package]] name = "gix-tempfile" version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "691ea1e31435c7e7d4d04705ec9d1c0d9482c46b2acf512bc723939d8f0af7fb" dependencies = [ "dashmap", "gix-fs", "libc", "parking_lot", "tempfile", ] [[package]] name = "gix-trace" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44dc45eae785c0eb14173e0f152e6e224dcf4d45b6a6999a3aed22af541ad678" [[package]] name = "gix-transport" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd0e34995b1aab0fa8dff2af8db726a0bfad3e119c89302604463264046e7ff" dependencies = [ "bstr", "gix-command", "gix-features", "gix-packetline", "gix-quote", "gix-sec", "gix-url", "thiserror 2.0.18", ] [[package]] name = "gix-traverse" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8de590ecc86a3b2870665f2288324fa9f7f8672c7fc2d4e020fdd81cd1f7aed" dependencies = [ "bitflags 2.11.1", "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-url" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bb01ec69d55e82ccb7a19e264501ead4e6aac38463a8cebfdd81e22bb67ab2" dependencies = [ "bstr", "gix-path", "percent-encoding", "thiserror 2.0.18", ] [[package]] name = "gix-utils" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66c50966184123caf580ffa64e28031a878597f1c7fceb8fe19566c38eb1b771" dependencies = [ "bstr", "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc6fc771c4063ba7cd2f47b91fb6076251c6a823b64b7fe7b8874b0fe4afae3" dependencies = [ "bstr", ] [[package]] name = "gix-worktree" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef414ed275e8407cd5d53d301e83be19700b0dd3f859d2434417b58f454a2d1" dependencies = [ "bstr", "gix-attributes", "gix-fs", "gix-glob", "gix-hash", "gix-ignore", "gix-index", "gix-object", "gix-path", "gix-validate", ] [[package]] name = "gix-worktree-state" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bffae8b3ca258fdd50370cd51f06deb4c76a3b43db3868bc28dde45ffa77d69" dependencies = [ "bstr", "gix-features", "gix-filter", "gix-fs", "gix-index", "gix-object", "gix-path", "gix-worktree", "io-close", "thiserror 2.0.18", ] [[package]] name = "gix-worktree-stream" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25e9ed30100c63f7590bc581c225e53f731a53e06aa79a245739c07f7dcc557" dependencies = [ "gix-attributes", "gix-error", "gix-features", "gix-filter", "gix-fs", "gix-hash", "gix-object", "gix-path", "gix-traverse", "parking_lot", ] [[package]] name = "globset" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata", "regex-syntax", ] [[package]] name = "half" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "zerocopy", ] [[package]] name = "hash32" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash 0.1.5", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", "stable_deref_trait", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", "serde", "serde_core", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "insta" version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ "console", "once_cell", "regex", "similar", "tempfile", ] [[package]] name = "instability" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling", "indoc", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "interim" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9ce9099a85f468663d3225bf87e85d0548968441e1db12248b996b24f0f5b5a" dependencies = [ "chrono", "logos", ] [[package]] name = "io-close" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" dependencies = [ "libc", "winapi", ] [[package]] name = "is_executable" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" dependencies = [ "windows-sys 0.60.2", ] [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", "windows-sys 0.61.2", ] [[package]] name = "jiff-static" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "jiff-tzdb" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] name = "jiff-tzdb-platform" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ "jiff-tzdb", ] [[package]] name = "jj-cli" version = "0.42.0" dependencies = [ "ansi-to-tui", "anstream", "assert_cmd", "assert_matches", "async-trait", "bstr", "chrono", "clap", "clap-markdown", "clap_complete", "clap_complete_nushell", "clap_mangen", "criterion", "crossterm", "datatest-stable", "dunce", "erased-serde", "etcetera", "futures 0.3.32", "gix", "globset", "indexmap", "indoc", "insta", "itertools 0.14.0", "jj-cli", "jj-lib", "jsonschema", "libc", "maplit", "mimalloc", "nix 0.31.3", "once_cell", "percent-encoding", "pest", "pest_derive", "pollster", "proptest", "proptest-state-machine", "rand 0.10.1", "rand_chacha 0.10.0", "ratatui", "regex", "rpassword", "sapling-renderdag", "sapling-streampager", "scm-record", "serde", "serde_json", "shlex 2.0.1", "slab", "tempfile", "test-case", "testutils", "textwrap", "thiserror 2.0.18", "timeago", "toml", "toml_edit", "tracing", "tracing-chrome", "tracing-subscriber", "unicode-width 0.2.2", "whoami", ] [[package]] name = "jj-lib" version = "0.42.0" dependencies = [ "assert_matches", "async-trait", "blake2", "bstr", "chrono", "clru", "criterion", "digest", "dunce", "either", "etcetera", "eyre", "futures 0.3.32", "gix", "gix-ignore", "globset", "hashbrown 0.17.1", "indexmap", "indoc", "insta", "interim", "itertools 0.14.0", "jj-lib-proc-macros", "maplit", "nix 0.31.3", "num_cpus", "once_cell", "pest", "pest_derive", "pollster", "pretty_assertions", "proptest", "prost", "rand 0.10.1", "rand_chacha 0.10.0", "rayon", "ref-cast", "regex", "rustix", "rustversion", "same-file", "sapling-renderdag", "serde", "smallvec", "strsim", "tempfile", "test-case", "testutils", "thiserror 2.0.18", "tokio", "toml_edit", "tracing", "watchman_client", "winreg", ] [[package]] name = "jj-lib-proc-macros" version = "0.42.0" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "js-sys" version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", "once_cell", "wasm-bindgen", ] [[package]] name = "jsonschema" version = "0.46.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631" dependencies = [ "ahash", "bytecount", "data-encoding", "email_address", "fancy-regex 0.18.0", "fraction", "getrandom 0.3.4", "idna", "itoa", "num-cmp", "num-traits", "percent-encoding", "referencing", "regex", "regex-syntax", "serde", "serde_json", "unicode-general-category", "uuid-simd", ] [[package]] name = "kasuari" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", "thiserror 2.0.18", ] [[package]] name = "kstring" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ "static_assertions", ] [[package]] name = "lab" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libmimalloc-sys" version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [ "cc", ] [[package]] name = "libredox" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags 2.11.1", "libc", "plain", "redox_syscall 0.7.4", ] [[package]] name = "libtest-mimic" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" dependencies = [ "anstream", "anstyle", "clap", "escape8259", ] [[package]] name = "line-clipping" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" dependencies = [ "beef", "fnv", "lazy_static", "proc-macro2", "quote", "regex-syntax", "rustc_version", "syn 2.0.117", ] [[package]] name = "logos-derive" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" dependencies = [ "logos-codegen", ] [[package]] name = "lru" version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] [[package]] name = "mac_address" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" dependencies = [ "nix 0.29.0", "winapi", ] [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "maybe-async" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] [[package]] name = "memmem" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "micromap" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" [[package]] name = "mimalloc" version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" dependencies = [ "libmimalloc-sys", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] [[package]] name = "multimap" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "nix" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] name = "nonempty" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "num" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-cmp" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "objc2-core-foundation" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "objc2-system-configuration" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" dependencies = [ "objc2-core-foundation", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "outref" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "page_size" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ "libc", "winapi", ] [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.18", "smallvec", "windows-link", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "pest_meta" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2", ] [[package]] name = "petgraph" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.5", "indexmap", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.6", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "pollster" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "predicates" version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "difflib", "predicates-core", ] [[package]] name = "predicates-core" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn 2.0.117", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" dependencies = [ "parking_lot", ] [[package]] name = "proptest" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", "bitflags 2.11.1", "num-traits", "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", "unarray", ] [[package]] name = "proptest-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c57924a81864dddafba92e1bf92f9bf82f97096c44489548a60e888e1547549b" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "proptest-state-machine" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1675727965d66ff6f335e7d398e477184da08f4bed22f1a7f0dbf2f077f56f2e" dependencies = [ "proptest", ] [[package]] name = "prost" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", ] [[package]] name = "prost-build" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", "itertools 0.14.0", "log", "multimap", "petgraph", "prettyplease", "prost", "prost-types", "regex", "syn 2.0.117", "tempfile", ] [[package]] name = "prost-derive" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "prost-types" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] [[package]] name = "rand" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", "rand_core 0.10.1", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.5", ] [[package]] name = "rand_chacha" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" dependencies = [ "ppv-lite86", "rand_core 0.10.1", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "rand_core" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_xorshift" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ "rand_core 0.9.5", ] [[package]] name = "ratatui" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-macros", "ratatui-termwiz", "ratatui-widgets", ] [[package]] name = "ratatui-core" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.11.1", "compact_str", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", "kasuari", "lru", "strum", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.2", ] [[package]] name = "ratatui-crossterm" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" dependencies = [ "cfg-if", "crossterm", "instability", "ratatui-core", ] [[package]] name = "ratatui-macros" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" dependencies = [ "ratatui-core", "ratatui-widgets", ] [[package]] name = "ratatui-termwiz" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" dependencies = [ "ratatui-core", "termwiz", ] [[package]] name = "ratatui-widgets" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ "bitflags 2.11.1", "hashbrown 0.16.1", "indoc", "instability", "itertools 0.14.0", "line-clipping", "ratatui-core", "strum", "time", "unicode-segmentation", "unicode-width 0.2.2", ] [[package]] name = "rayon" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "redox_syscall" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "redox_users" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", "thiserror 2.0.18", ] [[package]] name = "ref-cast" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "referencing" version = "0.46.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2" dependencies = [ "ahash", "fluent-uri", "getrandom 0.3.4", "hashbrown 0.16.1", "itoa", "micromap", "parking_lot", "percent-encoding", "serde_json", ] [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "roff" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189" [[package]] name = "rpassword" version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" dependencies = [ "libc", "rtoolbox", "windows-sys 0.61.2", ] [[package]] name = "rtoolbox" version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "sapling-renderdag" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edffb89cab87bd0901c5749d576f5d37a1f34e05160e936f463f4e94cc447b61" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "sapling-streampager" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17810e56620c313aa7ee8b1ffcd6b148e84061a9f80fbc609df0495c4b4a6172" dependencies = [ "dirs", "enum_dispatch", "lru", "memmap2", "regex", "scopeguard", "serde", "smallvec", "terminfo", "termwiz", "thiserror 2.0.18", "unicode-segmentation", "unicode-width 0.1.14", "vec_map", ] [[package]] name = "scm-record" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcf5abfa361c5eb6870aa88b7736b6063fa127fc1eddf96fc127cd422baef68" dependencies = [ "cassowary", "crossterm", "num-traits", "ratatui", "serde", "serde_json", "thiserror 2.0.18", "tracing", "unicode-width 0.2.2", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_bser" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56b4bcc15e42e5b5ae16c6f75582bef80d36c6ffe2c03b1b5317754b38f8717" dependencies = [ "anyhow", "byteorder", "bytes", "serde", "serde_bytes", "thiserror 1.0.69", ] [[package]] name = "serde_bytes" version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ "serde", "serde_core", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "serde_json" version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "serde_spanned" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "digest", ] [[package]] name = "sha1-checked" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ "digest", "sha1", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shell-words" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "libc", ] [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "tempfile" version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", ] [[package]] name = "terminal_size" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", "windows-sys 0.61.2", ] [[package]] name = "terminfo" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", "nom 7.1.3", "phf", "phf_codegen", ] [[package]] name = "termios" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" dependencies = [ "libc", ] [[package]] name = "termtree" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "termwiz" version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", "bitflags 2.11.1", "fancy-regex 0.11.0", "filedescriptor", "finl_unicode", "fixedbitset 0.4.2", "hex", "lazy_static", "libc", "log", "memmem", "nix 0.29.0", "num-derive", "num-traits", "ordered-float", "pest", "pest_derive", "phf", "sha2", "signal-hook", "siphasher", "terminfo", "termios", "thiserror 1.0.69", "ucd-trie", "unicode-segmentation", "vtparse", "wezterm-bidi", "wezterm-blob-leases", "wezterm-color-types", "wezterm-dynamic", "wezterm-input-types", "winapi", ] [[package]] name = "test-case" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" dependencies = [ "test-case-macros", ] [[package]] name = "test-case-core" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" dependencies = [ "cfg-if", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "test-case-macros" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", "test-case-core", ] [[package]] name = "testutils" version = "0.42.0" dependencies = [ "async-trait", "bstr", "eyre", "futures 0.3.32", "gix", "itertools 0.14.0", "jj-lib", "pollster", "proptest", "proptest-derive", "proptest-state-machine", "rand 0.10.1", "tempfile", "tokio", "toml_edit", ] [[package]] name = "textwrap" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", "unicode-linebreak", "unicode-width 0.2.2", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "time" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "libc", "num-conv", "num_threads", "powerfmt", "serde_core", "time-core", ] [[package]] name = "time-core" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "timeago" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86660b0935ca5912eb155f5b3794e2f8f1a09d8c00c9efd5c1075ca26403638" [[package]] name = "tinystr" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "tinyvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "tokio-util" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "log", "pin-project-lite", "slab", "tokio", ] [[package]] name = "toml" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", "serde_spanned", "toml_datetime", "toml_parser", "toml_writer", "winnow", ] [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "serde_core", "serde_spanned", "toml_datetime", "toml_parser", "toml_writer", "winnow", ] [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "tracing-chrome" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf0a738ed5d6450a9fb96e86a23ad808de2b727fd1394585da5cdd6788ffe724" dependencies = [ "serde_json", "tracing-core", "tracing-subscriber", ] [[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "typeid" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uluru" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" dependencies = [ "arrayvec", ] [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bom" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-general-category" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-linebreak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-truncate" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools 0.14.0", "unicode-segmentation", "unicode-width 0.2.2", ] [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "atomic", "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] [[package]] name = "uuid-simd" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" dependencies = [ "outref", "vsimd", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vsimd" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "vtparse" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" dependencies = [ "utf8parse", ] [[package]] name = "wait-timeout" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ "wit-bindgen 0.57.1", ] [[package]] name = "wasip3" version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen 0.51.0", ] [[package]] name = "wasite" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" dependencies = [ "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "wasm-bindgen" version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", "wasmparser", ] [[package]] name = "wasm-metadata" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", "wasm-encoder", "wasmparser", ] [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", ] [[package]] name = "watchman_client" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bc4c9bb443a7aae10d4fa7807bffc397805315e2305288c90c80e2f66cfb52" dependencies = [ "anyhow", "bytes", "futures 0.3.32", "maplit", "serde", "serde_bser", "thiserror 1.0.69", "tokio", "tokio-util", "winapi", ] [[package]] name = "web-sys" version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "wezterm-bidi" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" dependencies = [ "log", "wezterm-dynamic", ] [[package]] name = "wezterm-blob-leases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.4", "mac_address", "sha2", "thiserror 1.0.69", "uuid", ] [[package]] name = "wezterm-color-types" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" dependencies = [ "csscolorparser", "deltae", "lazy_static", "wezterm-dynamic", ] [[package]] name = "wezterm-dynamic" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" dependencies = [ "log", "ordered-float", "strsim", "thiserror 1.0.69", "wezterm-dynamic-derive", ] [[package]] name = "wezterm-dynamic-derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "wezterm-input-types" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" dependencies = [ "bitflags 1.3.2", "euclid", "lazy_static", "serde", "wezterm-dynamic", ] [[package]] name = "whoami" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" dependencies = [ "libc", "libredox", "objc2-system-configuration", "wasite", "web-sys", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.5", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] [[package]] name = "winreg" version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" dependencies = [ "cfg-if", "windows-sys 0.61.2", ] [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "wit-bindgen-core" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", "wit-parser", ] [[package]] name = "wit-bindgen-rust" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", ] [[package]] name = "wit-bindgen-rust-macro" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ "anyhow", "prettyplease", "proc-macro2", "quote", "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] [[package]] name = "wit-component" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.1", "indexmap", "log", "serde", "serde_derive", "serde_json", "wasm-encoder", "wasm-metadata", "wasmparser", "wit-parser", ] [[package]] name = "wit-parser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", "indexmap", "log", "semver", "serde", "serde_derive", "serde_json", "unicode-xid", "wasmparser", ] [[package]] name = "writeable" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "zerofrom" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", "synstructure", ] [[package]] name = "zerotrie" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "zlib-rs" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" jj-vcs-jj-b8f7c45/Cargo.toml000066400000000000000000000116371521031206200156630ustar00rootroot00000000000000cargo-features = [] [workspace] resolver = "3" members = ["cli", "lib", "lib/gen-protos", "lib/proc-macros", "lib/testutils"] [workspace.package] version = "0.42.0" license = "Apache-2.0" rust-version = "1.89" # NOTE: remember to update CI, mise.toml, contributing.md, changelog.md, and install-and-setup.md edition = "2024" readme = "README.md" homepage = "https://www.jj-vcs.dev/" repository = "https://github.com/jj-vcs/jj" documentation = "https://docs.jj-vcs.dev/" categories = ["version-control", "development-tools"] keywords = ["VCS", "DVCS", "SCM", "Git", "Mercurial"] [workspace.dependencies] ansi-to-tui = "8.0.1" anstream = "1.0.0" assert_cmd = "2.2.2" assert_matches = "1.5.0" async-trait = "0.1.89" blake2 = "0.10.6" bstr = { version = "1.12.1", features = ["serde"] } chrono = { version = "0.4.44", default-features = false, features = [ "std", "clock", "serde", ] } clap = { version = "4.6.1", features = [ "derive", "deprecated", "wrap_help", "string", ] } # Update clap-markdown manually since test_generate_md_cli_help snapshot # will need regenerating. clap-markdown = "=0.1.5" clap_complete = { version = "4.6.5", features = ["unstable-dynamic"] } clap_complete_nushell = "4.6.0" clap_mangen = "0.3.0" clru = "0.6.3" criterion = "0.8.2" crossterm = { version = "0.29", default-features = false, features = ["windows"] } datatest-stable = "0.3.3" digest = "0.10.7" dunce = "1.0.5" either = "1.16.0" erased-serde = "0.4.10" etcetera = "0.11.0" eyre = "0.6" futures = "0.3.32" gix = { version = "0.84.0", default-features = false, features = [ "attributes", "blob-diff", "index", "max-performance-safe", "sha1", "zlib-rs", ] } gix-ignore = { version = "0.21.0" } globset = "0.4.18" hashbrown = { version = "0.17.1", default-features = false, features = ["inline-more"] } indexmap = { version = "2.14.0", features = ["serde"] } indoc = "2.0.7" insta = { version = "1.47.2", features = ["filters"] } interim = { version = "0.2.1", features = ["chrono_0_4"] } itertools = "0.14.0" jsonschema = { version = "0.46.5", default-features = false } libc = { version = "0.2.186" } maplit = "1.0.2" mimalloc = "0.1.52" nix = "0.31.3" num_cpus = "1.17.0" once_cell = "1.21.4" percent-encoding = "2.3.2" pest = "2.8.6" pest_derive = "2.8.6" pollster = "0.4.0" pretty_assertions = "1.4.1" proc-macro2 = "1.0.104" proptest = "1.11.0" proptest-derive = "0.8.0" proptest-state-machine = "0.8.0" prost = "0.14.3" prost-build = "0.14.3" quote = "1.0.45" rand = "0.10.1" rand_chacha = "0.10.0" ratatui = { version = "0.30.0", features = ["crossterm"] } rayon = "1.12.0" ref-cast = "1.0.25" regex = "1.12.3" rpassword = "7.5.4" rustix = { version = "1.1.4", features = ["fs"] } rustversion = "1.0.22" same-file = "1.0.6" sapling-renderdag = "0.1.0" sapling-streampager = "0.12.0" scm-record = "0.10.0" serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0.150" shlex = "2.0.1" slab = "0.4.12" smallvec = { version = "1.15.1", features = [ "const_generics", "const_new", "serde", "union", ] } strsim = "0.11.1" syn = "2.0.111" tempfile = "3.27.0" test-case = "3.3.1" textwrap = "0.16.2" thiserror = "2.0.17" timeago = { version = "0.6.0", default-features = false } tokio = { version = "1.52.3", features = ["io-util"] } toml = "1.1.0" toml_edit = { version = "0.25.12", features = ["serde"] } tracing = "0.1.44" tracing-chrome = "0.7.2" tracing-subscriber = { version = "0.3.23", default-features = false, features = [ "std", "ansi", "env-filter", "fmt", ] } unicode-width = "0.2.2" watchman_client = "0.9.0" whoami = "2.1.2" winreg = "0.56" # put all inter-workspace libraries, i.e. those that use 'path = ...' here in # their own (alphabetically sorted) block jj-lib = { path = "lib", version = "0.42.0", default-features = false } jj-lib-proc-macros = { path = "lib/proc-macros", version = "0.42.0" } testutils = { path = "lib/testutils" } [workspace.lints.clippy] borrow_as_ptr = "warn" cast_lossless = "warn" cloned_instead_of_copied = "warn" elidable_lifetime_names = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" flat_map_option = "warn" format_collect = "warn" format_push_string = "warn" implicit_clone = "warn" manual_let_else = "warn" needless_for_each = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" ref_as_ptr = "warn" ref_option = "warn" semicolon_if_nothing_returned = "warn" single_char_pattern = "warn" stable_sort_primitive = "warn" uninlined_format_args = "warn" unnecessary_literal_bound = "warn" unnecessary_semicolon = "warn" unnested_or_patterns = "warn" unused_trait_names = "warn" use_self = "warn" useless_conversion = "warn" while_let_loop = "warn" [workspace.lints.rust] let_underscore_drop = "warn" redundant_imports = "warn" [profile.dev.package."*"] # Compile all dependencies with opt-level=3 to help speed up tests and # debug builds codegen-units = 1 debug = "line-tables-only" opt-level = 3 [profile.release] strip = "debuginfo" codegen-units = 1 jj-vcs-jj-b8f7c45/GOVERNANCE.md000066400000000000000000000156611521031206200157050ustar00rootroot00000000000000# 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 — 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 — 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. - Practicing transparency in the project, communicating decisions and their rationale when appropriate. 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) - Benjamin Tan (@bnjmnt4n) - Ilya Grigoriev (@ilyagr) - Martin von Zweigbergk (@martinvonz) - Scott Taylor (@scott2000) - 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. ### Single-Company Influence At most 1/3 of the maintainers may be paid for their contributions by a single company. This is to reduce the risk of a single company controlling the project's direction. If the 1/3 limit gets exceeded because an existing maintainer gets hired by the same company as some other existing maintainer(s), then the maintainers will have to decide how to resolve the situation. The maintainer in question gets to vote, as long as this doesn't mean the company in question has half the votes (usually meaning there are at least 5 maintainers total). jj-vcs-jj-b8f7c45/LICENSE000066400000000000000000000261361521031206200147400ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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 http://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. jj-vcs-jj-b8f7c45/README.md000066400000000000000000000427021521031206200152070ustar00rootroot00000000000000
# Jujutsu—a version control system

[![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)
[![License](https://img.shields.io/github/license/martinvonz/jj)](https://github.com/jj-vcs/jj/blob/main/LICENSE) [![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]   •  ** **[Installation]   •  ** **[Getting Started]   •  ** **[Development Roadmap]   •  ** **[Contributing](#contributing)** [Homepage]: https://www.jj-vcs.dev [Installation]: https://docs.jj-vcs.dev/latest/install-and-setup [Getting Started]: https://docs.jj-vcs.dev/latest/tutorial [Development Roadmap]: https://docs.jj-vcs.dev/latest/roadmap
## Introduction Jujutsu is a powerful [version control system](https://en.wikipedia.org/wiki/Version_control) for software projects. You use it to get a copy of your code, track changes to the code, and finally publish those changes for others to see and use. It is designed from the ground up to be easy to use—whether you're new or experienced, working on brand new projects alone, or large scale software projects with large histories and teams. Jujutsu is unlike most other systems, because internally it abstracts the user interface and version control algorithms from the *storage systems* used to serve your content. This allows it to serve as a VCS with many possible physical backends, that may have their own data or networking models—like [Mercurial] or [Breezy], or hybrid systems like Google's cloud-based design, [Piper/CitC]. [Mercurial]: https://www.mercurial-scm.org/ [Breezy]: https://www.breezy-vcs.org/ [Piper/CitC]: https://youtu.be/W71BTkUbdqE?t=645 Today, we use Git repositories as a storage layer to serve and track content, making it **compatible with many of your favorite Git-based tools, right now!** However, note that only commits and files are stored in Git; bookmarks (branches) and other higher-level metadata are stored in custom storage outside of Git. All core developers use Jujutsu to develop Jujutsu, right here on GitHub. But it should hopefully work with your favorite Git forges, too. We combine many distinct design choices and concepts from other version control systems into a single tool. Some of those sources of inspiration include: - **Git**: We make an effort to [be fast][perf]—with a snappy UX, efficient algorithms, correct data structures, and good-old-fashioned attention to detail. The default storage backend uses Git repositories for "physical storage", for wide interoperability and ease of onboarding. - **Mercurial & Sapling**: There are many Mercurial-inspired features, such as the [revset] language to select commits. There is [no explicit index][no-index] or staging area. Branches are "anonymous" like Mercurial, so you don't need to make up a name for each small change. Primitives for rewriting history are powerful and simple. Formatting output is done with a robust template language that can be configured by the user. - **Darcs**: Jujutsu keeps track of conflicts as [first-class objects][conflicts] in its model; they are first-class in the same way commits are, while alternatives like Git simply think of conflicts as textual diffs. While not as rigorous as systems like Darcs (which is based on a formalized theory of patches, as opposed to snapshots), the effect is that many forms of conflict resolution can be performed and propagated automatically. [perf]: https://github.com/jj-vcs/jj/discussions/49 [revset]: https://docs.jj-vcs.dev/latest/revsets/ [no-index]: https://docs.jj-vcs.dev/latest/git-comparison/#the-index [conflicts]: https://docs.jj-vcs.dev/latest/conflicts/ And it adds several innovative, useful features of its own: - **Working-copy-as-a-commit**: Changes to files are [recorded automatically][wcc] as normal commits, and amended on every subsequent change. This "snapshot" design simplifies the user-facing data model (commits are the only visible object), simplifies internal algorithms, and completely subsumes features like Git's stashes or the index/staging-area. - **Operation log & undo**: Jujutsu records every operation that is performed on the repository, from commits, to pulls, to pushes. This makes debugging problems like "what just happened?" or "how did I end up here?" easier, *especially* when you're helping your coworker answer those questions about their repository! And because everything is recorded, you can undo that mistake you just made with ease. Version control has finally entered [the 1960s][undo-history]! - **Automatic rebase and conflict resolution**: When you modify a commit, every descendent is automatically rebased on top of the freshly-modified one. This makes "patch-based" workflows a breeze. If you resolve a conflict in a commit, the _resolution_ of that conflict is also propagated through descendants as well. In effect, this is a completely transparent version of `git rebase --update-refs` combined with `git rerere`, supported by design. > [!WARNING] > The following features are available for use, but experimental; they may have > bugs, backwards incompatible storage changes, and user-interface changes! - **Safe, concurrent replication**: Have you ever wanted to store your version controlled repositories inside a Dropbox folder? Or continuously backup repositories to S3? No? Well, now you can! The fundamental problem with using filesystems like Dropbox and backup tools like `rsync` on your typical Git/Mercurial repositories is that they rely on *local filesystem operations* being atomic, serialized, and non-concurrent with respect to other reads and writes—which is _not_ true when operating on distributed file systems, or when operations like concurrent file copies (for backup) happen while lock files are being held. Jujutsu is instead designed to be [safe under concurrent scenarios][conc-safety]; simply using rsync or Dropbox and then using that resulting repository should never result in a repository in a *corrupt state*. The worst that _should_ happen is that it will expose conflicts between the local and remote state, leaving you to resolve them. [wcc]: https://docs.jj-vcs.dev/latest/working-copy/ [undo-history]: https://en.wikipedia.org/wiki/Undo#History [conc-safety]: https://docs.jj-vcs.dev/latest/technical/concurrency/ 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 "jj". 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 [![Discord](https://img.shields.io/discord/968932220549103686.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/dkmfj3aGQN), start a [GitHub Discussion](https://github.com/jj-vcs/jj/discussions), or 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 📣 - **December 2024**: The `jj` Repository has moved to the `jj-vcs` GitHub organization. - **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), 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 diff editor for all platforms, "immutable revsets" to avoid accidentally `edit`-ing the wrong revisions, and lots of polish. - **Jan 2023**: Martin gave a presentation about Google's plans for Jujutsu at Git Merge 2022! See the [slides][merge-slides] or the [recording][merge-talk]. ### Related Media - **Mar 2024**: Chris Krycho started [a YouTube series about Jujutsu][krycho-yt]. - **Feb 2024**: Chris Krycho published an article about Jujutsu called [jj init][krycho] and Steve Klabnik followed up with the [Jujutsu Tutorial][klabnik]. - **Jan 2024**: Jujutsu was featured in an LWN.net article called [Jujutsu: a new, Git-compatible version control system][lwn]. - **Jan 2023**: Martin's Talk about Jujutsu at Git Merge 2022, [video][merge-talk] and the associated [slides][merge-slides]. The wiki also contains a more extensive list of [media references][wiki-media]. [krycho-yt]: https://www.youtube.com/playlist?list=PLelyiwKWHHAq01Pvmpf6x7J0y-yQpmtxp [krycho]: https://v5.chriskrycho.com/essays/jj-init/ [klabnik]: https://steveklabnik.github.io/jujutsu-tutorial/ [lwn]: https://lwn.net/Articles/958468/ [merge-talk]: https://www.youtube.com/watch?v=bx_LGilOuE4 [merge-slides]: https://docs.google.com/presentation/d/1F8j9_UOOSGUN9MvHxPZX_L4bQ9NMcYOp1isn17kTC_M/view [merge-vid-2024]: https://www.youtube.com/watch?v=LV0JzI8IcCY [wiki-media]: https://github.com/jj-vcs/jj/wiki/Media ## Getting started > [!IMPORTANT] > Jujutsu is an **experimental version control system**. While Git compatibility > is stable, and most developers use it daily for all their needs, there may > still be work-in-progress features, suboptimal UX, and workflow gaps that make > it unusable for your particular use. Follow the [installation instructions](https://docs.jj-vcs.dev/latest/install-and-setup) to obtain and configure `jj`. The best way to get started is probably to go through [the tutorial](https://docs.jj-vcs.dev/latest/tutorial). Also see the [Git comparison](https://docs.jj-vcs.dev/latest/git-comparison), which includes a table of `jj` vs. `git` commands. As you become more familiar with Jujutsu, the following resources may be helpful: - The [FAQ](https://docs.jj-vcs.dev/latest/FAQ). - The [Glossary](https://docs.jj-vcs.dev/latest/glossary). - The `jj help` command (e.g. `jj help rebase`). - The `jj help -k ` 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 [the docs for the prerelease (main branch) version](https://docs.jj-vcs.dev/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 the header of the website when you scroll to the top of any page. ## Features ### Compatible with Git Jujutsu is designed so that the underlying data and storage model is abstract. Today, only the Git backend is production-ready. The Git backend uses the [gitoxide](https://github.com/Byron/gitoxide) Rust library. [backends]: https://docs.jj-vcs.dev/latest/glossary#backend 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. You can fetch branches from a regular Git remote and push branches to the remote. You can always switch back to Git. Here is how you can explore a GitHub repository with `jj`. You can even have a [colocated local workspace](https://docs.jj-vcs.dev/latest/git-compatibility#colocated-jujutsugit-repos) where you can use both `jj` and `git` commands interchangeably. ### The working copy is automatically committed Jujutsu uses a real commit to represent the working copy. Checking out a commit results in a new working-copy commit on top of the target commit. Almost all commands automatically amend the working-copy commit. The working-copy being a commit means that commands never fail because the working copy is dirty (no "error: Your local changes to the following files..."), and there is no need for `git stash`. Also, because the working copy is a commit, commands work the same way on the working-copy commit as on any other commit, so you can set the commit message before you're done with the changes. ### The repo is the source of truth With Jujutsu, the working copy plays a smaller role than with Git. Commands snapshot the working copy before they start, then they update the repo, and then the working copy is updated (if the working-copy commit was modified). Almost all commands (even checkout!) operate on the commits in the repo, leaving the common functionality of snapshotting and updating of the working copy to centralized code. For example, `jj restore` (similar to `git restore`) can restore from any commit and into any commit, and `jj describe` can set the commit message of any commit (defaults to the working-copy commit). ### Entire repo is under version control All operations you perform in the repo are recorded, along with a snapshot of the repo state after the operation. This means that you can easily restore to an earlier repo state, simply undo your operations one-by-one or even _revert_ a particular operation which does not have to be the most recent one. ### Conflicts can be recorded in commits If an operation results in [conflicts](https://docs.jj-vcs.dev/latest/glossary#conflict), information about those conflicts will be recorded in the commit(s). The operation will succeed. You can then resolve the conflicts later. One consequence of this design is that there's no need to continue interrupted operations. Instead, you get a single workflow for resolving conflicts, regardless of which command caused them. This design also lets Jujutsu rebase merge commits correctly (unlike both Git and Mercurial). Basic conflict resolution: Juggling conflicts: ### Automatic rebase Whenever you modify a commit, any descendants of the old commit will be rebased onto the new commit. Thanks to the conflict design described above, that can be done even if there are conflicts. Bookmarks pointing to rebased commits will be updated. So will the working copy if it points to a rebased commit. ### Comprehensive support for rewriting history Besides the usual rebase command, there's `jj describe` for editing the description (commit message) of an arbitrary commit. There's also `jj diffedit`, which lets you edit the changes in a commit without checking it out. To split a commit into two, use `jj split`. You can even move part of the changes in a commit to any other commit using `jj squash -i --from X --into Y`. ## Status The tool is fairly feature-complete, but some important features like support for Git submodules are not yet completed. There 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 is no native support for email-based workflows. Today, all core developers use `jj` to work on `jj`. I (Martin von Zweigbergk) have almost exclusively used `jj` to develop the project itself since early January 2021. I haven't had to re-clone from source (I don't think I've even had to restore from backup). There *will* be changes to workflows and backward-incompatible changes to the on-disk formats before version 1.0.0. For any format changes, we'll try to implement transparent upgrades (as we've done with recent changes), or provide upgrade commands or scripts if requested. ## Related work There are several tools trying to solve similar problems as Jujutsu. See [related work](https://docs.jj-vcs.dev/latest/related-work) for details. ## Contributing We welcome outside contributions, and there's plenty of things to do, so 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. We do have [a few policies and suggestions](https://docs.jj-vcs.dev/prerelease/contributing/) for contributors. The broad TL;DR: - Bug reports are very welcome! - Every commit that lands in the `main` branch is code reviewed. - Please behave yourself, and obey the Community Guidelines. - There **is** a mandatory CLA you must agree to. Importantly, it **does not** transfer copyright ownership to Google or anyone else; it simply gives us the right to safely redistribute and use your changes. ### Mandatory Google Disclaimer I (Martin von Zweigbergk, ) started Jujutsu as a hobby project in late 2019, and it has evolved into my full-time project at Google, with several other Googlers (now) assisting development in various capacities. That said, while Google is one of the main contributors, **this is not a supported Google product**, i.e. support is provided by the community. ## License Jujutsu is available as Open Source Software, under the Apache 2.0 license. See [`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). jj-vcs-jj-b8f7c45/SECURITY.md000066400000000000000000000007731521031206200155230ustar00rootroot00000000000000To report a security issue, please use the "Report a vulnerability" button on GitHub's Security tab for `jj`'s main repo, under [Advisories](https://github.com/jj-vcs/jj/security/advisories). Our vulnerability management team will respond within 3 working days of your report. If the issue is confirmed as a vulnerability, we will open a Security Advisory. This project follows a 90 day disclosure timeline. Feel free to email Jujutsu VCS Security at if you have questions. jj-vcs-jj-b8f7c45/cli/000077500000000000000000000000001521031206200144725ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/Cargo.toml000066400000000000000000000112771521031206200164320ustar00rootroot00000000000000[package] name = "jj-cli" description = "Jujutsu - an experimental version control system" default-run = "jj" autotests = false version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } license = { workspace = true } homepage = { workspace = true } repository = { workspace = true } documentation = { workspace = true } keywords = { workspace = true } include = [ "/LICENSE", "/build.rs", "/examples/", "/src/", "/docs/**", "/testing/", "/tests/", "!*.pending-snap", "!*.snap*", "/tests/cli-reference@.md.snap", ] [[bin]] name = "jj" path = "src/main.rs" [[bin]] name = "fake-bisector" path = "testing/fake-bisector.rs" required-features = ["test-fakes"] [[bin]] name = "fake-echo" path = "testing/fake-echo.rs" required-features = ["test-fakes"] [[bin]] name = "fake-editor" path = "testing/fake-editor.rs" required-features = ["test-fakes"] [[bin]] name = "fake-diff-editor" path = "testing/fake-diff-editor.rs" required-features = ["test-fakes"] [[bin]] name = "fake-formatter" path = "testing/fake-formatter.rs" required-features = ["test-fakes"] [[test]] name = "runner" [[test]] name = "datatest_runner" harness = false [dependencies] ansi-to-tui = { workspace = true } bstr = { workspace = true } chrono = { workspace = true } clap = { workspace = true } clap-markdown = { workspace = true } clap_complete = { workspace = true } clap_complete_nushell = { workspace = true } clap_mangen = { workspace = true } criterion = { workspace = true, optional = true } crossterm = { workspace = true } dunce = { workspace = true } erased-serde = { workspace = true } etcetera = { workspace = true } futures = { workspace = true } gix = { workspace = true, optional = true } globset = { workspace = true } indexmap = { workspace = true } indoc = { workspace = true } itertools = { workspace = true } jj-lib = { workspace = true } maplit = { workspace = true } mimalloc = { workspace = true } once_cell = { workspace = true } pest = { workspace = true } pest_derive = { workspace = true } pollster = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } regex = { workspace = true } ratatui = { workspace = true } rpassword = { workspace = true } sapling-renderdag = { workspace = true } sapling-streampager = { workspace = true } scm-record = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } shlex = { workspace = true } slab = { workspace = true } tempfile = { workspace = true } textwrap = { workspace = true } thiserror = { workspace = true } timeago = { workspace = true } toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true } tracing-chrome = { workspace = true } tracing-subscriber = { workspace = true } unicode-width = { workspace = true } whoami = { workspace = true } percent-encoding = { workspace = true } [target.'cfg(unix)'.dependencies] libc = { workspace = true } nix = { workspace = true, features = [ "signal" ] } [dev-dependencies] anstream = { workspace = true } assert_cmd = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } datatest-stable = { workspace = true } insta = { workspace = true } jsonschema = { workspace = true } proptest = { workspace = true } proptest-state-machine = { workspace = true } test-case = { workspace = true } testutils = { workspace = true } # https://github.com/rust-lang/cargo/issues/2911#issuecomment-1483256987 jj-cli = { path = ".", features = ["test-fakes"], default-features = false } [features] default = ["watchman", "git"] bench = ["dep:criterion"] git = ["jj-lib/git", "dep:gix"] test-fakes = ["jj-lib/testing"] watchman = ["jj-lib/watchman"] [package.metadata.binstall] # The archive name is jj, not jj-cli. Also, `cargo binstall` gets # confused by the `v` before versions in archive name. pkg-url = "{ repo }/releases/download/v{ version }/jj-v{ version }-{ target }.{ archive-format }" [lints] workspace = true [package.metadata.deb] # Install `cargo-deb` and run `cargo deb` to create a package in `target/debian/` name = "jj" maintainer = "Unspecified Debian Maintainer " copyright = "Copyright 2025 The Jujutsu Authors" license-file = "LICENSE" section = "vcs" priority = "optional" extended-description = "Distributed Version Control System inspired by Git, Mercurial and Darcs." assets = [ { source = "docs/*", dest = "usr/share/doc/jj/", mode = "644" }, # TODO: It'd be nice to package the output of `MKDOCS_OFFLINE=true uv run mkdocs build` in the docs dir as well, but this is not trivial. { source = "docs/*/*", dest = "usr/share/doc/jj/", mode = "644" }, { source = "target/release/jj", dest = "usr/bin/", mode = "755" }, ] default-features = true jj-vcs-jj-b8f7c45/cli/LICENSE000066400000000000000000000261361521031206200155070ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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 http://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. jj-vcs-jj-b8f7c45/cli/build.rs000066400000000000000000000061511521031206200161420ustar00rootroot00000000000000// Copyright 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::path::Path; use std::process::Command; const GIT_HEAD_PATH: &str = "../.git/HEAD"; const JJ_OP_HEADS_PATH: &str = "../.jj/repo/op_heads/heads"; fn main() { let version = std::env::var("CARGO_PKG_VERSION").unwrap(); println!("cargo:rerun-if-env-changed=NIX_JJ_GIT_HASH"); let git_hash = get_git_hash_from_nix().or_else(|| { if Path::new(GIT_HEAD_PATH).exists() { // In colocated workspace, .git/HEAD should reflect the working-copy parent. println!("cargo:rerun-if-changed={GIT_HEAD_PATH}"); } else if Path::new(JJ_OP_HEADS_PATH).exists() { // op_heads changes when working-copy files are mutated, which is way more // frequent than .git/HEAD. println!("cargo:rerun-if-changed={JJ_OP_HEADS_PATH}"); } get_git_hash_from_jj().or_else(get_git_hash_from_git) }); if let Some(git_hash) = git_hash { println!("cargo:rustc-env=JJ_VERSION={version}-{git_hash}"); } else { println!("cargo:rustc-env=JJ_VERSION={version}"); } let docs_symlink_path = Path::new("docs"); println!("cargo:rerun-if-changed={}", docs_symlink_path.display()); if docs_symlink_path.join("index.md").exists() { println!("cargo:rustc-env=JJ_DOCS_DIR=docs/"); } else { println!("cargo:rustc-env=JJ_DOCS_DIR=../docs/"); } } fn get_git_hash_from_nix() -> Option { std::env::var("NIX_JJ_GIT_HASH") .ok() .filter(|s| !s.is_empty()) } fn get_git_hash_from_jj() -> Option { Command::new("jj") .args([ "--ignore-working-copy", "--color=never", "log", "--no-graph", "-r=@-", "-T=commit_id ++ '-'", ]) .output() .ok() .filter(|output| output.status.success()) .map(|output| { let mut parent_commits = String::from_utf8(output.stdout).unwrap(); // If a development version of `jj` is compiled at a merge commit, this will // result in several commit ids separated by `-`s. parent_commits.truncate(parent_commits.trim_end_matches('-').len()); parent_commits }) } fn get_git_hash_from_git() -> Option { Command::new("git") .args(["rev-parse", "HEAD"]) .output() .ok() .filter(|output| output.status.success()) .map(|output| { str::from_utf8(&output.stdout) .unwrap() .trim_end() .to_owned() }) } jj-vcs-jj-b8f7c45/cli/docs000077700000000000000000000000001521031206200164262../docsustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/000077500000000000000000000000001521031206200163105ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-backend/000077500000000000000000000000001521031206200212075ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-backend/main.rs000066400000000000000000000144301521031206200225030ustar00rootroot00000000000000// Copyright 2022 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::path::Path; use std::pin::Pin; use std::time::SystemTime; use async_trait::async_trait; use futures::AsyncRead; use futures::stream::BoxStream; use jj_cli::cli_util::CliRunner; use jj_cli::cli_util::CommandHelper; use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; use jj_lib::backend::Backend; use jj_lib::backend::BackendInitError; use jj_lib::backend::BackendLoadError; use jj_lib::backend::BackendResult; use jj_lib::backend::ChangeId; use jj_lib::backend::Commit; use jj_lib::backend::CommitId; use jj_lib::backend::CopyHistory; use jj_lib::backend::CopyId; use jj_lib::backend::CopyRecord; use jj_lib::backend::FileId; use jj_lib::backend::RelatedCopy; use jj_lib::backend::SigningFn; use jj_lib::backend::SymlinkId; use jj_lib::backend::Tree; use jj_lib::backend::TreeId; use jj_lib::git_backend::GitBackend; use jj_lib::index::Index; use jj_lib::repo::StoreFactories; use jj_lib::repo_path::RepoPath; use jj_lib::repo_path::RepoPathBuf; use jj_lib::settings::UserSettings; use jj_lib::signing::Signer; use jj_lib::workspace::Workspace; use jj_lib::workspace::WorkspaceInitError; #[derive(clap::Parser, Clone, Debug)] enum CustomCommand { /// Initialize a workspace using the Jit backend InitJit, } fn create_store_factories() -> StoreFactories { let mut store_factories = StoreFactories::empty(); // Register the backend so it can be loaded when the repo is loaded. The name // must match `Backend::name()`. store_factories.add_backend( "jit", Box::new(|settings, store_path| Ok(Box::new(JitBackend::load(settings, store_path)?))), ); store_factories } async fn run_custom_command( ui: &mut Ui, command_helper: &CommandHelper, command: CustomCommand, ) -> Result<(), CommandError> { match command { CustomCommand::InitJit => { let wc_path = command_helper.cwd(); let settings = command_helper.settings_for_new_workspace(ui, wc_path)?.0; // Initialize a workspace with the custom backend Workspace::init_with_backend( &settings, wc_path, &|settings, store_path| Ok(Box::new(JitBackend::init(settings, store_path)?)), Signer::from_settings(&settings).map_err(WorkspaceInitError::SignInit)?, ) .await?; Ok(()) } } } fn main() -> std::process::ExitCode { CliRunner::init() .add_store_factories(create_store_factories()) .add_subcommand(run_custom_command) .run() .into() } /// A commit backend that's extremely similar to the Git backend #[derive(Debug)] struct JitBackend { inner: GitBackend, } impl JitBackend { fn init(settings: &UserSettings, store_path: &Path) -> Result { let inner = GitBackend::init_internal(settings, store_path)?; Ok(Self { inner }) } fn load(settings: &UserSettings, store_path: &Path) -> Result { let inner = GitBackend::load(settings, store_path)?; Ok(Self { inner }) } } #[async_trait] impl Backend for JitBackend { fn name(&self) -> &'static str { "jit" } fn commit_id_length(&self) -> usize { self.inner.commit_id_length() } fn change_id_length(&self) -> usize { self.inner.change_id_length() } fn root_commit_id(&self) -> &CommitId { self.inner.root_commit_id() } fn root_change_id(&self) -> &ChangeId { self.inner.root_change_id() } fn empty_tree_id(&self) -> &TreeId { self.inner.empty_tree_id() } fn concurrency(&self) -> usize { 1 } async fn read_file( &self, path: &RepoPath, id: &FileId, ) -> BackendResult>> { self.inner.read_file(path, id).await } async fn write_file( &self, path: &RepoPath, contents: &mut (dyn AsyncRead + Send + Unpin), ) -> BackendResult { self.inner.write_file(path, contents).await } async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult { self.inner.read_symlink(path, id).await } async fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult { self.inner.write_symlink(path, target).await } async fn read_copy(&self, id: &CopyId) -> BackendResult { self.inner.read_copy(id).await } async fn write_copy(&self, contents: &CopyHistory) -> BackendResult { self.inner.write_copy(contents).await } async fn get_related_copies(&self, copy_id: &CopyId) -> BackendResult> { self.inner.get_related_copies(copy_id).await } async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult { self.inner.read_tree(path, id).await } async fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult { self.inner.write_tree(path, contents).await } async fn read_commit(&self, id: &CommitId) -> BackendResult { self.inner.read_commit(id).await } async fn write_commit( &self, contents: Commit, sign_with: Option<&mut SigningFn>, ) -> BackendResult<(CommitId, Commit)> { self.inner.write_commit(contents, sign_with).await } fn get_copy_records( &self, paths: Option<&[RepoPathBuf]>, root: &CommitId, head: &CommitId, ) -> BackendResult>> { self.inner.get_copy_records(paths, root, head) } fn gc(&self, index: &dyn Index, keep_newer: SystemTime) -> BackendResult<()> { self.inner.gc(index, keep_newer) } } jj-vcs-jj-b8f7c45/cli/examples/custom-command/000077500000000000000000000000001521031206200212365ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-command/main.rs000066400000000000000000000041431521031206200225320ustar00rootroot00000000000000// Copyright 2022 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_cli::cli_util::CliRunner; use jj_cli::cli_util::CommandHelper; use jj_cli::cli_util::RevisionArg; use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; #[derive(clap::Parser, Clone, Debug)] enum CustomCommand { Frobnicate(FrobnicateArgs), } /// Frobnicate a revisions #[derive(clap::Args, Clone, Debug)] struct FrobnicateArgs { /// The revision to frobnicate #[arg(default_value = "@")] revision: RevisionArg, } async fn run_custom_command( ui: &mut Ui, command_helper: &CommandHelper, command: CustomCommand, ) -> Result<(), CommandError> { match command { CustomCommand::Frobnicate(args) => { let mut workspace_command = command_helper.workspace_helper(ui).await?; let commit = workspace_command .resolve_single_rev(ui, &args.revision) .await?; let mut tx = workspace_command.start_transaction(); let new_commit = tx .repo_mut() .rewrite_commit(&commit) .set_description("Frobnicated!") .write() .await?; tx.finish(ui, "frobnicate").await?; writeln!( ui.status(), "Frobnicated revision: {}", workspace_command.format_commit_summary(&new_commit) )?; Ok(()) } } } fn main() -> std::process::ExitCode { CliRunner::init() .add_subcommand(run_custom_command) .run() .into() } jj-vcs-jj-b8f7c45/cli/examples/custom-commit-templater/000077500000000000000000000000001521031206200231035ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-commit-templater/main.rs000066400000000000000000000147621521031206200244070ustar00rootroot00000000000000// 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 std::sync::Arc; use futures::TryStreamExt as _; use jj_cli::cli_util::CliRunner; use jj_cli::commit_templater::CommitTemplateBuildFnTable; use jj_cli::commit_templater::CommitTemplateLanguageExtension; use jj_cli::template_parser; use jj_cli::template_parser::TemplateParseError; use jj_cli::templater::TemplatePropertyExt as _; use jj_lib::backend::CommitId; use jj_lib::commit::Commit; use jj_lib::extensions_map::ExtensionsMap; use jj_lib::object_id::ObjectId as _; use jj_lib::repo::Repo; use jj_lib::revset::FunctionCallNode; use jj_lib::revset::LoweringContext; use jj_lib::revset::PartialSymbolResolver; use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetExpression; use jj_lib::revset::RevsetFilterExtension; use jj_lib::revset::RevsetFilterPredicate; use jj_lib::revset::RevsetParseError; use jj_lib::revset::RevsetResolutionError; use jj_lib::revset::SymbolResolverExtension; use jj_lib::revset::UserRevsetExpression; use once_cell::sync::OnceCell; use pollster::FutureExt as _; struct HexCounter; fn num_digits_in_id(id: &CommitId) -> i64 { let mut count = 0; for ch in id.hex().chars() { if ch.is_ascii_digit() { count += 1; } } count } fn num_char_in_id(commit: Commit, ch_match: char) -> i64 { let mut count = 0; for ch in commit.id().hex().chars() { if ch == ch_match { count += 1; } } count } #[derive(Default)] struct MostDigitsInId { count: OnceCell, } impl MostDigitsInId { fn count(&self, repo: &dyn Repo) -> i64 { *self.count.get_or_init(|| { RevsetExpression::all() .evaluate(repo) .unwrap() .stream() .try_collect::>() .block_on() .unwrap() .iter() .map(num_digits_in_id) .max() .unwrap_or(0) }) } } #[derive(Default)] struct TheDigitestResolver { cache: MostDigitsInId, } impl PartialSymbolResolver for TheDigitestResolver { fn resolve_symbol( &self, repo: &dyn Repo, symbol: &str, ) -> Result, RevsetResolutionError> { if symbol != "thedigitest" { return Ok(None); } Ok(RevsetExpression::all() .evaluate(repo) .map_err(|err| RevsetResolutionError::Other(err.into()))? .stream() .try_collect::>() .block_on() .unwrap() .iter() .find(|id| num_digits_in_id(id) == self.cache.count(repo)) .cloned()) } } struct TheDigitest; impl SymbolResolverExtension for TheDigitest { fn new_resolvers<'a>(&self, _repo: &'a dyn Repo) -> Vec> { vec![Box::::default()] } } impl CommitTemplateLanguageExtension for HexCounter { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo> { let mut table = CommitTemplateBuildFnTable::empty(); table.commit_methods.insert( "has_most_digits", |language, _diagnostics, _build_context, property, call| { call.expect_no_arguments()?; let most_digits = language .cache_extension::() .unwrap() .count(language.repo()); let out_property = property.map(move |commit| num_digits_in_id(commit.id()) == most_digits); Ok(out_property.into_dyn_wrapped()) }, ); table.commit_methods.insert( "num_digits_in_id", |_language, _diagnostics, _build_context, property, call| { call.expect_no_arguments()?; let out_property = property.map(|commit| num_digits_in_id(commit.id())); Ok(out_property.into_dyn_wrapped()) }, ); table.commit_methods.insert( "num_char_in_id", |_language, diagnostics, _build_context, property, call| { let [string_arg] = call.expect_exact_arguments()?; let char_arg = template_parser::catch_aliases( diagnostics, string_arg, |_diagnostics, arg| { let string = template_parser::expect_string_literal(arg)?; let chars: Vec<_> = string.chars().collect(); match chars[..] { [ch] => Ok(ch), _ => Err(TemplateParseError::expression( "Expected singular character argument", arg.span, )), } }, )?; let out_property = property.map(move |commit| num_char_in_id(commit, char_arg)); Ok(out_property.into_dyn_wrapped()) }, ); table } fn build_cache_extensions(&self, extensions: &mut ExtensionsMap) { extensions.insert(MostDigitsInId::default()); } } #[derive(Debug)] struct EvenDigitsFilter; impl RevsetFilterExtension for EvenDigitsFilter { fn matches_commit(&self, commit: &Commit) -> bool { num_digits_in_id(commit.id()) % 2 == 0 } } fn even_digits( _diagnostics: &mut RevsetDiagnostics, function: &FunctionCallNode, _context: &LoweringContext, ) -> Result, RevsetParseError> { function.expect_no_arguments()?; Ok(RevsetExpression::filter(RevsetFilterPredicate::Extension( Arc::new(EvenDigitsFilter), ))) } fn main() -> std::process::ExitCode { CliRunner::init() .add_symbol_resolver_extension(Box::new(TheDigitest)) .add_revset_function_extension("even_digits", even_digits) .add_commit_template_extension(Box::new(HexCounter)) .run() .into() } jj-vcs-jj-b8f7c45/cli/examples/custom-global-flag/000077500000000000000000000000001521031206200217675ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-global-flag/main.rs000066400000000000000000000022621521031206200232630ustar00rootroot00000000000000// Copyright 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 as _; use jj_cli::cli_util::CliRunner; use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; #[derive(clap::Args, Clone, Debug)] struct CustomGlobalArgs { /// Show a greeting before each command #[arg(long, global = true)] greet: bool, } fn process_before(ui: &mut Ui, custom_global_args: CustomGlobalArgs) -> Result<(), CommandError> { if custom_global_args.greet { writeln!(ui.stdout(), "Hello!")?; } Ok(()) } fn main() -> std::process::ExitCode { CliRunner::init() .add_global_args(process_before) .run() .into() } jj-vcs-jj-b8f7c45/cli/examples/custom-operation-templater/000077500000000000000000000000001521031206200236135ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-operation-templater/main.rs000066400000000000000000000065221521031206200251120ustar00rootroot00000000000000// 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 jj_cli::cli_util::CliRunner; use jj_cli::operation_templater::OperationTemplateLanguageBuildFnTable; use jj_cli::operation_templater::OperationTemplateLanguageExtension; use jj_cli::template_parser; use jj_cli::template_parser::TemplateParseError; use jj_cli::templater::TemplatePropertyExt as _; use jj_lib::extensions_map::ExtensionsMap; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::OperationId; use jj_lib::operation::Operation; struct HexCounter; fn num_digits_in_id(id: &OperationId) -> i64 { let mut count = 0; for ch in id.hex().chars() { if ch.is_ascii_digit() { count += 1; } } count } fn num_char_in_id(operation: Operation, ch_match: char) -> i64 { let mut count = 0; for ch in operation.id().hex().chars() { if ch == ch_match { count += 1; } } count } impl OperationTemplateLanguageExtension for HexCounter { fn build_fn_table(&self) -> OperationTemplateLanguageBuildFnTable { let mut table = OperationTemplateLanguageBuildFnTable::empty(); table.operation.operation_methods.insert( "num_digits_in_id", |_language, _diagnostics, _build_context, property, call| { call.expect_no_arguments()?; let out_property = property.map(|operation| num_digits_in_id(operation.id())); Ok(out_property.into_dyn_wrapped()) }, ); table.operation.operation_methods.insert( "num_char_in_id", |_language, diagnostics, _build_context, property, call| { let [string_arg] = call.expect_exact_arguments()?; let char_arg = template_parser::catch_aliases( diagnostics, string_arg, |_diagnostics, arg| { let string = template_parser::expect_string_literal(arg)?; let chars: Vec<_> = string.chars().collect(); match chars[..] { [ch] => Ok(ch), _ => Err(TemplateParseError::expression( "Expected singular character argument", arg.span, )), } }, )?; let out_property = property.map(move |operation| num_char_in_id(operation, char_arg)); Ok(out_property.into_dyn_wrapped()) }, ); table } fn build_cache_extensions(&self, _extensions: &mut ExtensionsMap) {} } fn main() -> std::process::ExitCode { CliRunner::init() .add_operation_template_extension(Box::new(HexCounter)) .run() .into() } jj-vcs-jj-b8f7c45/cli/examples/custom-working-copy/000077500000000000000000000000001521031206200222505ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/examples/custom-working-copy/main.rs000066400000000000000000000221441521031206200235450ustar00rootroot00000000000000// Copyright 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::path::Path; use std::path::PathBuf; use std::sync::Arc; use async_trait::async_trait; use itertools::Itertools as _; use jj_cli::cli_util::CliRunner; use jj_cli::cli_util::CommandHelper; use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; use jj_lib::backend::Backend; use jj_lib::commit::Commit; use jj_lib::git_backend::GitBackend; use jj_lib::local_working_copy::LocalWorkingCopy; use jj_lib::merged_tree::MergedTree; use jj_lib::op_store::OperationId; use jj_lib::ref_name::WorkspaceName; use jj_lib::ref_name::WorkspaceNameBuf; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo_path::RepoPath; use jj_lib::repo_path::RepoPathBuf; use jj_lib::settings::UserSettings; use jj_lib::signing::Signer; use jj_lib::store::Store; use jj_lib::working_copy::CheckoutError; use jj_lib::working_copy::CheckoutStats; use jj_lib::working_copy::LockedWorkingCopy; use jj_lib::working_copy::ResetError; use jj_lib::working_copy::SnapshotError; use jj_lib::working_copy::SnapshotOptions; use jj_lib::working_copy::SnapshotStats; use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopyFactory; use jj_lib::working_copy::WorkingCopyStateError; use jj_lib::workspace::WorkingCopyFactories; use jj_lib::workspace::Workspace; use jj_lib::workspace::WorkspaceInitError; #[derive(clap::Parser, Clone, Debug)] enum CustomCommand { /// Initialize a workspace using the "conflicts" working copy InitConflicts, } async fn run_custom_command( ui: &mut Ui, command_helper: &CommandHelper, command: CustomCommand, ) -> Result<(), CommandError> { match command { CustomCommand::InitConflicts => { let wc_path = command_helper.cwd(); let settings = command_helper.settings_for_new_workspace(ui, wc_path)?.0; let backend_initializer = |settings: &UserSettings, store_path: &Path| { let backend: Box = Box::new(GitBackend::init_internal(settings, store_path)?); Ok(backend) }; Workspace::init_with_factories( &settings, wc_path, &backend_initializer, Signer::from_settings(&settings).map_err(WorkspaceInitError::SignInit)?, &ReadonlyRepo::default_op_store_initializer(), &ReadonlyRepo::default_op_heads_store_initializer(), &ReadonlyRepo::default_index_store_initializer(), &ReadonlyRepo::default_submodule_store_initializer(), &ConflictsWorkingCopyFactory {}, WorkspaceName::DEFAULT.to_owned(), ) .await?; Ok(()) } } } fn main() -> std::process::ExitCode { let mut working_copy_factories = WorkingCopyFactories::new(); working_copy_factories.insert( ConflictsWorkingCopy::name().to_owned(), Box::new(ConflictsWorkingCopyFactory {}), ); CliRunner::init() .add_working_copy_factories(working_copy_factories) .add_subcommand(run_custom_command) .run() .into() } /// A working copy that adds a .conflicts file with a list of unresolved /// conflicts. /// /// Most functions below just delegate to the inner working-copy backend. The /// only interesting functions are `snapshot()` and `check_out()`. The former /// adds `.conflicts` to the .gitignores. The latter writes the `.conflicts` /// file to the working copy. struct ConflictsWorkingCopy { inner: Box, working_copy_path: PathBuf, } impl ConflictsWorkingCopy { fn name() -> &'static str { "conflicts" } fn init( store: Arc, working_copy_path: PathBuf, state_path: PathBuf, operation_id: OperationId, workspace_name: WorkspaceNameBuf, user_settings: &UserSettings, ) -> Result { let inner = LocalWorkingCopy::init( store, working_copy_path.clone(), state_path, operation_id, workspace_name, user_settings, )?; Ok(Self { inner: Box::new(inner), working_copy_path, }) } fn load( store: Arc, working_copy_path: PathBuf, state_path: PathBuf, user_settings: &UserSettings, ) -> Result { let inner = LocalWorkingCopy::load(store, working_copy_path.clone(), state_path, user_settings)?; Ok(Self { inner: Box::new(inner), working_copy_path, }) } } #[async_trait(?Send)] impl WorkingCopy for ConflictsWorkingCopy { fn name(&self) -> &str { Self::name() } fn workspace_name(&self) -> &WorkspaceName { self.inner.workspace_name() } fn operation_id(&self) -> &OperationId { self.inner.operation_id() } fn tree(&self) -> Result<&MergedTree, WorkingCopyStateError> { self.inner.tree() } fn sparse_patterns(&self) -> Result<&[RepoPathBuf], WorkingCopyStateError> { self.inner.sparse_patterns() } async fn start_mutation(&self) -> Result, WorkingCopyStateError> { let inner = self.inner.start_mutation().await?; Ok(Box::new(LockedConflictsWorkingCopy { wc_path: self.working_copy_path.clone(), inner, })) } } struct ConflictsWorkingCopyFactory {} impl WorkingCopyFactory for ConflictsWorkingCopyFactory { fn init_working_copy( &self, store: Arc, working_copy_path: PathBuf, state_path: PathBuf, operation_id: OperationId, workspace_name: WorkspaceNameBuf, settings: &UserSettings, ) -> Result, WorkingCopyStateError> { Ok(Box::new(ConflictsWorkingCopy::init( store, working_copy_path, state_path, operation_id, workspace_name, settings, )?)) } fn load_working_copy( &self, store: Arc, working_copy_path: PathBuf, state_path: PathBuf, settings: &UserSettings, ) -> Result, WorkingCopyStateError> { Ok(Box::new(ConflictsWorkingCopy::load( store, working_copy_path, state_path, settings, )?)) } } struct LockedConflictsWorkingCopy { wc_path: PathBuf, inner: Box, } #[async_trait] impl LockedWorkingCopy for LockedConflictsWorkingCopy { fn old_operation_id(&self) -> &OperationId { self.inner.old_operation_id() } fn old_tree(&self) -> &MergedTree { self.inner.old_tree() } async fn snapshot( &mut self, options: &SnapshotOptions, ) -> Result<(MergedTree, SnapshotStats), SnapshotError> { let options = SnapshotOptions { base_ignores: options.base_ignores.chain( RepoPath::root(), Path::new(""), "/.conflicts".as_bytes(), )?, ..options.clone() }; self.inner.snapshot(&options).await } async fn check_out(&mut self, commit: &Commit) -> Result { let conflicts = commit .tree() .conflicts() .map(|(path, _value)| format!("{}\n", path.as_internal_file_string())) .join(""); std::fs::write(self.wc_path.join(".conflicts"), conflicts).unwrap(); self.inner.check_out(commit).await } fn rename_workspace(&mut self, new_name: WorkspaceNameBuf) { self.inner.rename_workspace(new_name); } async fn reset(&mut self, commit: &Commit) -> Result<(), ResetError> { self.inner.reset(commit).await } async fn recover(&mut self, commit: &Commit) -> Result<(), ResetError> { self.inner.recover(commit).await } fn sparse_patterns(&self) -> Result<&[RepoPathBuf], WorkingCopyStateError> { self.inner.sparse_patterns() } async fn set_sparse_patterns( &mut self, new_sparse_patterns: Vec, ) -> Result { self.inner.set_sparse_patterns(new_sparse_patterns).await } async fn finish( self: Box, operation_id: OperationId, ) -> Result, WorkingCopyStateError> { let inner = self.inner.finish(operation_id).await?; Ok(Box::new(ConflictsWorkingCopy { inner, working_copy_path: self.wc_path, })) } } jj-vcs-jj-b8f7c45/cli/src/000077500000000000000000000000001521031206200152615ustar00rootroot00000000000000jj-vcs-jj-b8f7c45/cli/src/cleanup_guard.rs000066400000000000000000000076221521031206200204470ustar00rootroot00000000000000use std::io; use std::sync::Mutex; use std::sync::Once; use slab::Slab; use tracing::instrument; /// Contains the callbacks passed to currently-live [`CleanupGuard`]s static LIVE_GUARDS: Mutex = Mutex::new(Slab::new()); type GuardTable = Slab>; /// Prepare to run [`CleanupGuard`]s on `SIGINT`/`SIGTERM` pub fn init() { // Safety: `` ensures at most one call static CALLED: Once = Once::new(); CALLED.call_once(|| { if let Err(e) = unsafe { platform::init() } { eprintln!("couldn't register signal handler: {e}"); } }); } /// A drop guard that also runs on `SIGINT`/`SIGTERM` pub struct CleanupGuard { slot: usize, } impl CleanupGuard { /// Invoke `f` when dropped or killed by `SIGINT`/`SIGTERM` pub fn new(f: F) -> Self { let guards = &mut *LIVE_GUARDS.lock().unwrap(); Self { slot: guards.insert(Box::new(f)), } } } impl Drop for CleanupGuard { #[instrument(skip_all)] fn drop(&mut self) { let guards = &mut *LIVE_GUARDS.lock().unwrap(); let f = guards.remove(self.slot); f(); } } #[cfg(unix)] mod platform { use std::os::unix::io::IntoRawFd as _; use std::os::unix::io::RawFd; use std::os::unix::net::UnixDatagram; use std::panic::AssertUnwindSafe; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::thread; use libc::SIGINT; use libc::SIGTERM; use libc::c_int; use super::*; /// Safety: Must be called at most once pub unsafe fn init() -> io::Result<()> { unsafe { let (send, recv) = UnixDatagram::pair()?; // Spawn a background thread that waits for the signal handler to write a signal // into it thread::spawn(move || { let mut buf = [0]; let signal = match recv.recv(&mut buf) { Ok(1) => c_int::from(buf[0]), _ => unreachable!(), }; // We must hold the lock for the remainder of the process's lifetime to avoid a // race where a guard is created between `on_signal` and `raise`. let guards = &mut *LIVE_GUARDS.lock().unwrap(); if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| on_signal(guards))) { match e.downcast::() { Ok(s) => eprintln!("signal handler panicked: {s}"), Err(_) => eprintln!("signal handler panicked"), } } libc::signal(signal, libc::SIG_DFL); libc::raise(signal); }); SIGNAL_SEND = send.into_raw_fd(); libc::signal(SIGINT, handler as *const () as libc::sighandler_t); libc::signal(SIGTERM, handler as *const () as libc::sighandler_t); Ok(()) } } // Invoked on a background thread. Process exits after this returns. fn on_signal(guards: &mut GuardTable) { for guard in guards.drain() { guard(); } } unsafe extern "C" fn handler(signal: c_int) { unsafe { // Treat the second signal as instantly fatal. static SIGNALED: AtomicBool = AtomicBool::new(false); if SIGNALED.swap(true, Ordering::Relaxed) { libc::signal(signal, libc::SIG_DFL); libc::raise(signal); } let buf = [signal as u8]; libc::write(SIGNAL_SEND, buf.as_ptr().cast(), buf.len()); } } static mut SIGNAL_SEND: RawFd = 0; } #[cfg(not(unix))] mod platform { use super::*; /// Safety: this function is safe to call, but is marked as unsafe to have /// the same signature as other `init` functions in other platforms. pub unsafe fn init() -> io::Result<()> { Ok(()) } } jj-vcs-jj-b8f7c45/cli/src/cli_util.rs000066400000000000000000005330661521031206200174500ustar00rootroot00000000000000// Copyright 2022 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::borrow::Cow; use std::cell::OnceCell; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::env; use std::ffi::OsString; use std::fmt; use std::fmt::Debug; use std::io; use std::io::Write as _; use std::mem; use std::path::Path; use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; use std::sync::LazyLock; use std::time::SystemTime; use bstr::ByteVec as _; use chrono::TimeZone as _; use clap::ArgAction; use clap::ArgMatches; use clap::Command; use clap::FromArgMatches as _; use clap::builder::MapValueParser; use clap::builder::NonEmptyStringValueParser; use clap::builder::TypedValueParser as _; use clap::builder::ValueParserFactory; use clap::error::ContextKind; use clap::error::ContextValue; use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use futures::TryStreamExt as _; use futures::future::try_join_all; use indexmap::IndexMap; use indexmap::IndexSet; use indoc::indoc; use indoc::writedoc; use itertools::Itertools as _; use jj_lib::backend::BackendResult; use jj_lib::backend::ChangeId; use jj_lib::backend::CommitId; use jj_lib::backend::TreeValue; use jj_lib::commit::Commit; use jj_lib::config::ConfigGetError; use jj_lib::config::ConfigGetResultExt as _; use jj_lib::config::ConfigLayer; use jj_lib::config::ConfigMigrationRule; use jj_lib::config::ConfigNamePathBuf; use jj_lib::config::ConfigSource; use jj_lib::config::ConfigValue; use jj_lib::config::StackedConfig; use jj_lib::conflicts::ConflictMarkerStyle; use jj_lib::fileset; use jj_lib::fileset::FilesetAliasesMap; use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetExpression; use jj_lib::fileset::FilesetParseContext; use jj_lib::gitignore::GitIgnoreError; use jj_lib::gitignore::GitIgnoreFile; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::lock::FileLock; use jj_lib::matchers::Matcher; use jj_lib::matchers::NothingMatcher; use jj_lib::merge::Diff; use jj_lib::merge::MergedTreeValue; use jj_lib::merged_tree::MergedTree; use jj_lib::object_id::ObjectId as _; use jj_lib::op_heads_store; use jj_lib::op_store::OpStoreError; use jj_lib::op_store::OperationId; use jj_lib::op_store::RefTarget; use jj_lib::op_walk; use jj_lib::op_walk::OpsetEvaluationError; use jj_lib::operation::Operation; use jj_lib::ref_name::RefName; use jj_lib::ref_name::RefNameBuf; use jj_lib::ref_name::RemoteName; use jj_lib::ref_name::RemoteRefSymbol; use jj_lib::ref_name::WorkspaceName; use jj_lib::ref_name::WorkspaceNameBuf; use jj_lib::repo::CheckOutCommitError; use jj_lib::repo::EditCommitError; use jj_lib::repo::MutableRepo; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo; use jj_lib::repo::RepoLoader; use jj_lib::repo::StoreFactories; use jj_lib::repo::StoreLoadError; use jj_lib::repo::merge_factories_map; use jj_lib::repo_path::RepoPath; use jj_lib::repo_path::RepoPathBuf; use jj_lib::repo_path::RepoPathUiConverter; use jj_lib::repo_path::UiPathParseError; use jj_lib::revset; use jj_lib::revset::ResolvedRevsetExpression; use jj_lib::revset::RevsetAliasesMap; use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetExpression; use jj_lib::revset::RevsetExtensions; use jj_lib::revset::RevsetFilterPredicate; use jj_lib::revset::RevsetFunction; use jj_lib::revset::RevsetParseContext; use jj_lib::revset::RevsetStreamExt as _; use jj_lib::revset::RevsetWorkspaceContext; use jj_lib::revset::SymbolResolverExtension; use jj_lib::revset::UserRevsetExpression; use jj_lib::rewrite::restore_tree; use jj_lib::settings::HumanByteSize; use jj_lib::settings::UserSettings; use jj_lib::store::Store; use jj_lib::str_util::StringExpression; use jj_lib::str_util::StringMatcher; use jj_lib::str_util::StringPattern; use jj_lib::transaction::Transaction; use jj_lib::transaction::TransactionCommitError; use jj_lib::working_copy; use jj_lib::working_copy::CheckoutStats; use jj_lib::working_copy::LockedWorkingCopy; use jj_lib::working_copy::SnapshotOptions; use jj_lib::working_copy::SnapshotStats; use jj_lib::working_copy::UntrackedReason; use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopyFactory; use jj_lib::working_copy::WorkingCopyFreshness; use jj_lib::workspace::DefaultWorkspaceLoaderFactory; use jj_lib::workspace::LockedWorkspace; use jj_lib::workspace::WorkingCopyFactories; use jj_lib::workspace::Workspace; use jj_lib::workspace::WorkspaceLoadError; use jj_lib::workspace::WorkspaceLoader; use jj_lib::workspace::WorkspaceLoaderFactory; use jj_lib::workspace::default_working_copy_factories; use jj_lib::workspace::get_working_copy_factory; use pollster::FutureExt as _; use tracing::instrument; use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; use crate::command_error::CommandError; use crate::command_error::cli_error; use crate::command_error::config_error_with_message; use crate::command_error::handle_command_result; use crate::command_error::internal_error; use crate::command_error::internal_error_with_message; use crate::command_error::print_error_sources; use crate::command_error::print_parse_diagnostics; use crate::command_error::user_error; use crate::command_error::user_error_with_message; use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitTemplateLanguageExtension; use crate::complete; use crate::config::ConfigArgKind; use crate::config::ConfigEnv; use crate::config::RawConfig; use crate::config::config_from_environment; use crate::config::load_aliases_map; use crate::config::parse_config_args; use crate::description_util::TextEditor; use crate::diff_util; use crate::diff_util::DiffFormat; use crate::diff_util::DiffFormatArgs; use crate::diff_util::DiffRenderer; use crate::formatter::FormatRecorder; use crate::formatter::Formatter; use crate::formatter::FormatterExt as _; use crate::merge_tools::DiffEditor; use crate::merge_tools::MergeEditor; use crate::merge_tools::MergeToolConfigError; use crate::operation_templater::OperationTemplateLanguage; use crate::operation_templater::OperationTemplateLanguageExtension; use crate::revset_util; use crate::revset_util::RevsetExpressionEvaluator; use crate::revset_util::parse_union_name_patterns; use crate::template_builder; use crate::template_builder::TemplateLanguage; use crate::template_parser::TemplateAliasesMap; use crate::template_parser::TemplateDiagnostics; use crate::templater::TemplateRenderer; use crate::templater::WrapTemplateProperty; use crate::text_util; use crate::ui::ColorChoice; use crate::ui::Ui; const SHORT_CHANGE_ID_TEMPLATE_TEXT: &str = "format_short_change_id_with_change_offset(self)"; #[derive(Clone)] struct ChromeTracingFlushGuard { _inner: Option>, } impl Debug for ChromeTracingFlushGuard { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let Self { _inner } = self; f.debug_struct("ChromeTracingFlushGuard") .finish_non_exhaustive() } } /// Handle to initialize or change tracing subscription. #[derive(Clone, Debug)] pub struct TracingSubscription { reload_log_filter: tracing_subscriber::reload::Handle< tracing_subscriber::EnvFilter, tracing_subscriber::Registry, >, _chrome_tracing_flush_guard: ChromeTracingFlushGuard, } impl TracingSubscription { const ENV_VAR_NAME: &str = "JJ_LOG"; /// Initializes tracing with the default configuration. This should be /// called as early as possible. pub fn init() -> Self { let filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::ERROR.into()) .with_env_var(Self::ENV_VAR_NAME) .from_env_lossy(); let (filter, reload_log_filter) = tracing_subscriber::reload::Layer::new(filter); let (chrome_tracing_layer, chrome_tracing_flush_guard) = match std::env::var("JJ_TRACE") { Ok(filename) => { let filename = if filename.is_empty() { format!( "jj-trace-{}.json", SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(), ) } else { filename }; let include_args = std::env::var("JJ_TRACE_INCLUDE_ARGS").is_ok(); let (layer, guard) = ChromeLayerBuilder::new() .file(filename) .include_args(include_args) .build(); ( Some(layer), ChromeTracingFlushGuard { _inner: Some(Rc::new(guard)), }, ) } Err(_) => (None, ChromeTracingFlushGuard { _inner: None }), }; tracing_subscriber::registry() .with( tracing_subscriber::fmt::Layer::default() .with_writer(std::io::stderr) .with_filter(filter), ) .with(chrome_tracing_layer) .init(); Self { reload_log_filter, _chrome_tracing_flush_guard: chrome_tracing_flush_guard, } } pub fn enable_debug_logging(&self) -> Result<(), CommandError> { self.reload_log_filter .modify(|filter| { // The default is INFO. // jj-lib and jj-cli are whitelisted for DEBUG logging. // This ensures that other crates' logging doesn't show up by default. *filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) .with_env_var(Self::ENV_VAR_NAME) .from_env_lossy() .add_directive("jj_lib=debug".parse().unwrap()) .add_directive("jj_cli=debug".parse().unwrap()); }) .map_err(|err| internal_error_with_message("failed to enable debug logging", err))?; tracing::info!("debug logging enabled"); Ok(()) } } #[derive(Clone)] pub struct CommandHelper { data: Rc, } struct CommandHelperData { app: Command, cwd: PathBuf, string_args: Vec, matches: ArgMatches, global_args: GlobalArgs, config_env: ConfigEnv, config_migrations: Vec, raw_config: RawConfig, settings: UserSettings, revset_extensions: Arc, commit_template_extensions: Vec>, operation_template_extensions: Vec>, maybe_workspace_loader: Result, CommandError>, store_factories: StoreFactories, working_copy_factories: WorkingCopyFactories, workspace_loader_factory: Box, } impl CommandHelper { pub fn app(&self) -> &Command { &self.data.app } /// Canonical form of the current working directory path. /// /// A loaded `Workspace::workspace_root()` also returns a canonical path, so /// relative paths can be easily computed from these paths. pub fn cwd(&self) -> &Path { &self.data.cwd } pub fn string_args(&self) -> &Vec { &self.data.string_args } pub fn matches(&self) -> &ArgMatches { &self.data.matches } pub fn global_args(&self) -> &GlobalArgs { &self.data.global_args } pub fn config_env(&self) -> &ConfigEnv { &self.data.config_env } /// Unprocessed (or unresolved) configuration data. /// /// Use this only if the unmodified config data is needed. For example, `jj /// config set` should use this to write updated data back to file. pub fn raw_config(&self) -> &RawConfig { &self.data.raw_config } /// Settings for the current command and workspace. /// /// This may be different from the settings for new workspace created by /// e.g. `jj git init`. There may be conditional variables and repo config /// loaded for the cwd workspace. pub fn settings(&self) -> &UserSettings { &self.data.settings } /// Resolves configuration for new workspace located at the specified path. pub fn settings_for_new_workspace( &self, ui: &Ui, workspace_root: &Path, ) -> Result<(UserSettings, ConfigEnv), CommandError> { let mut config_env = self.data.config_env.clone(); let mut raw_config = self.data.raw_config.clone(); let repo_path = workspace_root.join(".jj").join("repo"); config_env.reset_repo_path(&repo_path); config_env.reload_repo_config(ui, &mut raw_config)?; config_env.reset_workspace_path(workspace_root); config_env.reload_workspace_config(ui, &mut raw_config)?; let mut config = config_env.resolve_config(&raw_config)?; // No migration messages here, which would usually be emitted before. jj_lib::config::migrate(&mut config, &self.data.config_migrations)?; Ok((self.data.settings.with_new_config(config)?, config_env)) } /// Loads text editor from the settings. pub fn text_editor(&self) -> Result { TextEditor::from_settings(self.settings()) } pub fn revset_extensions(&self) -> &Arc { &self.data.revset_extensions } /// Parses template of the given language into evaluation tree. /// /// This function also loads template aliases from the settings. Use /// `WorkspaceCommandHelper::parse_template()` if you've already /// instantiated the workspace helper. pub fn parse_template<'a, C, L>( &self, ui: &Ui, language: &L, template_text: &str, ) -> Result, CommandError> where C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized, L::Property: WrapTemplateProperty<'a, C>, { let mut diagnostics = TemplateDiagnostics::new(); let aliases = load_template_aliases(ui, self.settings().config())?; let template = template_builder::parse(language, &mut diagnostics, template_text, &aliases)?; print_parse_diagnostics(ui, "In template expression", &diagnostics)?; Ok(template) } pub fn should_commit_transaction(&self) -> bool { !self.global_args().no_integrate_operation } async fn maybe_commit_transaction( &self, tx: Transaction, description: impl Into, ) -> Result, TransactionCommitError> { let unpublished_op = tx.write(description).await?; if self.should_commit_transaction() { unpublished_op.publish().await } else { Ok(unpublished_op.leave_unpublished()) } } pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> { self.data .maybe_workspace_loader .as_deref() .map_err(Clone::clone) } fn new_workspace_loader_at( &self, workspace_root: &Path, ) -> Result, CommandError> { self.data .workspace_loader_factory .create(workspace_root) .map_err(|err| map_workspace_load_error(err, None)) } /// Loads workspace and repo, then snapshots the working copy if allowed. #[instrument(skip(self, ui))] pub async fn workspace_helper(&self, ui: &Ui) -> Result { let (workspace_command, stats) = self.workspace_helper_with_stats(ui).await?; print_snapshot_stats(ui, &stats, workspace_command.env().path_converter())?; Ok(workspace_command) } /// Loads workspace and repo, then snapshots the working copy if allowed and /// returns the SnapshotStats. /// /// Note that unless you have a good reason not to do so, you should always /// call [`print_snapshot_stats`] with the [`SnapshotStats`] returned by /// this function to present possible untracked files to the user. #[instrument(skip(self, ui))] pub async fn workspace_helper_with_stats( &self, ui: &Ui, ) -> Result<(WorkspaceCommandHelper, SnapshotStats), CommandError> { let mut workspace_command = self.workspace_helper_no_snapshot(ui).await?; let (workspace_command, stats) = match workspace_command.maybe_snapshot_impl(ui).await { Ok(stats) => (workspace_command, stats), Err(SnapshotWorkingCopyError::Command(err)) => return Err(err), Err(SnapshotWorkingCopyError::StaleWorkingCopy(err)) => { let auto_update_stale = self.settings().get_bool("snapshot.auto-update-stale")?; if !auto_update_stale { return Err(err); } // We detected the working copy was stale and the client is configured to // auto-update-stale, so let's do that now. We need to do it up here, not at a // lower level (e.g. inside snapshot_working_copy()) to avoid recursive locking // of the working copy. self.recover_stale_working_copy(ui).await? } }; Ok((workspace_command, stats)) } /// Loads workspace and repo, but never snapshots the working copy. Most /// commands should use `workspace_helper()` instead. #[instrument(skip(self, ui))] pub async fn workspace_helper_no_snapshot( &self, ui: &Ui, ) -> Result { let workspace = self.load_workspace()?; let op_head = self.resolve_operation(ui, workspace.repo_loader(), workspace.workspace_name())?; let repo = workspace.repo_loader().load_at(&op_head).await?; let mut env = self.workspace_environment(ui, &workspace)?; if let Err(err) = revset_util::try_resolve_trunk_alias(repo.as_ref(), &env.revset_parse_context()) { // The fallback can be builtin_trunk() if we're willing to support // inferred trunk forever. (#7990) let fallback = "root()"; writeln!( ui.warning_default(), "Failed to resolve `revset-aliases.trunk()`: {err}" )?; writeln!( ui.warning_no_heading(), "The `trunk()` alias is temporarily set to `{fallback}`." )?; writeln!( ui.hint_default(), "Use `jj config edit --repo` to adjust the `trunk()` alias." )?; env.revset_aliases_map .insert("trunk()", fallback, None) .expect("valid syntax"); env.reload_revset_expressions(ui)?; } WorkspaceCommandHelper::new(ui, workspace, repo, env, self.is_at_head_operation()) } pub fn get_working_copy_factory(&self) -> Result<&dyn WorkingCopyFactory, CommandError> { let loader = self.workspace_loader()?; // We convert StoreLoadError -> WorkspaceLoadError -> CommandError let factory: Result<_, WorkspaceLoadError> = get_working_copy_factory(loader, &self.data.working_copy_factories) .map_err(|e| e.into()); let factory = factory.map_err(|err| { map_workspace_load_error(err, self.data.global_args.repository.as_deref()) })?; Ok(factory) } /// Loads workspace for the current command. #[instrument(skip_all)] pub fn load_workspace(&self) -> Result { let loader = self.workspace_loader()?; loader .load( &self.data.settings, &self.data.store_factories, &self.data.working_copy_factories, ) .map_err(|err| { map_workspace_load_error(err, self.data.global_args.repository.as_deref()) }) } /// Loads workspace located at the specified path. #[instrument(skip(self, settings))] pub fn load_workspace_at( &self, workspace_root: &Path, settings: &UserSettings, ) -> Result { let loader = self.new_workspace_loader_at(workspace_root)?; loader .load( settings, &self.data.store_factories, &self.data.working_copy_factories, ) .map_err(|err| map_workspace_load_error(err, None)) } /// Note that unless you have a good reason not to do so, you should always /// call [`print_snapshot_stats`] with the [`SnapshotStats`] returned by /// this function to present possible untracked files to the user. pub async fn recover_stale_working_copy( &self, ui: &Ui, ) -> Result<(WorkspaceCommandHelper, SnapshotStats), CommandError> { let workspace = self.load_workspace()?; let op_id = workspace.working_copy().operation_id(); match workspace.repo_loader().load_operation(op_id).await { Ok(op) => { let repo = workspace.repo_loader().load_at(&op).await?; let mut workspace_command = self.for_workable_repo(ui, workspace, repo)?; workspace_command.check_working_copy_writable()?; // Snapshot the current working copy on top of the last known working-copy // operation, then merge the divergent operations. The wc_commit_id of the // merged repo wouldn't change because the old one wins, but it's probably // fine if we picked the new wc_commit_id. let stale_stats = workspace_command .snapshot_working_copy(ui) .await .map_err(|err| err.into_command_error())?; let wc_commit_id = workspace_command.get_wc_commit_id().unwrap(); let repo = workspace_command.repo().clone(); let stale_wc_commit = repo.store().get_commit_async(wc_commit_id).await?; let mut workspace_command = self.workspace_helper_no_snapshot(ui).await?; let repo = workspace_command.repo().clone(); let (mut locked_ws, desired_wc_commit) = workspace_command .unchecked_start_working_copy_mutation() .await?; match WorkingCopyFreshness::check_stale( locked_ws.locked_wc(), &desired_wc_commit, &repo, ) .await? { WorkingCopyFreshness::Fresh | WorkingCopyFreshness::Updated(_) => { drop(locked_ws); writeln!( ui.status(), "Attempted recovery, but the working copy is not stale" )?; } WorkingCopyFreshness::WorkingCopyStale | WorkingCopyFreshness::SiblingOperation => { let stats = update_stale_working_copy( locked_ws, repo.op_id().clone(), &stale_wc_commit, &desired_wc_commit, ) .await?; workspace_command.print_updated_working_copy_stats( ui, Some(&stale_wc_commit), &desired_wc_commit, &stats, )?; writeln!( ui.status(), "Updated working copy to fresh commit {}", short_commit_hash(desired_wc_commit.id()) )?; } } // There may be Git refs to import, so snapshot again. Git HEAD // will also be imported if it was updated after the working // copy became stale. The result wouldn't be ideal, but there // should be no data loss at least. let fresh_stats = workspace_command .maybe_snapshot_impl(ui) .await .map_err(|err| err.into_command_error())?; let merged_stats = { let SnapshotStats { mut untracked_paths, } = stale_stats; untracked_paths.extend(fresh_stats.untracked_paths); SnapshotStats { untracked_paths } }; Ok((workspace_command, merged_stats)) } Err(e @ OpStoreError::ObjectNotFound { .. }) => { writeln!( ui.status(), "Failed to read working copy's current operation; attempting recovery. Error \ message from read attempt: {e}" )?; let mut workspace_command = self.workspace_helper_no_snapshot(ui).await?; let stats = workspace_command .create_and_check_out_recovery_commit(ui) .await?; Ok((workspace_command, stats)) } Err(e) => Err(e.into()), } } /// Loads command environment for the given `workspace`. pub fn workspace_environment( &self, ui: &Ui, workspace: &Workspace, ) -> Result { WorkspaceCommandEnvironment::new(ui, self, workspace) } /// Returns true if the working copy to be loaded is writable, and therefore /// should usually be snapshotted. pub fn is_working_copy_writable(&self) -> bool { self.is_at_head_operation() && !self.data.global_args.ignore_working_copy } /// Returns true if the current operation is considered to be the head. pub fn is_at_head_operation(&self) -> bool { // TODO: should we accept --at-op= as the head op? or should we // make --at-op=@ imply --ignore-working-copy (i.e. not at the head.) matches!( self.data.global_args.at_operation.as_deref(), None | Some("@") ) } /// Resolves the current operation from the command-line argument. /// /// If no `--at-operation` is specified, the head operations will be /// loaded. If there are multiple heads, they'll be merged. #[instrument(skip_all)] pub fn resolve_operation( &self, ui: &Ui, repo_loader: &RepoLoader, workspace_name: &WorkspaceName, ) -> Result { if let Some(op_str) = &self.data.global_args.at_operation { Ok(op_walk::resolve_op_for_load(repo_loader, op_str).block_on()?) } else { op_heads_store::resolve_op_heads( repo_loader.op_heads_store().as_ref(), repo_loader.op_store(), async |op_heads| { writeln!( ui.status(), "Concurrent modification detected, resolving automatically.", )?; let base_repo = repo_loader.load_at(&op_heads[0]).block_on()?; // TODO: It may be helpful to print each operation we're merging here let mut tx = start_repo_transaction(&base_repo, workspace_name, &self.data.string_args); for other_op_head in op_heads.into_iter().skip(1) { tx.merge_operation(other_op_head).await?; let num_rebased = tx.repo_mut().rebase_descendants().await?; if num_rebased > 0 { writeln!( ui.status(), "Rebased {num_rebased} descendant commits onto commits rewritten \ by other operation" )?; } } Ok(tx .write("reconcile divergent operations") .await? .leave_unpublished() .operation() .clone()) }, ) .block_on() } } /// Creates helper for the repo whose view is supposed to be in sync with /// the working copy. If `--ignore-working-copy` is not specified, the /// returned helper will attempt to update the working copy. #[instrument(skip_all)] pub fn for_workable_repo( &self, ui: &Ui, workspace: Workspace, repo: Arc, ) -> Result { let env = self.workspace_environment(ui, &workspace)?; let loaded_at_head = true; WorkspaceCommandHelper::new(ui, workspace, repo, env, loaded_at_head) } } /// A ReadonlyRepo along with user-config-dependent derived data. The derived /// data is lazily loaded. struct ReadonlyUserRepo { repo: Arc, id_prefix_context: OnceCell, } impl ReadonlyUserRepo { fn new(repo: Arc) -> Self { Self { repo, id_prefix_context: OnceCell::new(), } } } /// A advanceable bookmark to satisfy the "advance-bookmarks" feature. /// /// This is a helper for `WorkspaceCommandTransaction`. It provides a /// type-safe way to separate the work of checking whether a bookmark /// can be advanced and actually advancing it. Advancing the bookmark /// never fails, but can't be done until the new `CommitId` is /// available. Splitting the work in this way also allows us to /// identify eligible bookmarks without actually moving them and /// return config errors to the user early. pub struct AdvanceableBookmark { name: RefNameBuf, old_commit_id: CommitId, } /// Parses advance-bookmarks settings into matcher. /// /// Settings are configured in the jj config.toml as lists of string matcher /// expressions for enabled and disabled bookmarks. Example: /// ```toml /// [experimental-advance-branches] /// # Enable the feature for all branches except "main". /// enabled-branches = ["*"] /// disabled-branches = ["main"] /// ``` fn load_advance_bookmarks_matcher( ui: &Ui, settings: &UserSettings, ) -> Result, CommandError> { let get_setting = |setting_key: &str| -> Result, _> { let name = ConfigNamePathBuf::from_iter(["experimental-advance-branches", setting_key]); settings.get(&name) }; // TODO: When we stabilize this feature, enabled/disabled patterns can be // combined into a single matcher expression. let enabled_names = get_setting("enabled-branches")?; let disabled_names = get_setting("disabled-branches")?; let enabled_expr = parse_union_name_patterns(ui, &enabled_names)?; let disabled_expr = parse_union_name_patterns(ui, &disabled_names)?; if enabled_names.is_empty() { Ok(None) } else { let expr = enabled_expr.intersection(disabled_expr.negated()); Ok(Some(expr.to_matcher())) } } /// Metadata and configuration loaded for a specific workspace. pub struct WorkspaceCommandEnvironment { command: CommandHelper, settings: UserSettings, fileset_aliases_map: FilesetAliasesMap, revset_aliases_map: RevsetAliasesMap, template_aliases_map: TemplateAliasesMap, default_ignored_remote: Option<&'static RemoteName>, revsets_use_glob_by_default: bool, path_converter: RepoPathUiConverter, workspace_name: WorkspaceNameBuf, immutable_heads_expression: Arc, short_prefixes_expression: Option>, conflict_marker_style: ConflictMarkerStyle, } impl WorkspaceCommandEnvironment { #[instrument(skip_all)] fn new(ui: &Ui, command: &CommandHelper, workspace: &Workspace) -> Result { let settings = workspace.settings(); let fileset_aliases_map = load_fileset_aliases(ui, settings.config())?; let revset_aliases_map = load_revset_aliases(ui, settings.config())?; let template_aliases_map = load_template_aliases(ui, settings.config())?; let default_ignored_remote = default_ignored_remote_name(workspace.repo_loader().store()); let path_converter = RepoPathUiConverter::Fs { cwd: command.cwd().to_owned(), base: workspace.workspace_root().to_owned(), }; let mut env = Self { command: command.clone(), settings: settings.clone(), fileset_aliases_map, revset_aliases_map, template_aliases_map, default_ignored_remote, revsets_use_glob_by_default: settings.get("ui.revsets-use-glob-by-default")?, path_converter, workspace_name: workspace.workspace_name().to_owned(), immutable_heads_expression: RevsetExpression::root(), short_prefixes_expression: None, conflict_marker_style: settings.get("ui.conflict-marker-style")?, }; env.reload_revset_expressions(ui)?; Ok(env) } pub(crate) fn path_converter(&self) -> &RepoPathUiConverter { &self.path_converter } pub fn workspace_name(&self) -> &WorkspaceName { &self.workspace_name } /// Parsing context for fileset expressions specified by command arguments. pub(crate) fn fileset_parse_context(&self) -> FilesetParseContext<'_> { FilesetParseContext { aliases_map: &self.fileset_aliases_map, path_converter: &self.path_converter, } } /// Parsing context for fileset expressions loaded from config files. pub(crate) fn fileset_parse_context_for_config(&self) -> FilesetParseContext<'_> { // TODO: bump MSRV to 1.91.0 to leverage const PathBuf::new() static ROOT_PATH_CONVERTER: LazyLock = LazyLock::new(|| RepoPathUiConverter::Fs { cwd: PathBuf::new(), base: PathBuf::new(), }); FilesetParseContext { aliases_map: &self.fileset_aliases_map, path_converter: &ROOT_PATH_CONVERTER, } } pub(crate) fn revset_parse_context(&self) -> RevsetParseContext<'_> { let workspace_context = RevsetWorkspaceContext { path_converter: &self.path_converter, workspace_name: &self.workspace_name, }; let now = if let Some(timestamp) = self.settings.commit_timestamp() { chrono::Local .timestamp_millis_opt(timestamp.timestamp.0) .unwrap() } else { chrono::Local::now() }; RevsetParseContext { aliases_map: &self.revset_aliases_map, local_variables: HashMap::new(), user_email: self.settings.user_email(), date_pattern_context: now.into(), default_ignored_remote: self.default_ignored_remote, fileset_aliases_map: &self.fileset_aliases_map, use_glob_by_default: self.revsets_use_glob_by_default, extensions: self.command.revset_extensions(), workspace: Some(workspace_context), } } /// Creates fresh new context which manages cache of short commit/change ID /// prefixes. New context should be created per repo view (or operation.) pub fn new_id_prefix_context(&self) -> IdPrefixContext { let context = IdPrefixContext::new(self.command.revset_extensions().clone()); match &self.short_prefixes_expression { None => context, Some(expression) => context.disambiguate_within(expression.clone()), } } /// Updates parsed revset expressions. fn reload_revset_expressions(&mut self, ui: &Ui) -> Result<(), CommandError> { self.immutable_heads_expression = self.load_immutable_heads_expression(ui)?; self.short_prefixes_expression = self.load_short_prefixes_expression(ui)?; Ok(()) } /// User-configured expression defining the immutable set. pub fn immutable_expression(&self) -> Arc { // Negated ancestors expression `~::( | root())` is slightly // easier to optimize than negated union `~(:: | root())`. self.immutable_heads_expression.ancestors() } /// User-configured expression defining the heads of the immutable set. pub fn immutable_heads_expression(&self) -> &Arc { &self.immutable_heads_expression } /// User-configured conflict marker style for materializing conflicts pub fn conflict_marker_style(&self) -> ConflictMarkerStyle { self.conflict_marker_style } fn load_immutable_heads_expression( &self, ui: &Ui, ) -> Result, CommandError> { let mut diagnostics = RevsetDiagnostics::new(); let expression = revset_util::parse_immutable_heads_expression( &mut diagnostics, &self.revset_parse_context(), ) .map_err(|e| config_error_with_message("Invalid `revset-aliases.immutable_heads()`", e))?; print_parse_diagnostics(ui, "In `revset-aliases.immutable_heads()`", &diagnostics)?; Ok(expression) } fn load_short_prefixes_expression( &self, ui: &Ui, ) -> Result>, CommandError> { let revset_string = self .settings .get_string("revsets.short-prefixes") .optional()? .map_or_else(|| self.settings.get_string("revsets.log"), Ok)?; if revset_string.is_empty() { Ok(None) } else { let mut diagnostics = RevsetDiagnostics::new(); let expression = revset::parse( &mut diagnostics, &revset_string, &self.revset_parse_context(), ) .map_err(|err| config_error_with_message("Invalid `revsets.short-prefixes`", err))?; print_parse_diagnostics(ui, "In `revsets.short-prefixes`", &diagnostics)?; Ok(Some(expression)) } } /// Returns first immutable commit. async fn find_immutable_commit( &self, repo: &dyn Repo, to_rewrite_expr: &Arc, ) -> Result, CommandError> { let immutable_expression = if self.command.global_args().ignore_immutable { UserRevsetExpression::root() } else { self.immutable_expression() }; // Not using self.id_prefix_context() because the disambiguation data // must not be calculated and cached against arbitrary repo. It's also // unlikely that the immutable expression contains short hashes. let id_prefix_context = IdPrefixContext::new(self.command.revset_extensions().clone()); let immutable_expr = RevsetExpressionEvaluator::new( repo, self.command.revset_extensions().clone(), &id_prefix_context, immutable_expression, ) .resolve() .map_err(|e| config_error_with_message("Invalid `revset-aliases.immutable_heads()`", e))?; let mut commit_id_iter = immutable_expr .intersection(to_rewrite_expr) .evaluate(repo)? .stream(); Ok(commit_id_iter.try_next().await?) } pub fn template_aliases_map(&self) -> &TemplateAliasesMap { &self.template_aliases_map } /// Parses template of the given language into evaluation tree. pub fn parse_template<'a, C, L>( &self, ui: &Ui, language: &L, template_text: &str, ) -> Result, CommandError> where C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized, L::Property: WrapTemplateProperty<'a, C>, { let mut diagnostics = TemplateDiagnostics::new(); let template = template_builder::parse( language, &mut diagnostics, template_text, &self.template_aliases_map, )?; print_parse_diagnostics(ui, "In template expression", &diagnostics)?; Ok(template) } /// Creates commit template language environment for this workspace and the /// given `repo`. pub fn commit_template_language<'a>( &'a self, repo: &'a dyn Repo, id_prefix_context: &'a IdPrefixContext, ) -> CommitTemplateLanguage<'a> { CommitTemplateLanguage::new( repo, &self.path_converter, &self.workspace_name, self.revset_parse_context(), id_prefix_context, self.immutable_expression(), self.conflict_marker_style, &self.command.data.commit_template_extensions, ) } pub fn operation_template_extensions(&self) -> &[Arc] { &self.command.data.operation_template_extensions } } /// A token that holds a lock for git import/export operations in colocated /// repositories. For non-colocated repos, this is an empty token (no actual /// lock held). The lock is automatically released when this token is dropped. pub struct GitImportExportLock { _lock: Option, } /// Provides utilities for writing a command that works on a [`Workspace`] /// (which most commands do). pub struct WorkspaceCommandHelper { workspace: Workspace, user_repo: ReadonlyUserRepo, env: WorkspaceCommandEnvironment, // TODO: Parsed template can be cached if it doesn't capture 'repo lifetime commit_summary_template_text: String, op_summary_template_text: String, may_snapshot_working_copy: bool, may_update_working_copy: bool, working_copy_shared_with_git: bool, } enum SnapshotWorkingCopyError { Command(CommandError), StaleWorkingCopy(CommandError), } impl SnapshotWorkingCopyError { fn into_command_error(self) -> CommandError { match self { Self::Command(err) => err, Self::StaleWorkingCopy(err) => err, } } } fn snapshot_command_error(err: E) -> SnapshotWorkingCopyError where E: Into, { SnapshotWorkingCopyError::Command(err.into()) } impl WorkspaceCommandHelper { #[instrument(skip_all)] fn new( ui: &Ui, workspace: Workspace, repo: Arc, env: WorkspaceCommandEnvironment, loaded_at_head: bool, ) -> Result { let settings = workspace.settings(); let commit_summary_template_text = settings.get_string("templates.commit_summary")?; let op_summary_template_text = settings.get_string("templates.op_summary")?; let may_snapshot_working_copy = loaded_at_head && !env.command.global_args().ignore_working_copy; let may_update_working_copy = may_snapshot_working_copy && env.command.should_commit_transaction(); let working_copy_shared_with_git = crate::git_util::is_colocated_git_workspace(&workspace, &repo); let helper = Self { workspace, user_repo: ReadonlyUserRepo::new(repo), env, commit_summary_template_text, op_summary_template_text, may_snapshot_working_copy, may_update_working_copy, working_copy_shared_with_git, }; // Parse commit_summary template early to report error before starting // mutable operation. helper.parse_operation_template(ui, &helper.op_summary_template_text)?; helper.parse_commit_template(ui, &helper.commit_summary_template_text)?; helper.parse_commit_template(ui, SHORT_CHANGE_ID_TEMPLATE_TEXT)?; Ok(helper) } /// Settings for this workspace. pub fn settings(&self) -> &UserSettings { self.workspace.settings() } pub fn check_working_copy_writable(&self) -> Result<(), CommandError> { if self.may_update_working_copy { Ok(()) } else { let hint = if self.env.command.global_args().ignore_working_copy { "Don't use --ignore-working-copy." } else if self.env.command.global_args().no_integrate_operation { "Don't use --no-integrate-operation." } else { "Don't use --at-op." }; Err(user_error("This command must be able to update the working copy.").hinted(hint)) } } /// Acquires a lock for git import/export operations if the workspace is /// colocated with Git. Returns a token that can be passed to functions /// that need to import from or export to Git. For non-colocated repos, /// returns a token with no lock inside. fn lock_git_import_export(&self) -> Result { let lock = if self.working_copy_shared_with_git { let lock_path = self.workspace.repo_path().join("git_import_export.lock"); Some(FileLock::lock(lock_path.clone()).map_err(|err| { user_error_with_message("Failed to take lock for Git import/export", err) })?) } else { None }; Ok(GitImportExportLock { _lock: lock }) } /// Note that unless you have a good reason not to do so, you should always /// call [`print_snapshot_stats`] with the [`SnapshotStats`] returned by /// this function to present possible untracked files to the user. #[instrument(skip_all)] async fn maybe_snapshot_impl( &mut self, ui: &Ui, ) -> Result { if !self.may_snapshot_working_copy { return Ok(SnapshotStats::default()); } // Acquire git import/export lock once for the entire import/snapshot/export // cycle. This prevents races with other processes during Git HEAD and // refs import/export. #[cfg_attr(not(feature = "git"), allow(unused_variables))] let git_import_export_lock = self .lock_git_import_export() .map_err(snapshot_command_error)?; // Reload at current head to avoid creating divergent operations if another // process committed an operation while we were waiting for the lock. if self.working_copy_shared_with_git { let repo = self.repo().clone(); let op_heads_store = repo.loader().op_heads_store(); let op_heads = op_heads_store .get_op_heads() .await .map_err(snapshot_command_error)?; if std::slice::from_ref(repo.op_id()) != op_heads { let op = self .env .command .resolve_operation(ui, repo.loader(), self.workspace_name()) .map_err(snapshot_command_error)?; let current_repo = repo .loader() .load_at(&op) .await .map_err(snapshot_command_error)?; self.user_repo = ReadonlyUserRepo::new(current_repo); } } #[cfg(feature = "git")] if self.working_copy_shared_with_git { self.import_git_head(ui, &git_import_export_lock) .await .map_err(snapshot_command_error)?; } // Because the Git refs (except HEAD) aren't imported yet, the ref // pointing to the new working-copy commit might not be exported. // In that situation, the ref would be conflicted anyway, so export // failure is okay. let stats = self.snapshot_working_copy(ui).await?; // import_git_refs() can rebase the working-copy commit. #[cfg(feature = "git")] if self.working_copy_shared_with_git { self.import_git_refs(ui, &git_import_export_lock) .await .map_err(snapshot_command_error)?; } Ok(stats) } /// Snapshots the working copy if allowed, and imports Git refs if the /// working copy is collocated with Git. /// /// Returns whether a snapshot was taken. #[instrument(skip_all)] pub async fn maybe_snapshot(&mut self, ui: &Ui) -> Result { let op_id_before = self.repo().op_id().clone(); let stats = self .maybe_snapshot_impl(ui) .await .map_err(|err| err.into_command_error())?; print_snapshot_stats(ui, &stats, self.env().path_converter())?; let op_id_after = self.repo().op_id(); Ok(op_id_before != *op_id_after) } /// Imports new HEAD from the colocated Git repo. /// /// If the Git HEAD has changed, this function checks out the new Git HEAD. /// The old working-copy commit will be abandoned if it's discardable. The /// working-copy state will be reset to point to the new Git HEAD. The /// working-copy contents won't be updated. #[cfg(feature = "git")] #[instrument(skip_all)] async fn import_git_head( &mut self, ui: &Ui, git_import_export_lock: &GitImportExportLock, ) -> Result<(), CommandError> { assert!(self.may_snapshot_working_copy); let mut tx = self.start_transaction(); jj_lib::git::import_head(tx.repo_mut()).await?; if !tx.repo().has_changes() { return Ok(()); } let mut tx = tx.into_inner(); let old_git_head = self.repo().view().git_head().clone(); let new_git_head = tx.repo().view().git_head().clone(); if let Some(new_git_head_id) = new_git_head.as_normal() { let workspace_name = self.workspace_name().to_owned(); let new_git_head_commit = tx.repo().store().get_commit_async(new_git_head_id).await?; let wc_commit = tx .repo_mut() .check_out(workspace_name, &new_git_head_commit) .await?; let mut locked_ws = self.workspace.start_working_copy_mutation().await?; // The working copy was presumably updated by the git command that updated // HEAD, so we just need to reset our working copy // state to it without updating working copy files. locked_ws.locked_wc().reset(&wc_commit).await?; tx.repo_mut().rebase_descendants().await?; self.user_repo = ReadonlyUserRepo::new( self.env .command .maybe_commit_transaction(tx, "import git head") .await?, ); if self.env.command.should_commit_transaction() { locked_ws .finish(self.user_repo.repo.op_id().clone()) .await?; } if old_git_head.is_present() { writeln!( ui.status(), "Reset the working copy parent to the new Git HEAD." )?; } else { // Don't print verbose message on initial checkout. } } else { // Unlikely, but the HEAD ref got deleted by git? self.finish_transaction(ui, tx, "import git head", git_import_export_lock) .await?; } Ok(()) } /// Imports branches and tags from the underlying Git repo, abandons old /// bookmarks. /// /// If the working-copy branch is rebased, and if update is allowed, the /// new working-copy commit will be checked out. /// /// This function does not import the Git HEAD, but the HEAD may be reset to /// the working copy parent if the repository is colocated. #[cfg(feature = "git")] #[instrument(skip_all)] async fn import_git_refs( &mut self, ui: &Ui, git_import_export_lock: &GitImportExportLock, ) -> Result<(), CommandError> { use jj_lib::git; let git_settings = git::GitSettings::from_settings(self.settings())?; let remote_settings = self.settings().remote_settings()?; let import_options = crate::git_util::load_git_import_options(ui, &git_settings, &remote_settings)?; let mut tx = self.start_transaction(); let stats = git::import_refs(tx.repo_mut(), &import_options).await?; crate::git_util::print_git_import_stats_summary(ui, &stats)?; if !tx.repo().has_changes() { return Ok(()); } let mut tx = tx.into_inner(); // Rebase here to show slightly different status message. let num_rebased = tx.repo_mut().rebase_descendants().await?; if num_rebased > 0 { writeln!( ui.status(), "Rebased {num_rebased} descendant commits off of commits rewritten from git" )?; } self.finish_transaction(ui, tx, "import git refs", git_import_export_lock) .await?; writeln!( ui.status(), "Done importing changes from the underlying Git repo." )?; Ok(()) } pub fn repo(&self) -> &Arc { &self.user_repo.repo } pub fn repo_path(&self) -> &Path { self.workspace.repo_path() } pub fn workspace(&self) -> &Workspace { &self.workspace } pub fn working_copy(&self) -> &dyn WorkingCopy { self.workspace.working_copy() } pub fn env(&self) -> &WorkspaceCommandEnvironment { &self.env } pub async fn unchecked_start_working_copy_mutation( &mut self, ) -> Result<(LockedWorkspace<'_>, Commit), CommandError> { self.check_working_copy_writable()?; let wc_commit = if let Some(wc_commit_id) = self.get_wc_commit_id() { self.repo().store().get_commit_async(wc_commit_id).await? } else { return Err(user_error("Nothing checked out in this workspace")); }; let locked_ws = self.workspace.start_working_copy_mutation().await?; Ok((locked_ws, wc_commit)) } pub async fn start_working_copy_mutation( &mut self, ) -> Result<(LockedWorkspace<'_>, Commit), CommandError> { let (mut locked_ws, wc_commit) = self.unchecked_start_working_copy_mutation().await?; if wc_commit.tree().tree_ids_and_labels() != locked_ws.locked_wc().old_tree().tree_ids_and_labels() { return Err(user_error("Concurrent working copy operation. Try again.")); } Ok((locked_ws, wc_commit)) } async fn create_and_check_out_recovery_commit( &mut self, ui: &Ui, ) -> Result { self.check_working_copy_writable()?; let workspace_name = self.workspace_name().to_owned(); let mut locked_ws = self.workspace.start_working_copy_mutation().await?; let (repo, new_commit) = working_copy::create_and_check_out_recovery_commit( locked_ws.locked_wc(), &self.user_repo.repo, workspace_name, "RECOVERY COMMIT FROM `jj workspace update-stale` This commit contains changes that were written to the working copy by an operation that was subsequently lost (or was at least unavailable when you ran `jj workspace update-stale`). Because the operation was lost, we don't know what the parent commits are supposed to be. That means that the diff compared to the current parents may contain changes from multiple commits. ", ) .await?; writeln!( ui.status(), "Created and checked out recovery commit {}", short_commit_hash(new_commit.id()) )?; locked_ws.finish(repo.op_id().clone()).await?; self.user_repo = ReadonlyUserRepo::new(repo); self.maybe_snapshot_impl(ui) .await .map_err(|err| err.into_command_error()) } pub fn workspace_root(&self) -> &Path { self.workspace.workspace_root() } pub fn workspace_name(&self) -> &WorkspaceName { self.workspace.workspace_name() } pub fn get_wc_commit_id(&self) -> Option<&CommitId> { self.repo().view().get_wc_commit_id(self.workspace_name()) } pub fn working_copy_shared_with_git(&self) -> bool { self.working_copy_shared_with_git } pub fn format_file_path(&self, file: &RepoPath) -> String { self.path_converter().format_file_path(file) } /// Parses a path relative to cwd into a RepoPath, which is relative to the /// workspace root. pub fn parse_file_path(&self, input: &str) -> Result { self.path_converter().parse_file_path(input) } /// Parses the given strings as file patterns. pub fn parse_file_patterns( &self, ui: &Ui, values: &[String], ) -> Result { // TODO: This function might be superseded by parse_union_filesets(), // but it would be weird if parse_union_*() had a special case for the // empty arguments. if values.is_empty() { Ok(FilesetExpression::all()) } else { self.parse_union_filesets(ui, values) } } /// Parses the given fileset expressions and concatenates them all. pub fn parse_union_filesets( &self, ui: &Ui, file_args: &[String], // TODO: introduce FileArg newtype? ) -> Result { let mut diagnostics = FilesetDiagnostics::new(); let context = self.env.fileset_parse_context(); let expressions: Vec<_> = file_args .iter() .map(|arg| fileset::parse_maybe_bare(&mut diagnostics, arg, &context)) .try_collect()?; print_parse_diagnostics(ui, "In fileset expression", &diagnostics)?; Ok(FilesetExpression::union_all(expressions)) } pub fn auto_tracking_matcher(&self, ui: &Ui) -> Result, CommandError> { let mut diagnostics = FilesetDiagnostics::new(); let pattern = self.settings().get_string("snapshot.auto-track")?; let context = self.env.fileset_parse_context_for_config(); let expression = fileset::parse(&mut diagnostics, &pattern, &context)?; print_parse_diagnostics(ui, "In `snapshot.auto-track`", &diagnostics)?; Ok(expression.to_matcher()) } pub fn snapshot_options_with_start_tracking_matcher<'a>( &self, start_tracking_matcher: &'a dyn Matcher, ) -> Result, CommandError> { let base_ignores = self.base_ignores()?; let HumanByteSize(mut max_new_file_size) = self .settings() .get_value_with("snapshot.max-new-file-size", TryInto::try_into)?; if max_new_file_size == 0 { max_new_file_size = u64::MAX; } Ok(SnapshotOptions { base_ignores, progress: None, start_tracking_matcher, force_tracking_matcher: &NothingMatcher, max_new_file_size, }) } pub(crate) fn path_converter(&self) -> &RepoPathUiConverter { self.env.path_converter() } #[cfg(not(feature = "git"))] pub fn base_ignores(&self) -> Result, GitIgnoreError> { Ok(GitIgnoreFile::empty()) } #[cfg(feature = "git")] #[instrument(skip_all)] pub fn base_ignores(&self) -> Result, GitIgnoreError> { let get_excludes_file_path = |config: &gix::config::File| -> Option { // TODO: maybe use path() and interpolate(), which can process non-utf-8 // path on Unix. if let Some(value) = config.string("core.excludesFile") { let path = str::from_utf8(&value) .ok() .map(jj_lib::file_util::expand_home_path)?; // The configured path is usually absolute, but if it's relative, // the "git" command would read the file at the work-tree directory. Some(self.workspace_root().join(path)) } else { xdg_config_home().map(|x| x.join("git").join("ignore")) } }; fn xdg_config_home() -> Option { if let Ok(x) = std::env::var("XDG_CONFIG_HOME") && !x.is_empty() { return Some(PathBuf::from(x)); } etcetera::home_dir().ok().map(|home| home.join(".config")) } let mut git_ignores = GitIgnoreFile::empty(); if let Ok(git_backend) = jj_lib::git::get_git_backend(self.repo().store()) { let git_repo = git_backend.git_repo(); if let Some(excludes_file_path) = get_excludes_file_path(&git_repo.config_snapshot()) { git_ignores = git_ignores.chain_with_file(RepoPath::root(), excludes_file_path)?; } git_ignores = git_ignores.chain_with_file( RepoPath::root(), git_backend.git_repo_path().join("info").join("exclude"), )?; } else if let Ok(git_config) = gix::config::File::from_globals() && let Some(excludes_file_path) = get_excludes_file_path(&git_config) { git_ignores = git_ignores.chain_with_file(RepoPath::root(), excludes_file_path)?; } Ok(git_ignores) } /// Creates textual diff renderer of the specified `formats`. pub fn diff_renderer(&self, formats: Vec) -> DiffRenderer<'_> { DiffRenderer::new( self.repo().as_ref(), self.path_converter(), self.env.conflict_marker_style(), formats, ) } /// Loads textual diff renderer from the settings and command arguments. pub fn diff_renderer_for( &self, args: &DiffFormatArgs, ) -> Result, CommandError> { let formats = diff_util::diff_formats_for(self.settings(), args)?; Ok(self.diff_renderer(formats)) } /// Loads textual diff renderer from the settings and log-like command /// arguments. Returns `Ok(None)` if there are no command arguments that /// enable patch output. pub fn diff_renderer_for_log( &self, args: &DiffFormatArgs, patch: bool, ) -> Result>, CommandError> { let formats = diff_util::diff_formats_for_log(self.settings(), args, patch)?; Ok((!formats.is_empty()).then(|| self.diff_renderer(formats))) } /// Loads diff editor from the settings. /// /// If the `tool_name` isn't specified, the default editor will be returned. pub fn diff_editor( &self, ui: &Ui, tool_name: Option<&str>, ) -> Result { let base_ignores = self.base_ignores()?; let conflict_marker_style = self.env.conflict_marker_style(); if let Some(name) = tool_name { Ok(DiffEditor::with_name( name, self.settings(), base_ignores, conflict_marker_style, )?) } else { Ok(DiffEditor::from_settings( ui, self.settings(), base_ignores, conflict_marker_style, )?) } } /// Conditionally loads diff editor from the settings. /// /// If the `tool_name` is specified, interactive session is implied. pub fn diff_selector( &self, ui: &Ui, tool_name: Option<&str>, force_interactive: bool, ) -> Result { if tool_name.is_some() || force_interactive { Ok(DiffSelector::Interactive(self.diff_editor(ui, tool_name)?)) } else { Ok(DiffSelector::NonInteractive) } } /// Loads 3-way merge editor from the settings. /// /// If the `tool_name` isn't specified, the default editor will be returned. pub fn merge_editor( &self, ui: &Ui, tool_name: Option<&str>, ) -> Result { let conflict_marker_style = self.env.conflict_marker_style(); if let Some(name) = tool_name { MergeEditor::with_name( name, self.settings(), self.path_converter().clone(), conflict_marker_style, ) } else { MergeEditor::from_settings( ui, self.settings(), self.path_converter().clone(), conflict_marker_style, ) } } /// Loads text editor from the settings. pub fn text_editor(&self) -> Result { TextEditor::from_settings(self.settings()) } pub fn resolve_single_op(&self, op_str: &str) -> Result { op_walk::resolve_op_with_repo(self.repo(), op_str).block_on() } /// Resolve a revset to a single revision. Return an error if the revset is /// empty or has multiple revisions. pub async fn resolve_single_rev( &self, ui: &Ui, revision_arg: &RevisionArg, ) -> Result { let expression = self.parse_revset(ui, revision_arg)?; revset_util::evaluate_revset_to_single_commit(revision_arg.as_ref(), &expression, || { self.commit_summary_template() }) .await } /// Evaluates revset expressions to set of commit IDs. The /// returned set preserves the order of the input expressions. pub async fn resolve_revsets_ordered( &self, ui: &Ui, revision_args: &[RevisionArg], ) -> Result, CommandError> { let mut all_commits = IndexSet::new(); for revision_arg in revision_args { let expression = self.parse_revset(ui, revision_arg)?; let mut stream = expression.evaluate_to_commit_ids()?; while let Some(commit_id) = stream.try_next().await? { all_commits.insert(commit_id); } } Ok(all_commits) } /// Evaluates revset expressions to non-empty set of commit IDs. The /// returned set preserves the order of the input expressions. pub async fn resolve_some_revsets( &self, ui: &Ui, revision_args: &[RevisionArg], ) -> Result, CommandError> { let all_commits = self.resolve_revsets_ordered(ui, revision_args).await?; if all_commits.is_empty() { Err(user_error("Empty revision set")) } else { Ok(all_commits) } } pub fn parse_revset( &self, ui: &Ui, revision_arg: &RevisionArg, ) -> Result, CommandError> { let mut diagnostics = RevsetDiagnostics::new(); let context = self.env.revset_parse_context(); let expression = revset::parse(&mut diagnostics, revision_arg.as_ref(), &context)?; print_parse_diagnostics(ui, "In revset expression", &diagnostics)?; Ok(self.attach_revset_evaluator(expression)) } /// Parses the given revset expressions and concatenates them all. pub fn parse_union_revsets( &self, ui: &Ui, revision_args: &[RevisionArg], ) -> Result, CommandError> { let mut diagnostics = RevsetDiagnostics::new(); let context = self.env.revset_parse_context(); let expressions: Vec<_> = revision_args .iter() .map(|arg| revset::parse(&mut diagnostics, arg.as_ref(), &context)) .try_collect()?; print_parse_diagnostics(ui, "In revset expression", &diagnostics)?; let expression = RevsetExpression::union_all(&expressions); Ok(self.attach_revset_evaluator(expression)) } pub fn attach_revset_evaluator( &self, expression: Arc, ) -> RevsetExpressionEvaluator<'_> { RevsetExpressionEvaluator::new( self.repo().as_ref(), self.env.command.revset_extensions().clone(), self.id_prefix_context(), expression, ) } pub fn id_prefix_context(&self) -> &IdPrefixContext { self.user_repo .id_prefix_context .get_or_init(|| self.env.new_id_prefix_context()) } /// Parses template of the given language into evaluation tree. pub fn parse_template<'a, C, L>( &self, ui: &Ui, language: &L, template_text: &str, ) -> Result, CommandError> where C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized, L::Property: WrapTemplateProperty<'a, C>, { self.env.parse_template(ui, language, template_text) } /// Parses template that is validated by `Self::new()`. fn reparse_valid_template<'a, C, L>( &self, language: &L, template_text: &str, ) -> TemplateRenderer<'a, C> where C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized, L::Property: WrapTemplateProperty<'a, C>, { template_builder::parse( language, &mut TemplateDiagnostics::new(), template_text, &self.env.template_aliases_map, ) .expect("parse error should be confined by WorkspaceCommandHelper::new()") } /// Parses commit template into evaluation tree. pub fn parse_commit_template( &self, ui: &Ui, template_text: &str, ) -> Result, CommandError> { let language = self.commit_template_language(); self.parse_template(ui, &language, template_text) } /// Parses commit template into evaluation tree. pub fn parse_operation_template( &self, ui: &Ui, template_text: &str, ) -> Result, CommandError> { let language = self.operation_template_language(); self.parse_template(ui, &language, template_text) } /// Creates commit template language environment for this workspace. pub fn commit_template_language(&self) -> CommitTemplateLanguage<'_> { self.env .commit_template_language(self.repo().as_ref(), self.id_prefix_context()) } /// Creates operation template language environment for this workspace. pub fn operation_template_language(&self) -> OperationTemplateLanguage { OperationTemplateLanguage::new( self.workspace.repo_loader(), Some(self.repo().op_id()), self.env.operation_template_extensions(), ) } /// Template for one-line summary of a commit. pub fn commit_summary_template(&self) -> TemplateRenderer<'_, Commit> { let language = self.commit_template_language(); self.reparse_valid_template(&language, &self.commit_summary_template_text) .labeled(["commit"]) } /// Template for one-line summary of an operation. pub fn operation_summary_template(&self) -> TemplateRenderer<'_, Operation> { let language = self.operation_template_language(); self.reparse_valid_template(&language, &self.op_summary_template_text) .labeled(["operation"]) } pub fn short_change_id_template(&self) -> TemplateRenderer<'_, Commit> { let language = self.commit_template_language(); self.reparse_valid_template(&language, SHORT_CHANGE_ID_TEMPLATE_TEXT) .labeled(["commit"]) } /// Returns one-line summary of the given `commit`. /// /// Use `write_commit_summary()` to get colorized output. Use /// `commit_summary_template()` if you have many commits to process. pub fn format_commit_summary(&self, commit: &Commit) -> String { let output = self.commit_summary_template().format_plain_text(commit); output.into_string_lossy() } /// Writes one-line summary of the given `commit`. /// /// Use `commit_summary_template()` if you have many commits to process. #[instrument(skip_all)] pub fn write_commit_summary( &self, formatter: &mut dyn Formatter, commit: &Commit, ) -> std::io::Result<()> { self.commit_summary_template().format(commit, formatter) } pub async fn check_rewritable<'a>( &self, commits: impl IntoIterator, ) -> Result<(), CommandError> { let commit_ids = commits.into_iter().cloned().collect_vec(); let to_rewrite_expr = RevsetExpression::commits(commit_ids); self.check_rewritable_expr(&to_rewrite_expr).await } pub async fn check_rewritable_expr( &self, to_rewrite_expr: &Arc, ) -> Result<(), CommandError> { let repo = self.repo().as_ref(); let Some(commit_id) = self .env .find_immutable_commit(repo, to_rewrite_expr) .await? else { return Ok(()); }; let error = if &commit_id == repo.store().root_commit_id() { user_error(format!("The root commit {commit_id:.12} is immutable")) } else { let mut error = user_error(format!("Commit {commit_id:.12} is immutable")); let commit = repo.store().get_commit_async(&commit_id).await?; error.add_formatted_hint_with(|formatter| { write!(formatter, "Could not modify commit: ")?; self.write_commit_summary(formatter, &commit)?; Ok(()) }); error.add_hint("Immutable commits are used to protect shared history."); error.add_hint(indoc::indoc! {" For more information, see: - https://docs.jj-vcs.dev/latest/config/#set-of-immutable-commits - `jj help -k config`, \"Set of immutable commits\""}); // Not using self.id_prefix_context() for consistency with // find_immutable_commit(). let id_prefix_context = IdPrefixContext::new(self.env.command.revset_extensions().clone()); let (lower_bound, upper_bound) = RevsetExpressionEvaluator::new( repo, self.env.command.revset_extensions().clone(), &id_prefix_context, self.env.immutable_expression(), ) .resolve()? .intersection(&to_rewrite_expr.descendants()) .evaluate(repo)? .count_estimate()?; let exact = upper_bound == Some(lower_bound); let or_more = if exact { "" } else { " or more" }; error.add_hint(format!( "This operation would rewrite {lower_bound}{or_more} immutable commits." )); error }; Err(error) } #[instrument(skip_all)] async fn snapshot_working_copy( &mut self, ui: &Ui, ) -> Result { let workspace_name = self.workspace_name().to_owned(); let repo = self.repo().clone(); let auto_tracking_matcher = self .auto_tracking_matcher(ui) .map_err(snapshot_command_error)?; let options = self .snapshot_options_with_start_tracking_matcher(&auto_tracking_matcher) .map_err(snapshot_command_error)?; // Compare working-copy tree and operation with repo's, and reload as needed. let mut locked_ws = self .workspace .start_working_copy_mutation() .await .map_err(snapshot_command_error)?; let Some((repo, wc_commit)) = handle_stale_working_copy(locked_ws.locked_wc(), repo, &workspace_name).await? else { // If the workspace has been deleted, it's unclear what to do, so we just skip // committing the working copy. return Ok(SnapshotStats::default()); }; self.user_repo = ReadonlyUserRepo::new(repo); let (new_tree, stats) = { let mut options = options; let progress = crate::progress::snapshot_progress(ui); options.progress = progress.as_ref().map(|x| x as _); locked_ws .locked_wc() .snapshot(&options) .await .map_err(snapshot_command_error)? }; if new_tree.tree_ids_and_labels() != wc_commit.tree().tree_ids_and_labels() { let mut tx = start_repo_transaction( &self.user_repo.repo, &workspace_name, self.env.command.string_args(), ); tx.set_is_snapshot(true); let mut_repo = tx.repo_mut(); let commit = mut_repo .rewrite_commit(&wc_commit) .set_tree(new_tree.clone()) .write() .await .map_err(snapshot_command_error)?; mut_repo .set_wc_commit(workspace_name, commit.id().clone()) .map_err(snapshot_command_error)?; // Rebase descendants let num_rebased = mut_repo .rebase_descendants() .await .map_err(snapshot_command_error)?; if num_rebased > 0 { writeln!( ui.status(), "Rebased {num_rebased} descendant commits onto updated working copy" ) .map_err(snapshot_command_error)?; } #[cfg(feature = "git")] if self.working_copy_shared_with_git && self.env.command.should_commit_transaction() { let old_tree = wc_commit.tree(); let new_tree = commit.tree(); export_working_copy_changes_to_git(ui, mut_repo, &old_tree, &new_tree) .await .map_err(snapshot_command_error)?; } let repo = self .env .command .maybe_commit_transaction(tx, "snapshot working copy") .await .map_err(snapshot_command_error)?; self.user_repo = ReadonlyUserRepo::new(repo); } #[cfg(feature = "git")] if self.working_copy_shared_with_git && let Ok(resolved_tree) = new_tree .trees() .await .map_err(snapshot_command_error)? .into_resolved() && resolved_tree .entries_non_recursive() .any(|entry| entry.name().as_internal_str().starts_with(".jjconflict")) { writeln!( ui.warning_default(), "The working copy contains '.jjconflict' files. These files are used by `jj` \ internally and should not be present in the working copy." ) .map_err(snapshot_command_error)?; writeln!( ui.hint_default(), "You may have used a regular `git` command to check out a conflicted commit." ) .map_err(snapshot_command_error)?; writeln!( ui.hint_default(), "You can use `jj abandon` to discard the working copy changes." ) .map_err(snapshot_command_error)?; } if self.env.command.should_commit_transaction() { locked_ws .finish(self.user_repo.repo.op_id().clone()) .await .map_err(snapshot_command_error)?; } Ok(stats) } async fn update_working_copy( &mut self, ui: &Ui, maybe_old_commit: Option<&Commit>, new_commit: &Commit, ) -> Result<(), CommandError> { assert!(self.may_update_working_copy); let stats = update_working_copy( &self.user_repo.repo, &mut self.workspace, maybe_old_commit, new_commit, ) .await?; self.print_updated_working_copy_stats(ui, maybe_old_commit, new_commit, &stats) } fn print_updated_working_copy_stats( &self, ui: &Ui, maybe_old_commit: Option<&Commit>, new_commit: &Commit, stats: &CheckoutStats, ) -> Result<(), CommandError> { if Some(new_commit) != maybe_old_commit && let Some(mut formatter) = ui.status_formatter() { let template = self.commit_summary_template(); write!(formatter, "Working copy (@) now at: ")?; template.format(new_commit, formatter.as_mut())?; writeln!(formatter)?; for parent in new_commit.parents().block_on()? { // "Working copy (@) now at: " write!(formatter, "Parent commit (@-) : ")?; template.format(&parent, formatter.as_mut())?; writeln!(formatter)?; } } print_checkout_stats(ui, stats, new_commit)?; if Some(new_commit) != maybe_old_commit && let Some(mut formatter) = ui.status_formatter() && new_commit.has_conflict() { let conflicts = new_commit.tree().conflicts().collect_vec(); writeln!( formatter.labeled("warning").with_heading("Warning: "), "There are unresolved conflicts at these paths:" )?; print_conflicted_paths(conflicts, formatter.as_mut(), self)?; } Ok(()) } pub fn start_transaction(&mut self) -> WorkspaceCommandTransaction<'_> { let tx = start_repo_transaction( self.repo(), self.workspace_name(), self.env.command.string_args(), ); let id_prefix_context = mem::take(&mut self.user_repo.id_prefix_context); WorkspaceCommandTransaction { helper: self, tx, id_prefix_context, } } async fn finish_transaction( &mut self, ui: &Ui, mut tx: Transaction, description: impl Into, _git_import_export_lock: &GitImportExportLock, ) -> Result<(), CommandError> { let num_rebased = tx.repo_mut().rebase_descendants().await?; if num_rebased > 0 { writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?; } for (name, wc_commit_id) in &tx.repo().view().wc_commit_ids().clone() { // This can fail if trunk() bookmark gets deleted or conflicted. If // the unresolvable trunk() issue gets addressed differently, it // should be okay to propagate the error. let wc_expr = RevsetExpression::commit(wc_commit_id.clone()); let is_immutable = match self.env.find_immutable_commit(tx.repo(), &wc_expr).await { Ok(commit_id) => commit_id.is_some(), Err(CommandError { error, .. }) => { writeln!( ui.warning_default(), "Failed to check mutability of the new working-copy revision." )?; print_error_sources(ui, Some(&error))?; // Give up because the same error would occur repeatedly. break; } }; if is_immutable { let wc_commit = tx.repo().store().get_commit_async(wc_commit_id).await?; tx.repo_mut().check_out(name.clone(), &wc_commit).await?; writeln!( ui.warning_default(), "The working-copy commit in workspace '{name}' became immutable, so a new \ commit has been created on top of it.", name = name.as_symbol() )?; } } if let Err(err) = revset_util::try_resolve_trunk_alias(tx.repo(), &self.env.revset_parse_context()) { // The warning would be printed above if working copies exist. if tx.repo().view().wc_commit_ids().is_empty() { writeln!( ui.warning_default(), "Failed to resolve `revset-aliases.trunk()`: {err}" )?; } writeln!( ui.hint_default(), "Use `jj config edit --repo` to adjust the `trunk()` alias." )?; } let old_repo = tx.base_repo().clone(); let maybe_old_wc_commit = old_repo .view() .get_wc_commit_id(self.workspace_name()) .map(|commit_id| tx.base_repo().store().get_commit(commit_id)) .transpose()?; let maybe_new_wc_commit = tx .repo() .view() .get_wc_commit_id(self.workspace_name()) .map(|commit_id| tx.repo().store().get_commit(commit_id)) .transpose()?; #[cfg(feature = "git")] if self.working_copy_shared_with_git && self.env.command.should_commit_transaction() { use std::error::Error as _; if let Some(wc_commit) = &maybe_new_wc_commit { // Export Git HEAD while holding the git-head lock to prevent races: // - Between two finish_transaction calls updating HEAD // - With import_git_head importing HEAD concurrently // This can still fail if HEAD was updated concurrently by another JJ process // (overlapping transaction) or a non-JJ process (e.g., git checkout). In that // case, the actual state will be imported on the next snapshot. match jj_lib::git::reset_head(tx.repo_mut(), wc_commit).await { Ok(()) => {} Err(err @ jj_lib::git::GitResetHeadError::UpdateHeadRef(_)) => { writeln!(ui.warning_default(), "{err}")?; print_error_sources(ui, err.source())?; } Err(err) => return Err(err.into()), } } let stats = jj_lib::git::export_refs(tx.repo_mut())?; crate::git_util::print_git_export_stats(ui, &stats)?; } self.user_repo = ReadonlyUserRepo::new( self.env .command .maybe_commit_transaction(tx, description) .await?, ); // Update working copy before reporting repo changes, so that // potential errors while reporting changes (broken pipe, etc) // don't leave the working copy in a stale state. if self.may_update_working_copy { if let Some(new_commit) = &maybe_new_wc_commit { self.update_working_copy(ui, maybe_old_wc_commit.as_ref(), new_commit) .await?; } else { // It seems the workspace was deleted, so we shouldn't try to // update it. } } self.report_repo_changes(ui, &old_repo).await?; if !self.env.command.should_commit_transaction() { writeln!( ui.status(), "Operation left uncommitted because --no-integrate-operation was requested: {}", short_operation_hash(self.repo().op_id()) )?; } let settings = self.settings(); let missing_user_name = settings.user_name().is_empty(); let missing_user_mail = settings.user_email().is_empty(); if missing_user_name || missing_user_mail { let not_configured_msg = match (missing_user_name, missing_user_mail) { (true, true) => "Name and email not configured.", (true, false) => "Name not configured.", (false, true) => "Email not configured.", _ => unreachable!(), }; writeln!( ui.warning_default(), "{not_configured_msg} Until configured, your commits will be created with the \ empty identity, and can't be pushed to remotes." )?; writeln!(ui.hint_default(), "To configure, run:")?; if missing_user_name { writeln!( ui.hint_no_heading(), r#" jj config set --user user.name "Some One""# )?; } if missing_user_mail { writeln!( ui.hint_no_heading(), r#" jj config set --user user.email "someone@example.com""# )?; } } Ok(()) } /// Inform the user about important changes to the repo since the previous /// operation (when `old_repo` was loaded). async fn report_repo_changes( &self, ui: &Ui, old_repo: &Arc, ) -> Result<(), CommandError> { let Some(mut fmt) = ui.status_formatter() else { return Ok(()); }; let old_view = old_repo.view(); let new_repo = self.repo().as_ref(); let new_view = new_repo.view(); let workspace_name = self.workspace_name(); if old_view.wc_commit_ids().contains_key(workspace_name) && !new_view.wc_commit_ids().contains_key(workspace_name) { writeln!( fmt.labeled("warning").with_heading("Warning: "), "The current workspace '{}' no longer exists after this operation. The working \ copy was left untouched.", workspace_name.as_symbol(), )?; writeln!( fmt.labeled("hint").with_heading("Hint: "), "Restore to an operation that contains the workspace (e.g. `jj undo` or `jj \ redo`).", )?; } let old_heads = RevsetExpression::commits(old_view.heads().iter().cloned().collect()); let new_heads = RevsetExpression::commits(new_view.heads().iter().cloned().collect()); // Filter the revsets by conflicts instead of reading all commits and doing the // filtering here. That way, we can afford to evaluate the revset even if there // are millions of commits added to the repo, assuming the revset engine can // efficiently skip non-conflicting commits. Filter out empty commits mostly so // `jj new ` doesn't result in a message about new conflicts. let conflicts = RevsetExpression::filter(RevsetFilterPredicate::HasConflict) .filtered(RevsetFilterPredicate::File(FilesetExpression::all())); let removed_conflicts_expr = new_heads.range(&old_heads).intersection(&conflicts); let added_conflicts_expr = old_heads.range(&new_heads).intersection(&conflicts); let get_commits = async |expr: Arc| -> Result, CommandError> { let commits = expr .evaluate(new_repo)? .stream() .commits(new_repo.store()) .try_collect() .await?; Ok(commits) }; let removed_conflict_commits = get_commits(removed_conflicts_expr).await?; let added_conflict_commits = get_commits(added_conflicts_expr).await?; fn commits_by_change_id(commits: &[Commit]) -> IndexMap<&ChangeId, Vec<&Commit>> { let mut result: IndexMap<&ChangeId, Vec<&Commit>> = IndexMap::new(); for commit in commits { result.entry(commit.change_id()).or_default().push(commit); } result } let removed_conflicts_by_change_id = commits_by_change_id(&removed_conflict_commits); let added_conflicts_by_change_id = commits_by_change_id(&added_conflict_commits); let mut resolved_conflicts_by_change_id = removed_conflicts_by_change_id.clone(); resolved_conflicts_by_change_id .retain(|change_id, _commits| !added_conflicts_by_change_id.contains_key(change_id)); let mut new_conflicts_by_change_id = added_conflicts_by_change_id.clone(); new_conflicts_by_change_id .retain(|change_id, _commits| !removed_conflicts_by_change_id.contains_key(change_id)); // TODO: Also report new divergence and maybe resolved divergence if !resolved_conflicts_by_change_id.is_empty() { // TODO: Report resolved and abandoned numbers separately. However, // that involves resolving the change_id among the visible commits in the new // repo, which isn't currently supported by Google's revset engine. let num_resolved: usize = resolved_conflicts_by_change_id .values() .map(|commits| commits.len()) .sum(); writeln!( fmt, "Existing conflicts were resolved or abandoned from {num_resolved} commits." )?; } if !new_conflicts_by_change_id.is_empty() { let num_conflicted: usize = new_conflicts_by_change_id .values() .map(|commits| commits.len()) .sum(); writeln!(fmt, "New conflicts appeared in {num_conflicted} commits:")?; print_updated_commits( fmt.as_mut(), &self.commit_summary_template(), new_conflicts_by_change_id.values().flatten().copied(), )?; } // Hint that the user might want to `jj new` to the first conflict commit to // resolve conflicts. Only show the hints if there were any new or resolved // conflicts, and only if there are still some conflicts. if !(added_conflict_commits.is_empty() || resolved_conflicts_by_change_id.is_empty() && new_conflicts_by_change_id.is_empty()) { // If the user just resolved some conflict and squashed them in, there won't be // any new conflicts. Clarify to them that there are still some other conflicts // to resolve. (We don't mention conflicts in commits that weren't affected by // the operation, however.) if new_conflicts_by_change_id.is_empty() { writeln!( fmt, "There are still unresolved conflicts in rebased descendants.", )?; } self.report_repo_conflicts( fmt.as_mut(), new_repo, added_conflict_commits .iter() .map(|commit| commit.id().clone()) .collect(), ) .await?; } Ok(()) } pub async fn report_repo_conflicts( &self, fmt: &mut dyn Formatter, repo: &ReadonlyRepo, conflicted_commits: Vec, ) -> Result<(), CommandError> { if !self.settings().get_bool("hints.resolving-conflicts")? || conflicted_commits.is_empty() { return Ok(()); } let only_one_conflicted_commit = conflicted_commits.len() == 1; let root_conflicts_revset = RevsetExpression::commits(conflicted_commits) .roots() .evaluate(repo)?; let root_conflict_commits: Vec<_> = root_conflicts_revset .stream() .commits(repo.store()) .try_collect() .await?; // The common part of these strings is not extracted, to avoid i18n issues. let instruction = if only_one_conflicted_commit { indoc! {" To resolve the conflicts, start by creating a commit on top of the conflicted commit: "} } else if root_conflict_commits.len() == 1 { indoc! {" To resolve the conflicts, start by creating a commit on top of the first conflicted commit: "} } else { indoc! {" To resolve the conflicts, start by creating a commit on top of one of the first conflicted commits: "} }; write!(fmt.labeled("hint").with_heading("Hint: "), "{instruction}")?; let format_short_change_id = self.short_change_id_template(); { let mut fmt = fmt.labeled("hint"); for commit in &root_conflict_commits { write!(fmt, " jj new ")?; format_short_change_id.format(commit, *fmt)?; writeln!(fmt)?; } } writedoc!( fmt.labeled("hint"), " Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you can inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. ", )?; Ok(()) } /// Identifies bookmarks which are eligible to be moved automatically /// during `jj commit` and `jj new`. Whether a bookmark is eligible is /// determined by its target and the user and repo config for /// "advance-bookmarks". /// /// Returns a Vec of bookmarks in `repo` that point to any of the `from` /// commits and that are eligible to advance. The `from` commits are /// typically the parents of the target commit of `jj commit` or `jj new`. /// /// Bookmarks are not moved until /// `WorkspaceCommandTransaction::advance_bookmarks()` is called with the /// `AdvanceableBookmark`s returned by this function. /// /// Returns an empty `std::Vec` if no bookmarks are eligible to advance. pub fn get_advanceable_bookmarks<'a>( &self, ui: &Ui, from: impl IntoIterator, ) -> Result, CommandError> { let Some(ab_matcher) = load_advance_bookmarks_matcher(ui, self.settings())? else { // Return early if we know that there's no work to do. return Ok(Vec::new()); }; let mut advanceable_bookmarks = Vec::new(); for from_commit in from { for (name, _) in self.repo().view().local_bookmarks_for_commit(from_commit) { if ab_matcher.is_match(name.as_str()) { advanceable_bookmarks.push(AdvanceableBookmark { name: name.to_owned(), old_commit_id: from_commit.clone(), }); } } } Ok(advanceable_bookmarks) } } #[cfg(feature = "git")] pub async fn export_working_copy_changes_to_git( ui: &Ui, mut_repo: &mut MutableRepo, old_tree: &MergedTree, new_tree: &MergedTree, ) -> Result<(), CommandError> { let repo = mut_repo.base_repo().as_ref(); jj_lib::git::update_intent_to_add(repo, old_tree, new_tree).await?; let stats = jj_lib::git::export_refs(mut_repo)?; crate::git_util::print_git_export_stats(ui, &stats)?; Ok(()) } #[cfg(not(feature = "git"))] pub async fn export_working_copy_changes_to_git( _ui: &Ui, _mut_repo: &mut MutableRepo, _old_tree: &MergedTree, _new_tree: &MergedTree, ) -> Result<(), CommandError> { Ok(()) } /// An ongoing [`Transaction`] tied to a particular workspace. /// /// `WorkspaceCommandTransaction`s are created with /// [`WorkspaceCommandHelper::start_transaction`] and committed with /// [`WorkspaceCommandTransaction::finish`]. The inner `Transaction` can also be /// extracted using [`WorkspaceCommandTransaction::into_inner`] in situations /// where finer-grained control over the `Transaction` is necessary. #[must_use] pub struct WorkspaceCommandTransaction<'a> { helper: &'a mut WorkspaceCommandHelper, tx: Transaction, /// Cache of index built against the current MutableRepo state. id_prefix_context: OnceCell, } impl WorkspaceCommandTransaction<'_> { /// Workspace helper that may use the base repo. pub fn base_workspace_helper(&self) -> &WorkspaceCommandHelper { self.helper } /// Settings for this workspace. pub fn settings(&self) -> &UserSettings { self.helper.settings() } pub fn base_repo(&self) -> &Arc { self.tx.base_repo() } pub fn repo(&self) -> &MutableRepo { self.tx.repo() } pub fn repo_mut(&mut self) -> &mut MutableRepo { self.id_prefix_context.take(); // invalidate self.tx.repo_mut() } pub fn check_out(&mut self, commit: &Commit) -> Result { let name = self.helper.workspace_name().to_owned(); self.id_prefix_context.take(); // invalidate self.tx.repo_mut().check_out(name, commit).block_on() } pub fn edit(&mut self, commit: &Commit) -> Result<(), EditCommitError> { let name = self.helper.workspace_name().to_owned(); self.id_prefix_context.take(); // invalidate self.tx.repo_mut().edit(name, commit).block_on() } pub fn format_commit_summary(&self, commit: &Commit) -> String { let output = self.commit_summary_template().format_plain_text(commit); output.into_string_lossy() } pub fn write_commit_summary( &self, formatter: &mut dyn Formatter, commit: &Commit, ) -> std::io::Result<()> { self.commit_summary_template().format(commit, formatter) } /// Template for one-line summary of a commit within transaction. pub fn commit_summary_template(&self) -> TemplateRenderer<'_, Commit> { let language = self.commit_template_language(); self.helper .reparse_valid_template(&language, &self.helper.commit_summary_template_text) .labeled(["commit"]) } /// Creates commit template language environment capturing the current /// transaction state. pub fn commit_template_language(&self) -> CommitTemplateLanguage<'_> { let id_prefix_context = self .id_prefix_context .get_or_init(|| self.helper.env.new_id_prefix_context()); self.helper .env .commit_template_language(self.tx.repo(), id_prefix_context) } /// Parses commit template with the current transaction state. pub fn parse_commit_template( &self, ui: &Ui, template_text: &str, ) -> Result, CommandError> { let language = self.commit_template_language(); self.helper.env.parse_template(ui, &language, template_text) } pub async fn finish(self, ui: &Ui, description: impl Into) -> Result<(), CommandError> { if !self.tx.repo().has_changes() { writeln!(ui.status(), "Nothing changed.")?; return Ok(()); } // Acquire git import/export lock before finishing the transaction to ensure // Git HEAD export happens atomically with the transaction commit. let git_import_export_lock = self.helper.lock_git_import_export()?; self.helper .finish_transaction(ui, self.tx, description, &git_import_export_lock) .await } /// Returns the wrapped [`Transaction`] for circumstances where /// finer-grained control is needed. The caller becomes responsible for /// finishing the `Transaction`, including rebasing descendants and updating /// the working copy, if applicable. pub fn into_inner(self) -> Transaction { self.tx } /// Moves each bookmark in `bookmarks` from an old commit it's associated /// with (configured by `get_advanceable_bookmarks`) to the `move_to` /// commit. If the bookmark is conflicted before the update, it will /// remain conflicted after the update, but the conflict will involve /// the `move_to` commit instead of the old commit. pub fn advance_bookmarks( &mut self, bookmarks: Vec, move_to: &CommitId, ) -> Result<(), CommandError> { for bookmark in bookmarks { // This removes the old commit ID from the bookmark's RefTarget and // replaces it with the `move_to` ID. self.repo_mut().merge_local_bookmark( &bookmark.name, &RefTarget::normal(bookmark.old_commit_id), &RefTarget::normal(move_to.clone()), )?; } Ok(()) } } pub fn find_workspace_dir(cwd: &Path) -> &Path { cwd.ancestors() .find(|path| path.join(".jj").is_dir()) .unwrap_or(cwd) } fn map_workspace_load_error(err: WorkspaceLoadError, user_wc_path: Option<&str>) -> CommandError { match err { WorkspaceLoadError::NoWorkspaceHere(wc_path) => { // Prefer user-specified path instead of absolute wc_path if any. let short_wc_path = user_wc_path.map_or(wc_path.as_ref(), Path::new); let message = format!(r#"There is no jj repo in "{}""#, short_wc_path.display()); let git_dir = wc_path.join(".git"); if git_dir.is_dir() { user_error(message).hinted( "It looks like this is a git repo. You can create a jj repo backed by it by \ running this: jj git init", ) } else { user_error(message) } } WorkspaceLoadError::RepoDoesNotExist(repo_dir) => user_error(format!( "The repository directory at {} is missing. Was it moved?", repo_dir.display(), )), WorkspaceLoadError::StoreLoadError(err @ StoreLoadError::UnsupportedType { .. }) => { internal_error_with_message( "This version of the jj binary doesn't support this type of repo", err, ) } WorkspaceLoadError::StoreLoadError( err @ (StoreLoadError::ReadError { .. } | StoreLoadError::Backend(_)), ) => internal_error_with_message("The repository appears broken or inaccessible", err), WorkspaceLoadError::StoreLoadError(StoreLoadError::Signing(err)) => user_error(err), WorkspaceLoadError::WorkingCopyState(err) => internal_error(err), WorkspaceLoadError::DecodeRepoPath(_) | WorkspaceLoadError::Path(_) => user_error(err), } } pub fn start_repo_transaction( repo: &Arc, workspace_name: &WorkspaceName, string_args: &[String], ) -> Transaction { let mut tx = repo.start_transaction(); tx.set_workspace_name(workspace_name); // TODO: Either do better shell-escaping here or store the values in some list // type (which we currently don't have). let shell_escape = |arg: &String| { if arg.as_bytes().iter().all(|b| { matches!(b, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b',' | b'-' | b'.' | b'/' | b':' | b'@' | b'_' ) }) { arg.clone() } else { format!("'{}'", arg.replace('\'', "\\'")) } }; let mut quoted_strings = vec!["jj".to_string()]; quoted_strings.extend(string_args.iter().skip(1).map(shell_escape)); tx.set_attribute("args".to_string(), quoted_strings.join(" ")); tx } /// Check if the working copy is stale and reload the repo if the repo is ahead /// of the working copy. /// /// Returns Ok(None) if the workspace doesn't exist in the repo (presumably /// because it was deleted). async fn handle_stale_working_copy( locked_wc: &mut dyn LockedWorkingCopy, repo: Arc, workspace_name: &WorkspaceName, ) -> Result, Commit)>, SnapshotWorkingCopyError> { let get_wc_commit = |repo: &ReadonlyRepo| -> Result, _> { repo.view() .get_wc_commit_id(workspace_name) .map(|id| repo.store().get_commit(id)) .transpose() .map_err(snapshot_command_error) }; let Some(wc_commit) = get_wc_commit(&repo)? else { return Ok(None); }; let old_op_id = locked_wc.old_operation_id().clone(); match WorkingCopyFreshness::check_stale(locked_wc, &wc_commit, &repo).await { Ok(WorkingCopyFreshness::Fresh) => Ok(Some((repo, wc_commit))), Ok(WorkingCopyFreshness::Updated(wc_operation)) => { let repo = repo .reload_at(&wc_operation) .await .map_err(snapshot_command_error)?; if let Some(wc_commit) = get_wc_commit(&repo)? { Ok(Some((repo, wc_commit))) } else { Ok(None) } } Ok(WorkingCopyFreshness::WorkingCopyStale) => { Err(SnapshotWorkingCopyError::StaleWorkingCopy( user_error(format!( "The working copy is stale (not updated since operation {}).", short_operation_hash(&old_op_id) )) .hinted( "Run `jj workspace update-stale` to update it. See https://docs.jj-vcs.dev/latest/working-copy/#stale-working-copy \ for more information.", ), )) } Ok(WorkingCopyFreshness::SiblingOperation) => { Err(SnapshotWorkingCopyError::StaleWorkingCopy( internal_error(format!( "The repo was loaded at operation {}, which seems to be a sibling of the \ working copy's operation {}", short_operation_hash(repo.op_id()), short_operation_hash(&old_op_id) )) .hinted(format!( "Run `jj op integrate {}` to add the working copy's operation to the \ operation log.", short_operation_hash(&old_op_id) )), )) } Err(OpStoreError::ObjectNotFound { .. }) => { Err(SnapshotWorkingCopyError::StaleWorkingCopy( user_error("Could not read working copy's operation.").hinted( "Run `jj workspace update-stale` to recover. See https://docs.jj-vcs.dev/latest/working-copy/#stale-working-copy \ for more information.", ), )) } Err(e) => Err(snapshot_command_error(e)), } } async fn update_stale_working_copy( mut locked_ws: LockedWorkspace<'_>, op_id: OperationId, stale_commit: &Commit, new_commit: &Commit, ) -> Result { // The same check as start_working_copy_mutation(), but with the stale // working-copy commit. if stale_commit.tree().tree_ids_and_labels() != locked_ws.locked_wc().old_tree().tree_ids_and_labels() { return Err(user_error("Concurrent working copy operation. Try again.")); } let stats = locked_ws .locked_wc() .check_out(new_commit) .await .map_err(|err| { internal_error_with_message( format!("Failed to check out commit {}", new_commit.id().hex()), err, ) })?; locked_ws.finish(op_id).await?; Ok(stats) } /// Prints a list of commits by the given summary template. The list may be /// elided. Use this to show created, rewritten, or abandoned commits. pub fn print_updated_commits<'a>( formatter: &mut dyn Formatter, template: &TemplateRenderer, commits: impl IntoIterator, ) -> io::Result<()> { let mut commits = commits.into_iter().fuse(); for commit in commits.by_ref().take(10) { write!(formatter, " ")?; template.format(commit, formatter)?; writeln!(formatter)?; } if commits.next().is_some() { writeln!(formatter, " ...")?; } Ok(()) } #[instrument(skip_all)] pub fn print_conflicted_paths( conflicts: Vec<(RepoPathBuf, BackendResult)>, formatter: &mut dyn Formatter, workspace_command: &WorkspaceCommandHelper, ) -> Result<(), CommandError> { let formatted_paths = conflicts .iter() .map(|(path, _conflict)| workspace_command.format_file_path(path)) .collect_vec(); let max_path_len = formatted_paths.iter().map(|p| p.len()).max().unwrap_or(0); let formatted_paths = formatted_paths .into_iter() .map(|p| format!("{:width$}", p, width = max_path_len.min(32) + 3)); for ((_, conflict), formatted_path) in std::iter::zip(conflicts, formatted_paths) { // TODO: Display the error for the path instead of failing the whole command if // `conflict` is an error? let conflict = conflict?.simplify(); let sides = conflict.num_sides(); let n_adds = conflict.adds().flatten().count(); let deletions = sides - n_adds; let mut seen_objects = BTreeMap::new(); // Sort for consistency and easier testing if deletions > 0 { seen_objects.insert( format!( // Starting with a number sorts this first "{deletions} deletion{}", if deletions > 1 { "s" } else { "" } ), "normal", // Deletions don't interfere with `jj resolve` or diff display ); } // TODO: We might decide it's OK for `jj resolve` to ignore special files in the // `removes` of a conflict (see e.g. https://github.com/jj-vcs/jj/pull/978). In // that case, `conflict.removes` should be removed below. for term in itertools::chain(conflict.removes(), conflict.adds()).flatten() { seen_objects.insert( match term { TreeValue::File { executable: false, .. } => continue, TreeValue::File { executable: true, .. } => "an executable", TreeValue::Symlink(_) => "a symlink", TreeValue::Tree(_) => "a directory", TreeValue::GitSubmodule(_) => "a git submodule", } .to_string(), "difficult", ); } write!(formatter, "{formatted_path} ")?; { let mut formatter = formatter.labeled("conflict_description"); let print_pair = |formatter: &mut dyn Formatter, (text, label): &(String, &str)| { write!(formatter.labeled(label), "{text}") }; print_pair( *formatter, &( format!("{sides}-sided"), if sides > 2 { "difficult" } else { "normal" }, ), )?; write!(formatter, " conflict")?; if !seen_objects.is_empty() { write!(formatter, " including ")?; let seen_objects = seen_objects.into_iter().collect_vec(); match &seen_objects[..] { [] => unreachable!(), [only] => print_pair(*formatter, only)?, [first, middle @ .., last] => { print_pair(*formatter, first)?; for pair in middle { write!(formatter, ", ")?; print_pair(*formatter, pair)?; } write!(formatter, " and ")?; print_pair(*formatter, last)?; } } } } writeln!(formatter)?; } Ok(()) } /// Build human-readable messages explaining why the file was not tracked fn build_untracked_reason_message(reason: &UntrackedReason) -> Option { match reason { UntrackedReason::FileTooLarge { size, max_size } => { // Show both exact and human bytes sizes to avoid something // like '1.0MiB, maximum size allowed is ~1.0MiB' let size_approx = HumanByteSize(*size); let max_size_approx = HumanByteSize(*max_size); Some(format!( "{size_approx} ({size} bytes); the maximum size allowed is {max_size_approx} \ ({max_size} bytes)", )) } // Paths with UntrackedReason::FileNotAutoTracked shouldn't be warned about // every time we make a snapshot. These paths will be printed by // "jj status" instead. UntrackedReason::FileNotAutoTracked => None, } } /// Print a warning to the user, listing untracked files that he may care about pub fn print_untracked_files( ui: &Ui, untracked_paths: &BTreeMap, path_converter: &RepoPathUiConverter, ) -> io::Result<()> { let mut untracked_paths = untracked_paths .iter() .filter_map(|(path, reason)| build_untracked_reason_message(reason).map(|m| (path, m))) .peekable(); if untracked_paths.peek().is_some() { writeln!(ui.warning_default(), "Refused to snapshot some files:")?; let mut formatter = ui.stderr_formatter(); for (path, message) in untracked_paths { let ui_path = path_converter.format_file_path(path); writeln!(formatter, " {ui_path}: {message}")?; } } Ok(()) } pub fn print_snapshot_stats( ui: &Ui, stats: &SnapshotStats, path_converter: &RepoPathUiConverter, ) -> io::Result<()> { print_untracked_files(ui, &stats.untracked_paths, path_converter)?; let large_files_sizes = stats .untracked_paths .values() .filter_map(|reason| match reason { UntrackedReason::FileTooLarge { size, .. } => Some(size), UntrackedReason::FileNotAutoTracked => None, }); if let Some(size) = large_files_sizes.max() { print_large_file_hint(ui, *size, None)?; } Ok(()) } /// Prints a hint about how to handle large files that were refused during /// snapshot. /// /// If `large_files` is provided, the hint will include file-track-specific /// options like `--include-ignored`. Otherwise, it shows a simpler hint /// suitable for general snapshot operations. pub fn print_large_file_hint( ui: &Ui, max_size: u64, large_files: Option<&[String]>, ) -> io::Result<()> { let (command, extra) = large_files .map(|files| { // shlex::try_quote fails if the string contains a nul byte, which // shouldn't happen for file paths. Fall back to unquoted on error. let files_list = files .iter() .map(|s| shlex::try_quote(s).unwrap_or(s.into())) .join(" "); let command = format!("file track {files_list}"); let extra = format!( r" * Run `jj file track --include-ignored {files_list}` This will track the file(s) regardless of size." ); (command, extra) }) .unwrap_or(("status".to_string(), String::new())); writedoc!( ui.hint_default(), r" This is to prevent large files from being added by accident. To fix this: * Add the file(s) to `.gitignore` * Run `jj config set --repo snapshot.max-new-file-size {max_size}` This will increase the maximum file size allowed for new files, in this repository only. * Run `jj --config snapshot.max-new-file-size={max_size} {command}` This will increase the maximum file size allowed for new files, for this command only.{extra} " )?; Ok(()) } pub fn print_checkout_stats( ui: &Ui, stats: &CheckoutStats, new_commit: &Commit, ) -> Result<(), std::io::Error> { if stats.added_files > 0 || stats.updated_files > 0 || stats.removed_files > 0 { writeln!( ui.status(), "Added {} files, modified {} files, removed {} files", stats.added_files, stats.updated_files, stats.removed_files )?; } if stats.skipped_files != 0 { writeln!( ui.warning_default(), "{} of those updates were skipped because there were conflicting changes in the \ working copy.", stats.skipped_files )?; writeln!( ui.hint_default(), "Inspect the changes compared to the intended target with `jj diff --from {}`. Discard the conflicting changes with `jj restore --from {}`.", short_commit_hash(new_commit.id()), short_commit_hash(new_commit.id()) )?; } Ok(()) } /// Prints warning about explicit paths that don't match any of the tree /// entries. pub fn print_unmatched_explicit_paths<'a>( ui: &Ui, workspace_command: &WorkspaceCommandHelper, expression: &FilesetExpression, trees: impl IntoIterator, ) -> io::Result<()> { let mut explicit_paths = expression.explicit_paths().collect_vec(); for tree in trees { // TODO: propagate errors explicit_paths.retain(|&path| tree.path_value(path).block_on().unwrap().is_absent()); } if !explicit_paths.is_empty() { let ui_paths = explicit_paths .iter() .map(|&path| workspace_command.format_file_path(path)) .join(", "); writeln!( ui.warning_default(), "No matching entries for paths: {ui_paths}" )?; } Ok(()) } pub async fn update_working_copy( repo: &Arc, workspace: &mut Workspace, old_commit: Option<&Commit>, new_commit: &Commit, ) -> Result { let old_tree = old_commit.map(|commit| commit.tree()); // TODO: CheckoutError::ConcurrentCheckout should probably just result in a // warning for most commands (but be an error for the checkout command) let stats = workspace .check_out(repo.op_id().clone(), old_tree.as_ref(), new_commit) .await .map_err(|err| { internal_error_with_message( format!("Failed to check out commit {}", new_commit.id().hex()), err, ) })?; Ok(stats) } /// Returns the special remote name that should be ignored by default. #[cfg_attr(not(feature = "git"), expect(unused_variables))] pub fn default_ignored_remote_name(store: &Store) -> Option<&'static RemoteName> { #[cfg(feature = "git")] { use jj_lib::git; if git::get_git_backend(store).is_ok() { return Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO); } } None } /// Whether or not the `bookmark` has any tracked remotes (i.e. is a tracking /// local bookmark.) pub fn has_tracked_remote_bookmarks(repo: &dyn Repo, bookmark: &RefName) -> bool { let remote_matcher = match default_ignored_remote_name(repo.store()) { Some(remote) => StringExpression::exact(remote).negated().to_matcher(), None => StringMatcher::all(), }; repo.view() .remote_bookmarks_matching(&StringMatcher::exact(bookmark), &remote_matcher) .any(|(_, remote_ref)| remote_ref.is_tracked()) } /// Whether or not the `tag` has any tracked remotes (i.e. is a tracking local /// tag.) pub fn has_tracked_remote_tags(repo: &dyn Repo, tag: &RefName) -> bool { let remote_matcher = match default_ignored_remote_name(repo.store()) { Some(remote) => StringExpression::exact(remote).negated().to_matcher(), None => StringMatcher::all(), }; repo.view() .remote_tags_matching(&StringMatcher::exact(tag), &remote_matcher) .any(|(_, remote_ref)| remote_ref.is_tracked()) } pub fn load_fileset_aliases( ui: &Ui, config: &StackedConfig, ) -> Result { let table_name = ConfigNamePathBuf::from_iter(["fileset-aliases"]); load_aliases_map(ui, config, &table_name) } pub fn load_revset_aliases( ui: &Ui, config: &StackedConfig, ) -> Result { let table_name = ConfigNamePathBuf::from_iter(["revset-aliases"]); let aliases_map = load_aliases_map(ui, config, &table_name)?; revset_util::warn_user_redefined_builtin(ui, config, &table_name)?; Ok(aliases_map) } pub fn load_template_aliases( ui: &Ui, config: &StackedConfig, ) -> Result { let table_name = ConfigNamePathBuf::from_iter(["template-aliases"]); load_aliases_map(ui, config, &table_name) } /// Helper to reformat content of log-like commands. #[derive(Clone, Debug)] pub struct LogContentFormat { width: usize, word_wrap: bool, } impl LogContentFormat { /// Creates new formatting helper for the terminal. pub fn new(ui: &Ui, settings: &UserSettings) -> Result { Ok(Self { width: ui.term_width(), word_wrap: settings.get_bool("ui.log-word-wrap")?, }) } /// Subtracts the given `width` and returns new formatting helper. #[must_use] pub fn sub_width(&self, width: usize) -> Self { Self { width: self.width.saturating_sub(width), word_wrap: self.word_wrap, } } /// Current width available to content. pub fn width(&self) -> usize { self.width } /// Writes content which will optionally be wrapped at the current width. pub async fn write>( &self, formatter: &mut dyn Formatter, content_fn: impl AsyncFnOnce(&mut dyn Formatter) -> Result<(), E>, ) -> Result<(), E> { if self.word_wrap { let mut recorder = FormatRecorder::new(formatter.maybe_color()); content_fn(&mut recorder).await?; text_util::write_wrapped(formatter, &recorder, self.width)?; } else { content_fn(formatter).await?; } Ok(()) } } pub fn short_commit_hash(commit_id: &CommitId) -> String { format!("{commit_id:.12}") } pub fn short_change_hash(change_id: &ChangeId) -> String { format!("{change_id:.12}") } pub fn short_operation_hash(operation_id: &OperationId) -> String { format!("{operation_id:.12}") } /// Wrapper around a `DiffEditor` to conditionally start interactive session. #[derive(Clone, Debug)] pub enum DiffSelector { NonInteractive, Interactive(DiffEditor), } impl DiffSelector { pub fn is_interactive(&self) -> bool { matches!(self, Self::Interactive(_)) } /// Restores diffs from the `right_tree` to the `left_tree` by using an /// interactive editor if enabled. /// /// Only files matching the `matcher` will be copied to the new tree. pub async fn select( &self, ui: &Ui, trees: Diff<&MergedTree>, tree_labels: Diff, matcher: &dyn Matcher, format_instructions: impl FnOnce() -> String, ) -> Result { let selected_tree = restore_tree( trees.after, trees.before, tree_labels.after, tree_labels.before, matcher, ) .await?; match self { Self::NonInteractive => Ok(selected_tree), Self::Interactive(editor) => { if selected_tree.tree_ids() == trees.before.tree_ids() { writeln!(ui.warning_default(), "Empty diff - won't run diff editor.")?; Ok(selected_tree) } else { // edit_diff_external() is designed to edit the right tree, // whereas we want to update the left tree. Unmatched paths // shouldn't be based off the right tree. Ok(editor .edit( Diff::new(trees.before, &selected_tree), matcher, format_instructions, ) .await?) } } } } } // TODO: Delete in jj 0.43+ #[derive(Clone, Debug)] pub(crate) struct RemoteBookmarkNamePattern { pub bookmark: StringPattern, pub remote: StringPattern, } impl FromStr for RemoteBookmarkNamePattern { type Err = String; fn from_str(src: &str) -> Result { // The kind prefix applies to both bookmark and remote fragments. It's // weird that unanchored patterns like substring:bookmark@remote is split // into two, but I can't think of a better syntax. // TODO: should we disable substring pattern? what if we added regex? let (maybe_kind, pat) = src .split_once(':') .map_or((None, src), |(kind, pat)| (Some(kind), pat)); let to_pattern = |pat: &str| { if let Some(kind) = maybe_kind { StringPattern::from_str_kind(pat, kind).map_err(|err| err.to_string()) } else { StringPattern::glob(pat).map_err(|err| err.to_string()) } }; // TODO: maybe reuse revset parser to handle bookmark/remote name containing @ let (bookmark, remote) = pat.rsplit_once('@').ok_or_else(|| { "remote bookmark must be specified in bookmark@remote form".to_owned() })?; Ok(Self { bookmark: to_pattern(bookmark)?, remote: to_pattern(remote)?, }) } } impl RemoteBookmarkNamePattern { pub fn as_exact(&self) -> Option> { let bookmark = RefName::new(self.bookmark.as_exact()?); let remote = RemoteName::new(self.remote.as_exact()?); Some(bookmark.to_remote_symbol(remote)) } } impl fmt::Display for RemoteBookmarkNamePattern { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO: use revset::format_remote_symbol() if FromStr is migrated to // the revset parser. let Self { bookmark, remote } = self; write!(f, "{bookmark}@{remote}") } } /// Computes the location (new parents and new children) to place commits. /// /// The `destination` argument is mutually exclusive to the `insert_after` and /// `insert_before` arguments. pub async fn compute_commit_location( ui: &Ui, workspace_command: &WorkspaceCommandHelper, destination: Option<&[RevisionArg]>, insert_after: Option<&[RevisionArg]>, insert_before: Option<&[RevisionArg]>, commit_type: &str, ) -> Result<(Vec, Vec), CommandError> { let resolve_revisions = async |revisions: Option<&[RevisionArg]>| -> Result>, CommandError> { if let Some(revisions) = revisions { Ok(Some( workspace_command .resolve_revsets_ordered(ui, revisions) .await? .into_iter() .collect_vec(), )) } else { Ok(None) } }; let destination_commit_ids = resolve_revisions(destination).await?; let after_commit_ids = resolve_revisions(insert_after).await?; let before_commit_ids = resolve_revisions(insert_before).await?; let (new_parent_ids, new_child_ids) = match (destination_commit_ids, after_commit_ids, before_commit_ids) { (Some(destination_commit_ids), None, None) => (destination_commit_ids, vec![]), (None, Some(after_commit_ids), Some(before_commit_ids)) => { (after_commit_ids, before_commit_ids) } (None, Some(after_commit_ids), None) => { let new_child_ids = RevsetExpression::commits(after_commit_ids.clone()) .children() .evaluate(workspace_command.repo().as_ref())? .stream() .try_collect() .await?; (after_commit_ids, new_child_ids) } (None, None, Some(before_commit_ids)) => { let before_commits = try_join_all( before_commit_ids .iter() .map(|id| workspace_command.repo().store().get_commit_async(id)), ) .await?; // Not using `RevsetExpression::parents` here to persist the order of parents // specified in `before_commits`. let new_parent_ids = before_commits .iter() .flat_map(|commit| commit.parent_ids()) .unique() .cloned() .collect_vec(); (new_parent_ids, before_commit_ids) } (Some(_), Some(_), _) | (Some(_), _, Some(_)) => { panic!("destination cannot be used with insert_after/insert_before") } (None, None, None) => { panic!("expected at least one of destination or insert_after/insert_before") } }; if !new_child_ids.is_empty() { workspace_command .check_rewritable(new_child_ids.iter()) .await?; ensure_no_commit_loop( workspace_command.repo().as_ref(), &RevsetExpression::commits(new_child_ids.clone()), &RevsetExpression::commits(new_parent_ids.clone()), commit_type, ) .await?; } if new_parent_ids.is_empty() { return Err(user_error("No revisions found to use as parent")); } Ok((new_parent_ids, new_child_ids)) } /// Ensure that there is no possible cycle between the potential children and /// parents of the given commits. async fn ensure_no_commit_loop( repo: &ReadonlyRepo, children_expression: &Arc, parents_expression: &Arc, commit_type: &str, ) -> Result<(), CommandError> { if let Some(commit_id) = children_expression .dag_range_to(parents_expression) .evaluate(repo)? .stream() .try_next() .await? { return Err(user_error(format!( "Refusing to create a loop: commit {} would be both an ancestor and a descendant of \ the {commit_type}", short_commit_hash(&commit_id), ))); } Ok(()) } /// Jujutsu (An experimental VCS) /// /// To get started, see the tutorial [`jj help -k tutorial`]. /// /// [`jj help -k tutorial`]: /// https://docs.jj-vcs.dev/latest/tutorial/ #[derive(clap::Parser, Clone, Debug)] #[command(name = "jj")] pub struct Args { #[command(flatten)] pub global_args: GlobalArgs, } #[derive(clap::Args, Clone, Debug)] #[command(next_help_heading = "Global Options")] pub struct GlobalArgs { /// Path to repository to operate on /// /// By default, Jujutsu searches for the closest .jj/ directory in an /// ancestor of the current working directory. #[arg(long, short = 'R', global = true, value_hint = clap::ValueHint::DirPath)] pub repository: Option, /// Don't snapshot the working copy, and don't update it /// /// By default, Jujutsu snapshots the working copy at the beginning of every /// command. The working copy is also updated at the end of the command, /// if the command modified the working-copy commit (`@`). If you want /// to avoid snapshotting the working copy and instead see a possibly /// stale working-copy commit, you can use `--ignore-working-copy`. /// This may be useful e.g. in a command prompt, especially if you have /// another process that commits the working copy. /// /// Loading the repository at a specific operation with `--at-operation` /// implies `--ignore-working-copy`. #[arg(long, global = true)] pub ignore_working_copy: bool, /// Run the command as usual but don't integrate any operations /// /// When this option is given, the operations will still be created as usual /// but they will not be integrated to the operation log. The working copy /// will also not be updated. /// /// The command will print the resulting operation ID. You can pass that to /// e.g. `jj --at-op` to inspect the resulting repo state, or you can pass /// it to `jj op restore` to restore the repo to that state. You can also /// pass the ID to `jj op integrate` to integrate the operation. /// /// Note that this does *not* prevent side effects outside the repo. For /// example, `jj git push --no-integrate-operation` will still perform the /// push. #[arg(long, global = true)] pub no_integrate_operation: bool, /// Allow rewriting immutable commits /// /// By default, Jujutsu prevents rewriting commits in the configured set of /// immutable commits. This option disables that check and lets you rewrite /// any commit but the root commit. /// /// This option only affects the check. It does not affect the /// `immutable_heads()` revset or the `immutable` template keyword. #[arg(long, global = true)] pub ignore_immutable: bool, /// Operation to load the repo at /// /// Operation to load the repo at. By default, Jujutsu loads the repo at the /// most recent operation, or at the merge of the divergent operations if /// any. /// /// You can use `--at-op=` to see what the repo looked like at /// an earlier operation. For example `jj --at-op= st` will /// show you what `jj st` would have shown you when the given operation had /// just finished. `--at-op=@` is pretty much the same as the default except /// that divergent operations will never be merged. /// /// Use `jj op log` to find the operation ID you want. Any unambiguous /// prefix of the operation ID is enough. /// /// When loading the repo at an earlier operation, the working copy will be /// ignored, as if `--ignore-working-copy` had been specified. /// /// It is possible to run mutating commands when loading the repo at an /// earlier operation. Doing that is equivalent to having run concurrent /// commands starting at the earlier operation. There's rarely a reason to /// do that, but it is possible. #[arg(long, visible_alias = "at-op", global = true)] #[arg(add = ArgValueCandidates::new(complete::operations))] pub at_operation: Option, /// Enable debug logging #[arg(long, global = true)] pub debug: bool, #[command(flatten)] pub early_args: EarlyArgs, } #[derive(clap::Args, Clone, Debug)] pub struct EarlyArgs { /// When to colorize output #[arg(long, value_name = "WHEN", global = true)] pub color: Option, /// Silence non-primary command output /// /// For example, `jj file list` will still list files, but it won't tell /// you if the working copy was snapshotted or if descendants were rebased. /// /// Warnings and errors will still be printed. #[arg(long, global = true, action = ArgAction::SetTrue)] // Parsing with ignore_errors will crash if this is bool, so use // Option. pub quiet: Option, /// Disable the pager #[arg(long, global = true, action = ArgAction::SetTrue)] // Parsing with ignore_errors will crash if this is bool, so use // Option. pub no_pager: Option, /// Additional configuration options (can be repeated) /// /// The name should be specified as TOML dotted keys. The value should be /// specified as a TOML expression. If string value isn't enclosed by any /// TOML constructs (such as array notation), quotes can be omitted. #[arg(long, value_name = "NAME=VALUE", global = true)] #[arg(add = ArgValueCompleter::new(complete::leaf_config_key_value))] pub config: Vec, /// Additional configuration files (can be repeated) #[arg(long, value_name = "PATH", global = true, value_hint = clap::ValueHint::FilePath)] pub config_file: Vec, } impl EarlyArgs { pub(crate) fn merged_config_args(&self, matches: &ArgMatches) -> Vec<(ConfigArgKind, &str)> { merge_args_with( matches, &[("config", &self.config), ("config_file", &self.config_file)], |id, value| match id { "config" => (ConfigArgKind::Item, value.as_ref()), "config_file" => (ConfigArgKind::File, value.as_ref()), _ => unreachable!("unexpected id {id:?}"), }, ) } fn has_config_args(&self) -> bool { !self.config.is_empty() || !self.config_file.is_empty() } } /// Wrapper around revset expression argument. /// /// An empty string is rejected early by the CLI value parser, but it's still /// allowed to construct an empty `RevisionArg` from a config value for /// example. An empty expression will be rejected by the revset parser. #[derive(Clone, Debug)] pub struct RevisionArg(Cow<'static, str>); impl RevisionArg { /// The working-copy symbol, which is the default of the most commands. pub const AT: Self = Self(Cow::Borrowed("@")); } impl From for RevisionArg { fn from(s: String) -> Self { Self(s.into()) } } impl AsRef for RevisionArg { fn as_ref(&self) -> &str { &self.0 } } impl fmt::Display for RevisionArg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } impl ValueParserFactory for RevisionArg { type Parser = MapValueParser Self>; fn value_parser() -> Self::Parser { NonEmptyStringValueParser::new().map(Self::from) } } /// Merges multiple clap args in order of appearance. /// /// The `id_values` is a list of `(id, values)` pairs, where `id` is the name of /// the clap `Arg`, and `values` are the parsed values for that arg. The /// `convert` function transforms each `(id, value)` pair to e.g. an enum. /// /// This is a workaround for . pub fn merge_args_with<'k, 'v, T, U>( matches: &ArgMatches, id_values: &[(&'k str, &'v [T])], mut convert: impl FnMut(&'k str, &'v T) -> U, ) -> Vec { let mut pos_values: Vec<(usize, U)> = Vec::new(); for (id, values) in id_values { pos_values.extend(itertools::zip_eq( matches.indices_of(id).into_iter().flatten(), values.iter().map(|v| convert(id, v)), )); } pos_values.sort_unstable_by_key(|&(pos, _)| pos); pos_values.into_iter().map(|(_, value)| value).collect() } fn resolve_default_command( ui: &Ui, config: &StackedConfig, app: &Command, mut string_args: Vec, ) -> Result, CommandError> { const PRIORITY_FLAGS: &[&str] = &["--help", "-h", "--version", "-V"]; let has_priority_flag = string_args .iter() .any(|arg| PRIORITY_FLAGS.contains(&arg.as_str())); if has_priority_flag { return Ok(string_args); } let app_clone = app .clone() .allow_external_subcommands(true) .ignore_errors(true); let matches = app_clone.try_get_matches_from(&string_args).ok(); if let Some(matches) = matches && matches.subcommand_name().is_none() { // Try loading as either a string or an array. // Only use `.optional()?` on the latter call to `config.get` - if it // was used on both, the type error from the first check would return // early. let default_command = if let Ok(string) = config.get::("ui.default-command") { // Warn when the user has misconfigured their default command as // "log -n 5" instead of ["log", "-n", "5"] if string.contains(' ') { let elements: ConfigValue = string.split_whitespace().collect(); writeln!( ui.warning_default(), "To include flags/arguments in `ui.default-command`, use an array instead of \ a string: `ui.default-command = {elements}`" )?; } vec![string] } else if let Some(array) = config.get::>("ui.default-command").optional()? { array } else { writeln!( ui.hint_default(), "Use `jj -h` for a list of available commands." )?; writeln!( ui.hint_no_heading(), "Run `jj config set --user ui.default-command log` to disable this message." )?; vec!["log".to_string()] }; // Insert the default command directly after the path to the binary. string_args.splice(1..1, default_command); } Ok(string_args) } fn resolve_aliases( ui: &Ui, config: &StackedConfig, app: &Command, mut string_args: Vec, ) -> Result, CommandError> { let defined_aliases: HashSet<_> = config.table_keys("aliases").collect(); let mut resolved_aliases = HashSet::new(); let mut real_commands = HashSet::new(); for command in app.get_subcommands() { real_commands.insert(command.get_name()); for alias in command.get_all_aliases() { real_commands.insert(alias); } } for alias in defined_aliases.intersection(&real_commands).sorted() { writeln!( ui.warning_default(), "Cannot define an alias that overrides the built-in command '{alias}'" )?; } loop { let app_clone = app.clone().allow_external_subcommands(true); let matches = app_clone.try_get_matches_from(&string_args).ok(); if let Some((command_name, submatches)) = matches.as_ref().and_then(|m| m.subcommand()) && !real_commands.contains(command_name) { let alias_name = command_name.to_string(); let alias_args = submatches .get_many::("") .unwrap_or_default() .map(|arg| arg.to_str().unwrap().to_string()) .collect_vec(); if resolved_aliases.contains(&*alias_name) { return Err(user_error(format!( "Recursive alias definition involving `{alias_name}`" ))); } if let Some(&alias_name) = defined_aliases.get(&*alias_name) { let alias_definition: Vec = match config.get(["aliases", alias_name]) { Ok(definition) => definition, Err(original_err) => config .get(["aliases", alias_name, "definition"]) .map_err(|_| original_err)?, }; assert!(string_args.ends_with(&alias_args)); string_args.truncate(string_args.len() - 1 - alias_args.len()); string_args.extend(alias_definition); string_args.extend_from_slice(&alias_args); resolved_aliases.insert(alias_name); continue; } else { // Not a real command and not an alias, so return what we've resolved so far return Ok(string_args); } } // No more alias commands, or hit unknown option return Ok(string_args); } } /// Parse args that must be interpreted early, e.g. before printing help. fn parse_early_args( app: &Command, args: &[String], ) -> Result<(EarlyArgs, Vec), CommandError> { // ignore_errors() bypasses errors like missing subcommand let early_matches = app .clone() .disable_version_flag(true) // Do not emit DisplayHelp error .disable_help_flag(true) // Do not stop parsing at -h/--help .arg( clap::Arg::new("help") .short('h') .long("help") .global(true) .action(ArgAction::Count), ) .ignore_errors(true) .try_get_matches_from(args)?; let args = EarlyArgs::from_arg_matches(&early_matches).unwrap(); let mut config_layers = parse_config_args(&args.merged_config_args(&early_matches))?; // Command arguments overrides any other configuration including the // variables loaded from --config* arguments. let mut layer = ConfigLayer::empty(ConfigSource::CommandArg); if let Some(choice) = args.color { layer.set_value("ui.color", choice.to_string()).unwrap(); } if args.quiet.unwrap_or_default() { layer.set_value("ui.quiet", true).unwrap(); } if args.no_pager.unwrap_or_default() { layer.set_value("ui.paginate", "never").unwrap(); } if !layer.is_empty() { config_layers.push(layer); } Ok((args, config_layers)) } fn handle_shell_completion( ui: &Ui, app: &Command, config: &StackedConfig, cwd: &Path, ) -> Result<(), CommandError> { let mut orig_args = env::args_os(); let mut args = vec![]; // Take the first two arguments as is, they must be passed to clap_complete // without any changes. They are usually "jj --". args.extend(orig_args.by_ref().take(2)); // Make sure aliases are expanded before passing them to clap_complete. We // skip the first two args ("jj" and "--") for alias resolution, then we // stitch the args back together, like clap_complete expects them. if orig_args.len() > 0 { let complete_index: Option = env::var("_CLAP_COMPLETE_INDEX") .ok() .and_then(|s| s.parse().ok()); let resolved_aliases = if let Some(index) = complete_index { // As of clap_complete 4.5.38, zsh completion script doesn't pad an // empty arg at the complete position. If the args doesn't include a // command name, the default command would be expanded at that // position. Therefore, no other command names would be suggested. let pad_len = usize::saturating_sub(index + 1, orig_args.len()); let padded_args = orig_args .by_ref() .chain(std::iter::repeat_n(OsString::new(), pad_len)); // Expand aliases left of the completion index. let mut expanded_args = expand_args_for_completion(ui, app, padded_args.take(index + 1), config)?; // Adjust env var to compensate for shift of the completion point in the // expanded command line. // SAFETY: Program is running single-threaded at this point. unsafe { env::set_var( "_CLAP_COMPLETE_INDEX", (expanded_args.len() - 1).to_string(), ); } // Remove extra padding again to align with clap_complete's expectations for // zsh. let split_off_padding = expanded_args.split_off(expanded_args.len() - pad_len); assert!( split_off_padding.iter().all(|s| s.is_empty()), "split-off padding should only consist of empty strings but was \ {split_off_padding:?}", ); // Append the remaining arguments to the right of the completion point. expanded_args.extend(to_string_args(orig_args)?); expanded_args } else { expand_args_for_completion(ui, app, orig_args, config)? }; args.extend(resolved_aliases.into_iter().map(OsString::from)); } let ran_completion = clap_complete::CompleteEnv::with_factory(|| { let mut app = app.clone(); // Dynamic completer can produce completion for hidden aliases, so we // can remove short subcommand names from the completion candidates. hide_short_subcommand_aliases(&mut app); // for completing aliases app.allow_external_subcommands(true) }) .try_complete(args.iter(), Some(cwd))?; assert!( ran_completion, "This function should not be called without the COMPLETE variable set." ); Ok(()) } /// Removes prefix command names (e.g. "c" for "create") from visible aliases, /// and adds them to (hidden) aliases. fn hide_short_subcommand_aliases(cmd: &mut Command) { for cmd in cmd.get_subcommands_mut() { hide_short_subcommand_aliases(cmd); } let (short_aliases, new_visible_aliases) = cmd .get_visible_aliases() .map(|name| name.to_owned()) .partition::, _>(|name| cmd.get_name().starts_with(name)); if short_aliases.is_empty() { return; } *cmd = mem::take(cmd) // clear existing visible aliases and add new .visible_alias(None) .visible_aliases(new_visible_aliases) // add to hidden aliases .aliases(short_aliases); } pub fn expand_args( ui: &Ui, app: &Command, args_os: impl IntoIterator, config: &StackedConfig, ) -> Result, CommandError> { let string_args = to_string_args(args_os)?; let string_args = resolve_default_command(ui, config, app, string_args)?; resolve_aliases(ui, config, app, string_args) } fn expand_args_for_completion( ui: &Ui, app: &Command, args_os: impl IntoIterator, config: &StackedConfig, ) -> Result, CommandError> { let string_args = to_string_args(args_os)?; // If a subcommand has been given, including the potentially incomplete argument // that is being completed, the default command is not resolved and the // completion candidates for the subcommand are prioritized. let mut string_args = resolve_default_command(ui, config, app, string_args)?; // Resolution of subcommand aliases must not consider the argument that is being // completed. let cursor_arg = string_args.pop(); let mut resolved_args = resolve_aliases(ui, config, app, string_args)?; resolved_args.extend(cursor_arg); Ok(resolved_args) } fn to_string_args( args_os: impl IntoIterator, ) -> Result, CommandError> { args_os .into_iter() .map(|arg_os| { arg_os .into_string() .map_err(|_| cli_error("Non-UTF-8 argument")) }) .collect() } fn parse_args(app: &Command, string_args: &[String]) -> Result<(ArgMatches, Args), clap::Error> { let matches = app .clone() .arg_required_else_help(true) .subcommand_required(true) .try_get_matches_from(string_args)?; let args = Args::from_arg_matches(&matches).unwrap(); Ok((matches, args)) } fn command_name(mut matches: &ArgMatches) -> String { let mut command = String::new(); while let Some((subcommand, new_matches)) = matches.subcommand() { if !command.is_empty() { command.push(' '); } command.push_str(subcommand); matches = new_matches; } command } pub fn format_template(ui: &Ui, arg: &C, template: &TemplateRenderer) -> String { let mut output = vec![]; template .format(arg, ui.new_formatter(&mut output).as_mut()) .expect("write() to vec backed formatter should never fail"); // Template output is usually UTF-8, but it can contain file content. output.into_string_lossy() } // Like `BoxFuture<_>`, but doesn't require `Send`. type BoxedCliDispatchFuture<'a> = Pin> + 'a>>; pub type BoxedAsyncCliDispatch<'a> = Box; type BoxedAsyncCliDispatchHook<'a> = Box; /// Object-safe trait for async command dispatch function. pub trait AsyncCliDispatch { fn call<'a>( self: Box, ui: &'a mut Ui, command_helper: &'a CommandHelper, ) -> BoxedCliDispatchFuture<'a> where Self: 'a; } /// Object-safe trait for async command dispatch hook function. trait AsyncCliDispatchHook { fn call<'a>( self: Box, ui: &'a mut Ui, command_helper: &'a CommandHelper, old_dispatch: BoxedAsyncCliDispatch<'a>, ) -> BoxedCliDispatchFuture<'a> where Self: 'a; } /// Object-safe wrapper for async command dispatch function. struct AsyncCliDispatchFn(F); impl AsyncCliDispatch for AsyncCliDispatchFn where F: AsyncFnOnce(&mut Ui, &CommandHelper) -> Result<(), CommandError>, { fn call<'a>( self: Box, ui: &'a mut Ui, command_helper: &'a CommandHelper, ) -> BoxedCliDispatchFuture<'a> where Self: 'a, { Box::pin((self.0)(ui, command_helper)) } } /// Object-safe wrapper for async command dispatch hook function. struct AsyncCliDispatchHookFn(F); impl AsyncCliDispatchHook for AsyncCliDispatchHookFn where F: AsyncFnOnce(&mut Ui, &CommandHelper, BoxedAsyncCliDispatch<'_>) -> Result<(), CommandError>, { fn call<'a>( self: Box, ui: &'a mut Ui, command_helper: &'a CommandHelper, old_dispatch: BoxedAsyncCliDispatch<'a>, ) -> BoxedCliDispatchFuture<'a> where Self: 'a, { Box::pin((self.0)(ui, command_helper, old_dispatch)) } } /// CLI command builder and runner. #[must_use] pub struct CliRunner<'a> { tracing_subscription: TracingSubscription, app: Command, config_layers: Vec, config_migrations: Vec, store_factories: StoreFactories, working_copy_factories: WorkingCopyFactories, workspace_loader_factory: Box, revset_extensions: RevsetExtensions, commit_template_extensions: Vec>, operation_template_extensions: Vec>, dispatch: BoxedAsyncCliDispatch<'a>, dispatch_hooks: Vec>, process_global_args_fns: Vec>, } type ProcessGlobalArgsFn<'a> = Box Result<(), CommandError> + 'a>; impl<'a> CliRunner<'a> { /// Initializes CLI environment and returns a builder. This should be called /// as early as possible. pub fn init() -> Self { let tracing_subscription = TracingSubscription::init(); crate::cleanup_guard::init(); Self { tracing_subscription, app: crate::commands::default_app(), config_layers: crate::config::default_config_layers(), config_migrations: crate::config::default_config_migrations(), store_factories: StoreFactories::default(), working_copy_factories: default_working_copy_factories(), workspace_loader_factory: Box::new(DefaultWorkspaceLoaderFactory), revset_extensions: Default::default(), commit_template_extensions: vec![], operation_template_extensions: vec![], dispatch: Box::new(AsyncCliDispatchFn(crate::commands::run_command)), dispatch_hooks: vec![], process_global_args_fns: vec![], } } /// Set the name of the CLI application to be displayed in help messages. pub fn name(mut self, name: &str) -> Self { self.app = self.app.name(name.to_string()); self } /// Set the about message to be displayed in help messages. pub fn about(mut self, about: &str) -> Self { self.app = self.app.about(about.to_string()); self } /// Set the version to be displayed by `jj version`. pub fn version(mut self, version: &str) -> Self { self.app = self.app.version(version.to_string()); self } /// Adds default configs in addition to the normal defaults. /// /// The `layer.source` must be `Default`. Other sources such as `User` would /// be replaced by loaded configuration. pub fn add_extra_config(mut self, layer: ConfigLayer) -> Self { assert_eq!(layer.source, ConfigSource::Default); self.config_layers.push(layer); self } /// Adds config migration rule in addition to the default rules. pub fn add_extra_config_migration(mut self, rule: ConfigMigrationRule) -> Self { self.config_migrations.push(rule); self } /// Adds `StoreFactories` to be used. pub fn add_store_factories(mut self, store_factories: StoreFactories) -> Self { self.store_factories.merge(store_factories); self } /// Adds working copy factories to be used. pub fn add_working_copy_factories( mut self, working_copy_factories: WorkingCopyFactories, ) -> Self { merge_factories_map(&mut self.working_copy_factories, working_copy_factories); self } pub fn set_workspace_loader_factory( mut self, workspace_loader_factory: Box, ) -> Self { self.workspace_loader_factory = workspace_loader_factory; self } pub fn add_symbol_resolver_extension( mut self, symbol_resolver: Box, ) -> Self { self.revset_extensions.add_symbol_resolver(symbol_resolver); self } pub fn add_revset_function_extension( mut self, name: &'static str, func: RevsetFunction, ) -> Self { self.revset_extensions.add_custom_function(name, func); self } pub fn add_commit_template_extension( mut self, commit_template_extension: Box, ) -> Self { self.commit_template_extensions .push(commit_template_extension.into()); self } pub fn add_operation_template_extension( mut self, operation_template_extension: Box, ) -> Self { self.operation_template_extensions .push(operation_template_extension.into()); self } /// Add a hook that gets called when it's time to run the command. It is /// the hook's responsibility to call the given inner dispatch function to /// run the command. pub fn add_dispatch_hook(mut self, dispatch_hook_fn: F) -> Self where F: AsyncFnOnce(&mut Ui, &CommandHelper, BoxedAsyncCliDispatch) -> Result<(), CommandError> + 'a, { self.dispatch_hooks .push(Box::new(AsyncCliDispatchHookFn(dispatch_hook_fn))); self } /// Registers new subcommands in addition to the default ones. pub fn add_subcommand(mut self, custom_dispatch_fn: F) -> Self where C: clap::Subcommand, F: AsyncFnOnce(&mut Ui, &CommandHelper, C) -> Result<(), CommandError> + 'a, { let old_dispatch = self.dispatch; let new_dispatch_fn = async move |ui: &mut Ui, command_helper: &CommandHelper| match C::from_arg_matches( command_helper.matches(), ) { Ok(command) => custom_dispatch_fn(ui, command_helper, command).await, Err(_) => old_dispatch.call(ui, command_helper).await, }; self.app = C::augment_subcommands(self.app); self.dispatch = Box::new(AsyncCliDispatchFn(new_dispatch_fn)); self } /// Registers new global arguments in addition to the default ones. pub fn add_global_args(mut self, process_before: F) -> Self where A: clap::Args, F: FnOnce(&mut Ui, A) -> Result<(), CommandError> + 'a, { let process_global_args_fn = move |ui: &mut Ui, matches: &ArgMatches| { let custom_args = A::from_arg_matches(matches).unwrap(); process_before(ui, custom_args) }; self.app = A::augment_args(self.app); self.process_global_args_fns .push(Box::new(process_global_args_fn)); self } #[instrument(skip_all)] async fn run_internal( self, ui: &mut Ui, mut raw_config: RawConfig, ) -> Result<(), CommandError> { // `cwd` is canonicalized for consistency with `Workspace::workspace_root()` and // to easily compute relative paths between them. let cwd = env::current_dir() .and_then(dunce::canonicalize) .map_err(|_| { user_error("Could not determine current directory").hinted( "Did you update to a commit where the directory doesn't exist or can't be \ accessed?", ) })?; let mut config_env = ConfigEnv::from_environment(); let mut last_config_migration_descriptions = Vec::new(); let mut migrate_config = |config: &mut StackedConfig| -> Result<(), CommandError> { last_config_migration_descriptions = jj_lib::config::migrate(config, &self.config_migrations)?; Ok(()) }; // Initial load: user, repo, and workspace-level configs for // alias/default-command resolution // Use cwd-relative workspace configs to resolve default command and // aliases. WorkspaceLoader::init() won't do any heavy lifting other // than the path resolution. let maybe_cwd_workspace_loader = self .workspace_loader_factory .create(find_workspace_dir(&cwd)) .map_err(|err| map_workspace_load_error(err, Some("."))); config_env.reload_user_config(&mut raw_config)?; if let Ok(loader) = &maybe_cwd_workspace_loader { config_env.reset_repo_path(loader.repo_path()); config_env.reload_repo_config(ui, &mut raw_config)?; config_env.reset_workspace_path(loader.workspace_root()); config_env.reload_workspace_config(ui, &mut raw_config)?; } let mut config = config_env.resolve_config(&raw_config)?; migrate_config(&mut config)?; ui.reset(&config)?; if env::var_os("COMPLETE").is_some_and(|v| !v.is_empty() && v != "0") { return handle_shell_completion(&Ui::null(), &self.app, &config, &cwd); } let string_args = expand_args(ui, &self.app, env::args_os(), &config)?; let (args, config_layers) = parse_early_args(&self.app, &string_args)?; if !config_layers.is_empty() { raw_config.as_mut().extend_layers(config_layers); config = config_env.resolve_config(&raw_config)?; migrate_config(&mut config)?; ui.reset(&config)?; } if args.has_config_args() { warn_if_args_mismatch(ui, &self.app, &config, &string_args)?; } let (matches, args) = parse_args(&self.app, &string_args) .map_err(|err| map_clap_cli_error(err, ui, &config))?; if args.global_args.debug { // TODO: set up debug logging as early as possible self.tracing_subscription.enable_debug_logging()?; } for process_global_args_fn in self.process_global_args_fns { process_global_args_fn(ui, &matches)?; } config_env.set_command_name(command_name(&matches)); let maybe_workspace_loader = if let Some(path) = &args.global_args.repository { // TODO: maybe path should be canonicalized by WorkspaceLoader? let abs_path = cwd.join(path); let abs_path = dunce::canonicalize(&abs_path).unwrap_or(abs_path); // Invalid -R path is an error. No need to proceed. let loader = self .workspace_loader_factory .create(&abs_path) .map_err(|err| map_workspace_load_error(err, Some(path)))?; config_env.reset_repo_path(loader.repo_path()); config_env.reload_repo_config(ui, &mut raw_config)?; config_env.reset_workspace_path(loader.workspace_root()); config_env.reload_workspace_config(ui, &mut raw_config)?; Ok(loader) } else { maybe_cwd_workspace_loader }; // Apply workspace configs, --config arguments, and --when.commands. config = config_env.resolve_config(&raw_config)?; migrate_config(&mut config)?; ui.reset(&config)?; // Print only the last migration messages to omit duplicates. for (source, desc) in &last_config_migration_descriptions { let source_str = match source { ConfigSource::Default => "default-provided", ConfigSource::EnvBase | ConfigSource::EnvOverrides => "environment-provided", ConfigSource::User => "user-level", ConfigSource::Repo => "repo-level", ConfigSource::Workspace => "workspace-level", ConfigSource::CommandArg => "CLI-provided", }; writeln!( ui.warning_default(), "Deprecated {source_str} config: {desc}" )?; } if args.global_args.repository.is_some() { warn_if_args_mismatch(ui, &self.app, &config, &string_args)?; } let settings = UserSettings::from_config(config)?; let command_helper_data = CommandHelperData { app: self.app, cwd, string_args, matches, global_args: args.global_args, config_env, config_migrations: self.config_migrations, raw_config, settings, revset_extensions: self.revset_extensions.into(), commit_template_extensions: self.commit_template_extensions, operation_template_extensions: self.operation_template_extensions, maybe_workspace_loader, store_factories: self.store_factories, working_copy_factories: self.working_copy_factories, workspace_loader_factory: self.workspace_loader_factory, }; let command_helper = CommandHelper { data: Rc::new(command_helper_data), }; let dispatch = self.dispatch_hooks .into_iter() .fold(self.dispatch, |old_dispatch, dispatch_hook| { let f = async move |ui: &mut Ui, command_helper: &CommandHelper| { dispatch_hook.call(ui, command_helper, old_dispatch).await }; Box::new(AsyncCliDispatchFn(f)) }); dispatch.call(ui, &command_helper).await } #[must_use] #[instrument(skip(self))] pub fn run(mut self) -> u8 { // Tell crossterm to ignore NO_COLOR (we check it ourselves) crossterm::style::force_color_output(true); let config = config_from_environment(self.config_layers.drain(..)); // Set up ui assuming the default config has no conditional variables. // If it had, the configuration will be fixed by the next ui.reset(). let mut ui = Ui::with_config(config.as_ref()) .expect("default config should be valid, env vars are stringly typed"); let result = self.run_internal(&mut ui, config).block_on(); let exit_code = handle_command_result(&mut ui, result); ui.finalize_pager(); exit_code } } fn map_clap_cli_error(err: clap::Error, ui: &Ui, config: &StackedConfig) -> CommandError { if let Some(ContextValue::String(cmd)) = err.get(ContextKind::InvalidSubcommand) { let remove_useless_error_context = |mut err: clap::Error| { // Clap suggests unhelpful subcommands, e.g. `config` for `clone`. // We don't want suggestions when we know this isn't a misspelling. err.remove(ContextKind::SuggestedSubcommand); err.remove(ContextKind::Suggested); // Remove an empty line err.remove(ContextKind::Usage); // Also unhelpful for these errors. err }; match cmd.as_str() { // git commands that a brand-new user might type during their first // experiments with `jj` "clone" | "init" => { let cmd = cmd.clone(); return CommandError::from(remove_useless_error_context(err)) .hinted(format!( "You probably want `jj git {cmd}`. See also `jj help git`." )) .hinted(format!( r#"You can configure `aliases.{cmd} = ["git", "{cmd}"]` if you want `jj {cmd}` to work and always use the Git backend."# )); } "amend" => { return CommandError::from(remove_useless_error_context(err)) .hinted( r#"You probably want `jj squash`. You can configure `aliases.amend = ["squash"]` if you want `jj amend` to work."#); } _ => {} } } if let (Some(ContextValue::String(arg)), Some(ContextValue::String(value))) = ( err.get(ContextKind::InvalidArg), err.get(ContextKind::InvalidValue), ) && arg.as_str() == "--template