├── .cargo └── audit.toml ├── .github ├── codecov.yml ├── renovate.json └── workflows │ ├── audit.yml │ ├── careful.yml │ ├── ci-heavy.yml │ ├── ci.yml │ ├── coverage.yaml │ ├── cross-ci.yml │ ├── release-plz.yml │ ├── style.yml │ └── triage.yml ├── .gitignore ├── .justfile ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build-dependencies.just ├── cliff.toml ├── coverage └── .gitkeep ├── crates ├── backend │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── src │ │ ├── choose.rs │ │ ├── lib.rs │ │ ├── local.rs │ │ ├── opendal.rs │ │ ├── rclone.rs │ │ ├── rest.rs │ │ └── util.rs │ └── tests │ │ └── fixtures │ │ └── opendal │ │ ├── b2.toml │ │ ├── connections_20.toml │ │ ├── retry_20.toml │ │ ├── retry_default.toml │ │ ├── retry_off.toml │ │ ├── s3_aws.toml │ │ ├── s3_idrive.toml │ │ ├── throttle.toml │ │ └── webdav_owncloud_nextcloud.toml ├── config │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ └── src │ │ └── lib.rs ├── core │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── archiver.rs │ │ ├── archiver │ │ │ ├── file_archiver.rs │ │ │ ├── parent.rs │ │ │ ├── tree.rs │ │ │ └── tree_archiver.rs │ │ ├── backend.rs │ │ ├── backend │ │ │ ├── cache.rs │ │ │ ├── childstdout.rs │ │ │ ├── decrypt.rs │ │ │ ├── dry_run.rs │ │ │ ├── hotcold.rs │ │ │ ├── ignore.rs │ │ │ ├── local_destination.rs │ │ │ ├── node.rs │ │ │ ├── stdin.rs │ │ │ └── warm_up.rs │ │ ├── blob.rs │ │ ├── blob │ │ │ ├── packer.rs │ │ │ └── tree.rs │ │ ├── chunker.rs │ │ ├── commands.rs │ │ ├── commands │ │ │ ├── backup.rs │ │ │ ├── cat.rs │ │ │ ├── check.rs │ │ │ ├── config.rs │ │ │ ├── copy.rs │ │ │ ├── dump.rs │ │ │ ├── forget.rs │ │ │ ├── init.rs │ │ │ ├── key.rs │ │ │ ├── merge.rs │ │ │ ├── prune.rs │ │ │ ├── repair.rs │ │ │ ├── repair │ │ │ │ ├── index.rs │ │ │ │ └── snapshots.rs │ │ │ ├── repoinfo.rs │ │ │ ├── restore.rs │ │ │ ├── snapshots.rs │ │ │ └── snapshots │ │ │ │ ├── rustic_core__commands__check__tests__250MiB.snap │ │ │ │ ├── rustic_core__commands__check__tests__5%.snap │ │ │ │ ├── rustic_core__commands__check__tests__5__12.snap │ │ │ │ ├── rustic_core__commands__check__tests__all.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_15_month_hours.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_29_28.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_4_month_days.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_5_12.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_daily_15.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_daily_month.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_daily_week.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_daily_year.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_hourly_20.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_hourly_day.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_hourly_month.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_hourly_week.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_hourly_year.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_monthly_5.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_monthly_year.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_weekly_10.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_weekly_month.snap │ │ │ │ ├── rustic_core__commands__check__tests__n_m_weekly_year.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily10.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily2,keep-weekly2,keep-monthly6.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily3,keep-weekly2,keep-month[cut].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily3,keep-weekly4.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily3.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily30.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-daily7,keep-weekly2,keep-month[cut].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-half-yearly10.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-hourly-1.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-hourly20.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-ids[23ef].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last-1,keep-hourly-1.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last-1.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last10.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last15.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last2,keep-daily10.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last200.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last5,keep-daily5.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-last99.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-monthly6.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-nonetrue.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-quarter-yearly10.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-tags[bar,foo].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-tags[foo].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-weekly2.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-weekly4.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-daily1year 2months 3days 3h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-half-yearly1year 2month[cut].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-hourly1year 2months 3days 3h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-monthly1year 2months 3d[cut].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-quarter-yearly1year 2mo[cut].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-weekly1year 2months 3days 3h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within-yearly1year 2months 3days 3h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within13days 23h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within1day.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within1h,keep-within-hourly1da[cut].snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within1m.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within1month 14days.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within1year 1month 1day.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within1year 2months 3days 3h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within2days.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within2months 2h.snap │ │ │ │ ├── rustic_core__commands__forget__tests__keep-within7days.snap │ │ │ │ └── rustic_core__commands__forget__tests__keep-yearly10.snap │ │ ├── crypto.rs │ │ ├── crypto │ │ │ ├── aespoly1305.rs │ │ │ └── hasher.rs │ │ ├── error.rs │ │ ├── id.rs │ │ ├── index.rs │ │ ├── index │ │ │ ├── binarysorted.rs │ │ │ └── indexer.rs │ │ ├── lib.rs │ │ ├── progress.rs │ │ ├── repofile.rs │ │ ├── repofile │ │ │ ├── configfile.rs │ │ │ ├── indexfile.rs │ │ │ ├── keyfile.rs │ │ │ ├── packfile.rs │ │ │ └── snapshotfile.rs │ │ ├── repository.rs │ │ ├── repository │ │ │ ├── command_input.rs │ │ │ └── warm_up.rs │ │ ├── vfs.rs │ │ └── vfs │ │ │ └── format.rs │ └── tests │ │ ├── command_input.rs │ │ ├── errors.rs │ │ ├── fixtures │ │ ├── backup-data.tar.gz │ │ ├── config │ │ ├── key-failing │ │ ├── key1 │ │ └── key2 │ │ ├── integration.rs │ │ ├── integration │ │ ├── backup.rs │ │ ├── find.rs │ │ ├── ls.rs │ │ ├── prune.rs │ │ ├── restore.rs │ │ └── vfs.rs │ │ ├── keys.rs │ │ └── snapshots │ │ ├── errors__error_debug.snap │ │ ├── errors__error_display.snap │ │ ├── errors__log_output_minimal_passes.snap │ │ ├── errors__log_output_passes.snap │ │ ├── integration__backup-tar-groups-nix.snap │ │ ├── integration__backup-tar-groups-windows.snap │ │ ├── integration__backup-tar-matching-snaps-nix.snap │ │ ├── integration__backup-tar-matching-snaps-windows.snap │ │ ├── integration__backup-tar-summary-first-nix.snap │ │ ├── integration__backup-tar-summary-first-windows.snap │ │ ├── integration__backup-tar-summary-second-nix.snap │ │ ├── integration__backup-tar-summary-second-windows.snap │ │ ├── integration__backup-tar-summary-third-nix.snap │ │ ├── integration__backup-tar-summary-third-windows.snap │ │ ├── integration__backup-tar-tree-nix.snap │ │ ├── integration__backup-tar-tree-windows.snap │ │ ├── integration__dryrun-tar-summary-first-nix.snap │ │ ├── integration__dryrun-tar-summary-first-windows.snap │ │ ├── integration__dryrun-tar-summary-second-nix.snap │ │ ├── integration__dryrun-tar-summary-second-windows.snap │ │ ├── integration__dryrun-tar-tree-nix.snap │ │ ├── integration__dryrun-tar-tree-windows.snap │ │ ├── integration__find-matching-existing-nix.snap │ │ ├── integration__find-matching-existing-windows.snap │ │ ├── integration__find-matching-nodes-not-found-nix.snap │ │ ├── integration__find-matching-nodes-not-found-windows.snap │ │ ├── integration__find-matching-wildcard-existing-nix.snap │ │ ├── integration__find-matching-wildcard-existing-windows.snap │ │ ├── integration__find-nodes-existing-nix.snap │ │ ├── integration__find-nodes-existing-windows.snap │ │ ├── integration__find-nodes-not-found-nix.snap │ │ ├── integration__find-nodes-not-found-windows.snap │ │ ├── integration__ls-nix.snap │ │ ├── integration__ls-windows.snap │ │ ├── integration__stdin-command-summary-nix.snap │ │ ├── integration__stdin-command-summary-windows.snap │ │ ├── integration__vfs-nix.snap │ │ ├── integration__vfs-windows.snap │ │ └── integration__windows.snap └── testing │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── src │ ├── backend.rs │ └── lib.rs ├── deny.toml ├── docs └── Readme.md ├── dprint.json ├── examples ├── backup │ ├── Cargo.toml │ └── examples │ │ └── backup.rs ├── check │ ├── Cargo.toml │ └── examples │ │ └── check.rs ├── config │ ├── Cargo.toml │ └── examples │ │ └── config.rs ├── copy │ ├── Cargo.toml │ └── examples │ │ └── copy.rs ├── find │ ├── Cargo.toml │ └── examples │ │ └── find.rs ├── forget │ ├── Cargo.toml │ └── examples │ │ └── forget.rs ├── init │ ├── Cargo.toml │ └── examples │ │ └── init.rs ├── key │ ├── Cargo.toml │ └── examples │ │ └── key.rs ├── ls │ ├── Cargo.toml │ └── examples │ │ └── ls.rs ├── merge │ ├── Cargo.toml │ └── examples │ │ └── merge.rs ├── prune │ ├── Cargo.toml │ └── examples │ │ └── prune.rs ├── restore │ ├── Cargo.toml │ └── examples │ │ └── restore.rs └── tag │ ├── Cargo.toml │ └── examples │ └── tag.rs └── release-plz.toml /.cargo/audit.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | ignore = [ 3 | # FIXME!: See https://github.com/RustCrypto/RSA/issues/19#issuecomment-1822995643. 4 | # There is no workaround available yet. 5 | "RUSTSEC-2023-0071", 6 | ] 7 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar 4 | range: 85..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives 12 | threshold: 1% 13 | 14 | ignore: 15 | - "tests" # Test files aren't important for coverage 16 | - "crates/testing" # Testing crate contains Test helpers which aren't important for coverage 17 | - "crates/cli" # CLI crate contains prompts to the user, which we currently deem not important for coverage 18 | 19 | # Make comments less noisy 20 | comment: 21 | layout: "files" 22 | require_changes: true 23 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>rustic-rs/.github:renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**/Cargo.toml' 7 | - '**/Cargo.lock' 8 | - '**.yml' 9 | schedule: 10 | # Runs at 00:00 UTC everyday 11 | - cron: "0 0 * * *" 12 | push: 13 | paths: 14 | - '**/Cargo.toml' 15 | - '**/Cargo.lock' 16 | - '**.yml' 17 | merge_group: 18 | types: [checks_requested] 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | audit: 26 | if: ${{ github.repository_owner == 'rustic-rs' }} 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 31 | # Ensure that the latest version of Cargo is installed 32 | - name: Install Rust toolchain 33 | uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # v1 34 | with: 35 | toolchain: stable 36 | - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 37 | 38 | - name: Generate lockfile (Cargo.lock) 39 | run: cargo generate-lockfile 40 | 41 | - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 42 | with: 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | ignore: RUSTSEC-2023-0071 # rsa thingy, ignored for now 45 | 46 | cargo-deny: 47 | if: ${{ github.repository_owner == 'rustic-rs' }} 48 | name: Run cargo-deny 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 52 | 53 | - uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1 # v2 54 | with: 55 | command: check bans licenses sources 56 | 57 | result: 58 | if: ${{ github.repository_owner == 'rustic-rs' }} 59 | name: Result (Audit) 60 | runs-on: ubuntu-latest 61 | needs: 62 | - audit 63 | - cargo-deny 64 | steps: 65 | - name: Mark the job as successful 66 | run: exit 0 67 | if: success() 68 | - name: Mark the job as unsuccessful 69 | run: exit 1 70 | if: "!success()" 71 | -------------------------------------------------------------------------------- /.github/workflows/careful.yml: -------------------------------------------------------------------------------- 1 | name: Careful Integration 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 5" 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | careful: 14 | name: Careful Test 15 | runs-on: ${{ matrix.job.os }} 16 | strategy: 17 | matrix: 18 | rust: [nightly] # runs on nightly only 19 | job: 20 | - os: macos-latest 21 | - os: ubuntu-latest 22 | - os: windows-latest 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 25 | if: github.event_name != 'pull_request' 26 | with: 27 | fetch-depth: 0 28 | 29 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 30 | if: github.event_name == 'pull_request' 31 | with: 32 | ref: ${{ github.event.pull_request.head.sha }} 33 | fetch-depth: 0 34 | 35 | - name: Install Rust toolchain 36 | uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # v1 37 | with: 38 | toolchain: ${{ matrix.rust }} 39 | - name: install cargo-careful 40 | uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb # v2 41 | with: 42 | tool: cargo-careful 43 | - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 44 | 45 | - name: Run Cargo Careful 46 | run: cargo +${{ matrix.rust }} careful test 47 | 48 | # TODO: don't run miri for now, due to addition of workspace 49 | #1 crates and we'll need to figure out if we want to run miri 50 | # miri: 51 | # name: Miri Test 52 | # runs-on: ${{ matrix.job.os }} 53 | # strategy: 54 | # fail-fast: false 55 | # matrix: 56 | # rust: [nightly] # runs on nightly only 57 | # job: 58 | # - os: macos-latest 59 | # - os: ubuntu-latest 60 | # - os: windows-latest 61 | # steps: 62 | # - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 63 | # if: github.event_name != 'pull_request' 64 | # with: 65 | # fetch-depth: 0 66 | 67 | # - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 68 | # if: github.event_name == 'pull_request' 69 | # with: 70 | # ref: ${{ github.event.pull_request.head.sha }} 71 | # fetch-depth: 0 72 | 73 | # - name: Install Rust toolchain 74 | # uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 # v1 75 | # with: 76 | # toolchain: ${{ matrix.rust }} 77 | # components: miri 78 | # - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2 79 | 80 | # - name: Run Cargo Clean 81 | # run: cargo +${{ matrix.rust }} clean # miri needs clean builds 82 | 83 | # - name: Patch Cargo.toml 84 | # shell: bash 85 | # run: | 86 | # # Account for sha256_compress not being interpreted by miri 87 | # # https://github.com/rust-lang/miri/issues/3066 88 | # sed -i -e 's/^sha2 = { version.*/sha2 = "0"/g' ./Cargo.toml 89 | # - name: Run Cargo Miri Setup 90 | # run: cargo +${{ matrix.rust }} miri setup # keep output clean 91 | 92 | # - name: Run Cargo Miri 93 | # env: 94 | # MIRIFLAGS: -Zmiri-disable-isolation 95 | # run: cargo +${{ matrix.rust }} miri test -- --nocapture 96 | 97 | result: 98 | name: Result (Careful CI) 99 | runs-on: ubuntu-latest 100 | needs: 101 | - careful 102 | # - miri # FIXME: don't run miri for now, due to addition of workspace 103 | steps: 104 | - name: Mark the job as successful 105 | run: exit 0 106 | if: success() 107 | - name: Mark the job as unsuccessful 108 | run: exit 1 109 | if: "!success()" 110 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 5 | cancel-in-progress: true 6 | 7 | env: 8 | CI: true 9 | 10 | on: 11 | pull_request: 12 | 13 | jobs: 14 | fmt: 15 | name: Rustfmt 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 19 | - name: Install Rust toolchain 20 | uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # v1 21 | with: 22 | toolchain: stable 23 | - run: rustup component add rustfmt 24 | - name: Run Cargo Fmt 25 | run: cargo fmt --all -- --check 26 | 27 | clippy: 28 | name: Clippy 29 | runs-on: ubuntu-latest 30 | 31 | steps: 32 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 33 | - name: Install Rust toolchain 34 | uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # v1 35 | with: 36 | toolchain: stable 37 | components: clippy 38 | - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 39 | - name: Run clippy 40 | run: cargo clippy --all-targets --all-features -- -D warnings 41 | 42 | test: 43 | name: Test 44 | runs-on: ${{ matrix.job.os }} 45 | strategy: 46 | # Don't fail fast, so we can actually see all the results 47 | fail-fast: false 48 | matrix: 49 | rust: [stable] 50 | job: 51 | - os: macos-latest 52 | - os: ubuntu-latest 53 | - os: windows-latest 54 | steps: 55 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 56 | if: github.event_name != 'pull_request' 57 | with: 58 | fetch-depth: 0 59 | 60 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 61 | if: github.event_name == 'pull_request' 62 | with: 63 | ref: ${{ github.event.pull_request.head.sha }} 64 | fetch-depth: 0 65 | 66 | - name: Install Rust toolchain 67 | uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # v1 68 | with: 69 | toolchain: ${{ matrix.rust }} 70 | - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 71 | - name: Run Cargo Test 72 | run: cargo +${{ matrix.rust }} test --all-targets --all-features --workspace --examples 73 | id: run_tests 74 | env: 75 | INSTA_UPDATE: new 76 | - name: Upload snapshots of failed tests 77 | if: ${{ failure() && steps.run_tests.outcome == 'failure' }} 78 | uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4 79 | with: 80 | name: failed-snapshots-${{ matrix.job.os }} 81 | path: "**/snapshots/*.snap.new" 82 | 83 | docs: 84 | name: Build docs 85 | runs-on: ${{ matrix.job.os }} 86 | strategy: 87 | matrix: 88 | rust: [stable] 89 | job: 90 | - os: macos-latest 91 | - os: ubuntu-latest 92 | - os: windows-latest 93 | steps: 94 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 95 | if: github.event_name != 'pull_request' 96 | with: 97 | fetch-depth: 0 98 | 99 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 100 | if: github.event_name == 'pull_request' 101 | with: 102 | ref: ${{ github.event.pull_request.head.sha }} 103 | fetch-depth: 0 104 | 105 | - name: Install Rust toolchain 106 | uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203 # v1 107 | with: 108 | toolchain: ${{ matrix.rust }} 109 | - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2 110 | - name: Run Cargo Doc 111 | run: cargo +${{ matrix.rust }} doc --no-deps --all-features --workspace --examples 112 | 113 | result: 114 | name: Result (CI) 115 | runs-on: ubuntu-latest 116 | needs: 117 | - fmt 118 | - clippy 119 | - test 120 | - docs 121 | steps: 122 | - name: Mark the job as successful 123 | run: exit 0 124 | if: success() 125 | - name: Mark the job as unsuccessful 126 | run: exit 1 127 | if: "!success()" 128 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Test Coverage 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | paths: 10 | - "**.rs" 11 | - "**.snap" 12 | - "**.yml" 13 | jobs: 14 | test: 15 | name: Generate Coverage Report 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 21 | 22 | - name: Install cargo-tarpaulin 23 | uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb # v2 24 | with: 25 | tool: cargo-tarpaulin 26 | 27 | # We run the coverage report on the workspace, but we configured in codecov to only look at parts of the workspace essentially 28 | # 29 | # This is because we have a workspace with multiple crates, and we want to generate coverage for all of them, but we only want to 30 | # report the coverage of rustic_backend and rustic_core crates (currently) as this is where the main logic is 31 | - name: Generate code coverage 32 | env: 33 | RUST_BACKTRACE: "0" 34 | run: | 35 | cargo tarpaulin --verbose --all-features --workspace --timeout 120 --out xml 36 | 37 | - name: Upload coverage reports to Codecov 38 | uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5 39 | with: 40 | token: ${{ secrets.CODECOV_TOKEN }} 41 | slug: rustic-rs/rustic_core 42 | -------------------------------------------------------------------------------- /.github/workflows/cross-ci.yml: -------------------------------------------------------------------------------- 1 | name: Cross CI (light) 2 | 3 | on: 4 | pull_request: 5 | 6 | defaults: 7 | run: 8 | shell: bash 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | cross-check: 16 | # Only run if the commit doesn't come from a merged PR, we assume CI is running in the PR as well 17 | # so we don't want to have runs double up 18 | if: github.event.pull_request.merged == false 19 | name: Cross checking ${{ matrix.job.target }} on ${{ matrix.rust }} 20 | runs-on: ${{ matrix.job.os }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | rust: [stable] 25 | job: 26 | - os: windows-latest 27 | os-name: windows 28 | target: x86_64-pc-windows-msvc 29 | architecture: x86_64 30 | use-cross: false 31 | - os: windows-latest 32 | os-name: windows 33 | target: x86_64-pc-windows-gnu 34 | architecture: x86_64 35 | use-cross: false 36 | - os: macos-13 37 | os-name: macos 38 | target: x86_64-apple-darwin 39 | architecture: x86_64 40 | use-cross: false 41 | - os: macos-latest 42 | os-name: macos 43 | target: aarch64-apple-darwin 44 | architecture: arm64 45 | use-cross: true 46 | - os: ubuntu-latest 47 | os-name: linux 48 | target: x86_64-unknown-linux-gnu 49 | architecture: x86_64 50 | use-cross: false 51 | - os: ubuntu-latest 52 | os-name: linux 53 | target: x86_64-unknown-linux-musl 54 | architecture: x86_64 55 | use-cross: false 56 | - os: ubuntu-latest 57 | os-name: linux 58 | target: aarch64-unknown-linux-gnu 59 | architecture: arm64 60 | use-cross: true 61 | - os: ubuntu-latest 62 | os-name: linux 63 | target: aarch64-unknown-linux-musl 64 | architecture: arm64 65 | use-cross: true 66 | - os: ubuntu-latest 67 | os-name: linux 68 | target: i686-unknown-linux-gnu 69 | architecture: i686 70 | use-cross: true 71 | - os: ubuntu-latest 72 | os-name: netbsd 73 | target: x86_64-unknown-netbsd 74 | architecture: x86_64 75 | use-cross: true 76 | - os: ubuntu-latest 77 | os-name: linux 78 | target: armv7-unknown-linux-gnueabihf 79 | architecture: armv7 80 | use-cross: true 81 | 82 | steps: 83 | - name: Checkout repository 84 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 85 | 86 | - name: Run Cross-CI action 87 | uses: rustic-rs/cross-ci-action@main 88 | with: 89 | toolchain: ${{ matrix.rust }} 90 | target: ${{ matrix.job.target }} 91 | use-cross: ${{ matrix.job.use-cross }} 92 | project-cache-key: "rustic_core" 93 | 94 | result: 95 | # Only run if the commit doesn't come from a merged PR, we assume CI is running in the PR as well 96 | # so we don't want to have runs double up 97 | if: github.event.pull_request.merged == false 98 | name: Result (Cross-CI) 99 | runs-on: ubuntu-latest 100 | needs: cross-check 101 | steps: 102 | - name: Mark the job as successful 103 | run: exit 0 104 | if: success() 105 | - name: Mark the job as unsuccessful 106 | run: exit 1 107 | if: "!success()" 108 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | if: ${{ github.repository_owner == 'rustic-rs' }} 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Generate GitHub token 19 | uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1 20 | id: generate-token 21 | with: 22 | app-id: ${{ secrets.RELEASE_PLZ_APP_ID }} 23 | private-key: ${{ secrets.RELEASE_PLZ_APP_PRIVATE_KEY }} 24 | - name: Checkout repository 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 26 | with: 27 | fetch-depth: 0 28 | token: ${{ steps.generate-token.outputs.token }} 29 | - name: Install Rust toolchain 30 | uses: dtolnay/rust-toolchain@stable 31 | 32 | - name: Run release-plz 33 | uses: MarcoIeni/release-plz-action@395042a06223c60c3e953d5fa124e97dfef96672 # v0.5 34 | env: 35 | GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} 36 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | name: Lint Markdown / Toml 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.json' 7 | - '**.toml' 8 | - '**.md' 9 | - '**.yml' 10 | - '**.yaml' 11 | push: 12 | branches: [main] 13 | paths: 14 | - '**.json' 15 | - '**.toml' 16 | - '**.md' 17 | - '**.yml' 18 | - '**.yaml' 19 | merge_group: 20 | types: [checks_requested] 21 | 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | style: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 31 | 32 | - uses: dprint/check@2f1cf31537886c3bfb05591c031f7744e48ba8a1 # v2.2 33 | 34 | result: 35 | name: Result (Style) 36 | runs-on: ubuntu-latest 37 | needs: 38 | - style 39 | steps: 40 | - name: Mark the job as successful 41 | run: exit 0 42 | if: success() 43 | - name: Mark the job as unsuccessful 44 | run: exit 1 45 | if: "!success()" 46 | -------------------------------------------------------------------------------- /.github/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: 4 | - opened 5 | 6 | jobs: 7 | label_issue: 8 | if: ${{ github.repository_owner == 'rustic-rs' }} 9 | name: Label issue 10 | runs-on: ubuntu-latest 11 | steps: 12 | - env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | ISSUE_URL: ${{ github.event.issue.html_url }} 15 | run: | 16 | # check if issue doesn't have any labels 17 | if [[ $(gh issue view $ISSUE_URL --json labels -q '.labels | length') -eq 0 ]]; then 18 | # add S-triage label 19 | gh issue edit $ISSUE_URL --add-label "S-triage" 20 | fi 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .vscode 4 | mutants.out 5 | cargo-test* 6 | coverage/*lcov 7 | .testscompletions-* 8 | 9 | # Generated by Cargo 10 | # Use in library crates 11 | # https://blog.rust-lang.org/2023/08/29/committing-lockfiles.html 12 | # Cargo.lock 13 | 14 | # Coverage information 15 | coverage/*.info 16 | 17 | # local repo config 18 | .cargo/config.toml 19 | 20 | # Generated by Tests 21 | crates/core/tests/generated/ 22 | -------------------------------------------------------------------------------- /.justfile: -------------------------------------------------------------------------------- 1 | # 'Just' Configuration 2 | # Loads .env file for variables to be used in 3 | # in this just file 4 | 5 | set dotenv-load := true 6 | 7 | # Ignore recipes that are commented out 8 | 9 | set ignore-comments := true 10 | 11 | # Set shell for Windows OSs: 12 | # If you have PowerShell Core installed and want to use it, 13 | # use `pwsh.exe` instead of `powershell.exe` 14 | 15 | set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] 16 | 17 | # Set shell for non-Windows OSs: 18 | 19 | set shell := ["bash", "-uc"] 20 | 21 | export RUST_BACKTRACE := "1" 22 | export RUST_LOG := "info" 23 | export CI := "1" 24 | 25 | build: 26 | cargo build --all-features 27 | cargo build -r --all-features 28 | 29 | b: build 30 | 31 | check: 32 | cargo check --no-default-features --all-targets --workspace 33 | cargo check --all-features --all-targets --workspace 34 | 35 | c: check 36 | 37 | ci: 38 | just loop . dev 39 | 40 | dev: format lint test lint-deps 41 | 42 | d: dev 43 | 44 | doc: 45 | cargo +stable doc --no-deps --all-features --workspace --examples 46 | 47 | format-dprint: 48 | dprint fmt 49 | 50 | format-cargo: 51 | cargo fmt --all 52 | 53 | format: format-cargo format-dprint 54 | 55 | fmt: format 56 | 57 | rev: 58 | cargo insta review 59 | 60 | inverse-deps crate: 61 | cargo tree -e features -i {{ crate }} 62 | 63 | lint: check 64 | cargo clippy --no-default-features -- -D warnings 65 | cargo clippy --all-targets --all-features -- -D warnings 66 | 67 | lint-deps: 68 | cargo audit 69 | cargo deny check 70 | 71 | loop dir action: 72 | watchexec -w {{ dir }} -- "just {{ action }}" 73 | 74 | test: check lint 75 | cargo test --all-targets --all-features --workspace --examples 76 | 77 | test-ignored: check lint 78 | cargo test --all-targets --all-features --workspace --examples -- --ignored 79 | 80 | t: test test-ignored 81 | 82 | coverage $RUST_BACKTRACE="0": 83 | cargo tarpaulin --verbose --all-features --workspace --timeout 120 --out Lcov --output-dir coverage 84 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in the corresponding 4 | changelog files for each crate. 5 | 6 | - [backend changelog](./crates/backend/CHANGELOG.md) 7 | - [config changelog](./crates/config/CHANGELOG.md) 8 | - [core changelog](./crates/core/CHANGELOG.md) 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to `rustic_core`! 4 | 5 | We appreciate your help in making this project better. 6 | 7 | Please read the 8 | [contribution guide](https://rustic.cli.rs/docs/contributing-to-rustic.html) to 9 | get started. 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/backend", 4 | "crates/config", 5 | "crates/core", 6 | "crates/testing", 7 | "examples/*", 8 | ] 9 | resolver = "3" 10 | 11 | [workspace.package] 12 | rust-version = "1.85.0" 13 | 14 | [workspace.dependencies] 15 | # Internal Dependencies 16 | rustic_backend = { path = "crates/backend", version = "0" } 17 | rustic_core = { path = "crates/core", version = "0" } 18 | rustic_testing = { path = "crates/testing", version = "0" } 19 | 20 | aho-corasick = "1.1.3" 21 | anyhow = "1.0.98" 22 | bytes = "1.10.1" 23 | displaydoc = "0.2.5" 24 | enum-map = "2.7.3" 25 | log = "0.4.27" 26 | simplelog = "0.12.2" 27 | thiserror = "2.0.12" 28 | 29 | # dev-dependencies 30 | rstest = "0.25.0" 31 | tempfile = "3.19.1" 32 | 33 | # see: https://nnethercote.github.io/perf-book/build-configuration.html 34 | [profile.dev] 35 | opt-level = 0 36 | debug = true 37 | rpath = false 38 | lto = false 39 | debug-assertions = true 40 | codegen-units = 4 41 | 42 | # compile dependencies with optimizations in dev mode 43 | # see: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#overrides 44 | [profile.dev.package."*"] 45 | opt-level = 3 46 | debug = true 47 | 48 | [profile.release] 49 | opt-level = 3 50 | debug = false # true for profiling 51 | rpath = false 52 | lto = "fat" 53 | debug-assertions = false 54 | codegen-units = 1 55 | strip = true 56 | panic = "abort" 57 | 58 | [profile.test] 59 | opt-level = 1 60 | debug = true 61 | rpath = false 62 | lto = false 63 | debug-assertions = true 64 | codegen-units = 4 65 | 66 | [profile.bench] 67 | opt-level = 3 68 | debug = true # true for profiling 69 | rpath = false 70 | lto = true 71 | debug-assertions = false 72 | codegen-units = 1 73 | 74 | [workspace.lints.rust] 75 | unsafe_code = "forbid" 76 | missing_docs = "warn" 77 | rust_2018_idioms = { level = "warn", priority = -1 } 78 | trivial_casts = "warn" 79 | unused_lifetimes = "warn" 80 | unused_qualifications = "warn" 81 | bad_style = "warn" 82 | dead_code = "allow" # TODO: "warn" 83 | improper_ctypes = "warn" 84 | missing_copy_implementations = "warn" 85 | missing_debug_implementations = "warn" 86 | non_shorthand_field_patterns = "warn" 87 | no_mangle_generic_items = "warn" 88 | overflowing_literals = "warn" 89 | path_statements = "warn" 90 | patterns_in_fns_without_body = "warn" 91 | trivial_numeric_casts = "warn" 92 | unused_results = "warn" 93 | unused_extern_crates = "warn" 94 | unused_import_braces = "warn" 95 | unconditional_recursion = "warn" 96 | unused = { level = "warn", priority = -1 } 97 | unused_allocation = "warn" 98 | unused_comparisons = "warn" 99 | unused_parens = "warn" 100 | while_true = "warn" 101 | unreachable_pub = "allow" 102 | 103 | [workspace.lints.clippy] 104 | redundant_pub_crate = "allow" 105 | pedantic = { level = "warn", priority = -1 } 106 | nursery = { level = "warn", priority = -1 } 107 | # expect_used = "warn" # TODO! 108 | # unwrap_used = "warn" # TODO! 109 | literal_string_with_formatting_args = "allow" 110 | enum_glob_use = "warn" 111 | correctness = { level = "warn", priority = -1 } 112 | suspicious = { level = "warn", priority = -1 } 113 | complexity = { level = "warn", priority = -1 } 114 | perf = { level = "warn", priority = -1 } 115 | cast_lossless = "warn" 116 | default_trait_access = "warn" 117 | doc_markdown = "warn" 118 | manual_string_new = "warn" 119 | match_same_arms = "warn" 120 | semicolon_if_nothing_returned = "warn" 121 | trivially_copy_pass_by_ref = "warn" 122 | module_name_repetitions = "allow" 123 | # TODO: Remove when Windows support landed 124 | # mostly Windows-related functionality is missing `const` 125 | # as it's only OK(()), but doesn't make it reasonable to 126 | # have a breaking change in the future. They won't be const. 127 | missing_const_for_fn = "allow" 128 | needless_raw_string_hashes = "allow" 129 | 130 | [workspace.lints.rustdoc] 131 | # We run rustdoc with `--document-private-items` so we can document private items 132 | private_intra_doc_links = "allow" 133 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-gnu] 2 | pre-build = [ # additional commands to run prior to building the package 3 | "dpkg --add-architecture $CROSS_DEB_ARCH", 4 | "apt-get update && apt-get --assume-yes install nasm:$CROSS_DEB_ARCH libssl-dev:$CROSS_DEB_ARCH", 5 | ] 6 | 7 | [target.x86_64-pc-windows-gnu.env] 8 | passthrough = [ 9 | "AWS_LC_SYS_PREBUILT_NASM=1", 10 | ] 11 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Alexander Weiss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /build-dependencies.just: -------------------------------------------------------------------------------- 1 | ### DEFAULT ### 2 | 3 | # Install dependencies for the default feature on x86_64-unknown-linux-musl 4 | install-default-x86_64-unknown-linux-musl: 5 | sudo apt-get update 6 | sudo apt-get install -y musl-tools 7 | 8 | # Install dependencies for the default feature on aarch64-unknown-linux-musl 9 | install-default-aarch64-unknown-linux-musl: 10 | sudo apt-get update 11 | sudo apt-get install -y musl-tools 12 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | # changelog header 10 | header = """ 11 | # Changelog\n 12 | All notable changes to this project will be documented in this file.\n 13 | """ 14 | # template for the changelog body 15 | # https://tera.netlify.app/docs 16 | body = """ 17 | {% if version %}\ 18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% else %}\ 20 | ## [unreleased] 21 | {% endif %}\ 22 | {% for group, commits in commits | group_by(attribute="group") %} 23 | ### {{ group | upper_first }} 24 | {% for commit in commits %} 25 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ 26 | {% endfor %} 27 | {% endfor %}\n 28 | """ 29 | # remove the leading and trailing whitespace from the template 30 | trim = true 31 | # changelog footer 32 | footer = """ 33 | 34 | """ 35 | # postprocessors 36 | postprocessors = [ 37 | { pattern = '', replace = "https://github.com/rustic-rs/rustic_core" }, 38 | ] 39 | [git] 40 | # parse the commits based on https://www.conventionalcommits.org 41 | conventional_commits = true 42 | # filter out the commits that are not conventional 43 | filter_unconventional = true 44 | # process each line of a commit as an individual commit 45 | split_commits = false 46 | # regex for preprocessing the commit messages 47 | commit_preprocessors = [ 48 | { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, # replace issue numbers 49 | ] 50 | # regex for parsing and grouping commits 51 | commit_parsers = [ 52 | { message = "^feat", group = "Features" }, 53 | { message = "^fix", group = "Bug Fixes" }, 54 | { message = "^doc", group = "Documentation" }, 55 | { message = "^perf", group = "Performance" }, 56 | { message = "^refactor", group = "Refactor" }, 57 | { message = "^style", group = "Styling", skip = true }, # we ignore styling in the changelog 58 | { message = "^test", group = "Testing" }, 59 | { message = "^chore\\(release\\): prepare for", skip = true }, 60 | { message = "^chore\\(deps\\)", skip = true }, 61 | { message = "^chore\\(pr\\)", skip = true }, 62 | { message = "^chore\\(pull\\)", skip = true }, 63 | { message = "^chore|ci", group = "Miscellaneous Tasks" }, 64 | { body = ".*security", group = "Security" }, 65 | { message = "^revert", group = "Revert" }, 66 | ] 67 | # protect breaking changes from being skipped due to matching a skipping commit_parser 68 | protect_breaking_commits = false 69 | # filter out the commits that are not matched by commit parsers 70 | filter_commits = false 71 | # glob pattern for matching git tags 72 | tag_pattern = "v[0-9]*" 73 | # regex for skipping tags 74 | skip_tags = "v0.1.0-beta.1" 75 | # regex for ignoring tags 76 | ignore_tags = "" 77 | # sort the tags topologically 78 | topo_order = false 79 | # sort the commits inside sections by oldest/newest order 80 | sort_commits = "oldest" 81 | # limit the number of commits included in the changelog. 82 | # limit_commits = 42 83 | -------------------------------------------------------------------------------- /coverage/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustic-rs/rustic_core/213665e7a5d1a27df91b29b122b35aac24a76a83/coverage/.gitkeep -------------------------------------------------------------------------------- /crates/backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustic_backend" 3 | version = "0.5.2" 4 | authors = ["the rustic-rs team"] 5 | categories = ["data-structures", "filesystem"] 6 | documentation = "https://docs.rs/rustic_backend" 7 | edition = "2024" 8 | homepage = "https://rustic.cli.rs/" 9 | include = ["src/**/*", "LICENSE-*", "README.md"] 10 | keywords = ["backup", "restic", "deduplication", "encryption", "library"] 11 | license = "Apache-2.0 OR MIT" 12 | publish = true 13 | readme = "README.md" 14 | repository = "https://github.com/rustic-rs/rustic_core/tree/main/crates/backend" 15 | resolver = "3" 16 | rust-version = { workspace = true } 17 | description = """ 18 | rustic_backend - library for supporting various backends in rustic-rs 19 | """ 20 | 21 | [lib] 22 | path = "src/lib.rs" 23 | name = "rustic_backend" 24 | test = true 25 | doctest = true 26 | bench = true 27 | doc = true 28 | harness = true 29 | 30 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 31 | 32 | [features] 33 | default = ["opendal", "rest", "rclone"] 34 | cli = ["merge", "clap"] 35 | merge = ["dep:conflate"] 36 | clap = ["dep:clap"] 37 | opendal = [ 38 | "dep:opendal", 39 | "dep:rayon", 40 | "dep:tokio", 41 | "tokio/rt-multi-thread", 42 | "dep:typed-path", 43 | ] 44 | rest = ["dep:reqwest", "dep:backon"] 45 | rclone = ["rest", "dep:rand", "dep:semver"] 46 | 47 | [dependencies] 48 | # core 49 | rustic_core = { workspace = true } 50 | 51 | # errors 52 | displaydoc = { workspace = true } 53 | thiserror = { workspace = true } 54 | 55 | # logging 56 | log = { workspace = true } 57 | 58 | # other dependencies 59 | bytes = { workspace = true } 60 | derive_setters = "0.1.6" 61 | humantime = "2.2.0" 62 | itertools = "0.14.0" 63 | strum = "0.27" 64 | strum_macros = "0.27" 65 | 66 | # general / backend choosing 67 | hex = { version = "0.4.3", features = ["serde"] } 68 | serde = { version = "1.0.219" } 69 | url = "2.5.4" 70 | 71 | # cli support 72 | clap = { version = "4.5.37", optional = true, features = ["derive", "env", "wrap_help"] } 73 | conflate = { version = "0.3.3", optional = true } 74 | 75 | # local backend 76 | aho-corasick = { workspace = true } 77 | walkdir = "2.5.0" 78 | 79 | # rest backend 80 | backon = { version = "1.5.0", optional = true } 81 | reqwest = { version = "0.12.15", default-features = false, features = ["json", "rustls-tls-native-roots", "stream", "blocking"], optional = true } 82 | 83 | # rclone backend 84 | rand = { version = "0.9.1", optional = true } 85 | semver = { version = "1.0.26", optional = true } 86 | 87 | # opendal backend 88 | bytesize = "2.0.1" 89 | rayon = { version = "1.10.0", optional = true } 90 | tokio = { version = "1.44.2", optional = true, default-features = false } 91 | typed-path = { version = "0.10.0", optional = true } 92 | 93 | [target.'cfg(not(windows))'.dependencies] 94 | # opendal backend 95 | # - sftp is not supported on windows, see https://github.com/apache/incubator-opendal/issues/2963 96 | # - ftp is temporarily disabled due to dependency on aws-lc-sys 97 | opendal = { version = "0.53.1", features = ["services-b2", "services-sftp", "services-swift", "services-azblob", "services-azdls", "services-cos", "services-fs", "services-dropbox", "services-gdrive", "services-gcs", "services-ghac", "services-http", "services-ipmfs", "services-memory", "services-obs", "services-onedrive", "services-oss", "services-s3", "services-webdav", "services-webhdfs", "services-azfile", "layers-blocking", "layers-throttle", "services-yandex-disk"], optional = true } 98 | 99 | [target.'cfg(windows)'.dependencies] 100 | # opendal backend 101 | # - ftp is temporarily disabled due to dependency on aws-lc-sys 102 | opendal = { version = "0.53.1", features = ["services-b2", "services-swift", "services-azblob", "services-azdls", "services-cos", "services-fs", "services-dropbox", "services-gdrive", "services-gcs", "services-ghac", "services-http", "services-ipmfs", "services-memory", "services-obs", "services-onedrive", "services-oss", "services-s3", "services-webdav", "services-webhdfs", "services-azfile", "layers-blocking", "layers-throttle", "services-yandex-disk"], optional = true } 103 | 104 | [dev-dependencies] 105 | anyhow = { workspace = true } 106 | rstest = { workspace = true } 107 | toml = "0.8.20" 108 | 109 | [lints] 110 | workspace = true 111 | -------------------------------------------------------------------------------- /crates/backend/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Alexander Weiss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | A library for supporting various backends in rustic. 3 | 4 | # Overview 5 | 6 | This section gives a brief overview of the primary types in this crate: 7 | 8 | `rustic_backend` is a support crate for `rustic_core` which provides a way to access a 9 | repository using different backends. 10 | 11 | The primary types in this crate are: 12 | 13 | - `BackendOptions` - A struct for configuring options for a used backend. 14 | - `SupportedBackend` - An enum for the supported backends. 15 | 16 | The following backends are currently supported and can be enabled with features: 17 | 18 | - `LocalBackend` - Backend for accessing a local filesystem. 19 | - `OpenDALBackend` - Backend for accessing a `OpenDAL` filesystem. 20 | - `RcloneBackend` - Backend for accessing a Rclone filesystem. 21 | - `RestBackend` - Backend for accessing a REST API. 22 | 23 | ## Usage & Examples 24 | 25 | Due to being a support crate for `rustic_core`, there are no examples here. 26 | Please check the examples in the [`rustic_core`](https://crates.io/crates/rustic_core) crate. 27 | 28 | ## Crate features 29 | 30 | This crate exposes a few features for controlling dependency usage: 31 | 32 | - **cli** - Enables support for CLI features by enabling `merge` and `clap` 33 | features. *This feature is disabled by default*. 34 | 35 | - **clap** - Enables a dependency on the `clap` crate and enables parsing from 36 | the commandline. *This feature is disabled by default*. 37 | 38 | - **merge** - Enables support for merging multiple values into one, which 39 | enables the `conflate` dependency. This is needed for parsing commandline 40 | arguments and merging them into one (e.g. `config`). *This feature is disabled 41 | by default*. 42 | 43 | ### Backend-related features 44 | 45 | - **opendal** - Enables support for the `opendal` backend. *This feature is 46 | enabled by default*. 47 | 48 | - **rclone** - Enables support for the `rclone` backend. *This feature is 49 | enabled by default*. 50 | 51 | - **rest** - Enables support for the `rest` backend. *This feature is enabled by 52 | default*. 53 | */ 54 | 55 | // formatting args are used for error messages 56 | #![allow(clippy::literal_string_with_formatting_args)] 57 | 58 | pub mod choose; 59 | /// Local backend for Rustic. 60 | pub mod local; 61 | /// Utility functions for the backend. 62 | pub mod util; 63 | 64 | /// `OpenDAL` backend for Rustic. 65 | #[cfg(feature = "opendal")] 66 | pub mod opendal; 67 | 68 | /// `Rclone` backend for Rustic. 69 | #[cfg(feature = "rclone")] 70 | pub mod rclone; 71 | 72 | /// REST backend for Rustic. 73 | #[cfg(feature = "rest")] 74 | pub mod rest; 75 | 76 | #[cfg(feature = "opendal")] 77 | pub use crate::opendal::OpenDALBackend; 78 | 79 | #[cfg(feature = "rclone")] 80 | pub use crate::rclone::RcloneBackend; 81 | 82 | #[cfg(feature = "rest")] 83 | pub use crate::rest::RestBackend; 84 | 85 | // rustic_backend Public API 86 | pub use crate::{ 87 | choose::{BackendOptions, SupportedBackend}, 88 | local::LocalBackend, 89 | }; 90 | 91 | // re-export for error handling 92 | pub use rustic_core::{ErrorKind, RusticError, RusticResult, Severity, Status}; 93 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/b2.toml: -------------------------------------------------------------------------------- 1 | path = "b2" 2 | [options] 3 | application_key_id = "my_id" # B2 application key ID 4 | application_key = "my_key" # B2 application key secret. Can be also set using OPENDAL_APPLICATION_KEY 5 | bucket = "bucket_name" # B2 bucket name 6 | bucket_id = "bucket_id" # B2 bucket ID 7 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/connections_20.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | region = "test_region" 4 | bucket = "bucket_name" 5 | connections = "20" 6 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/retry_20.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | region = "test_region" 4 | bucket = "bucket_name" 5 | retry = "20" 6 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/retry_default.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | region = "test_region" 4 | bucket = "bucket_name" 5 | retry = "default" 6 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/retry_off.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | region = "test_region" 4 | bucket = "bucket_name" 5 | retry = "off" 6 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/s3_aws.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | access_key_id = "xxx" 4 | secret_access_key = "xxx" 5 | region = "test_region" 6 | bucket = "bucket_name" 7 | root = "/path/to/repo" 8 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/s3_idrive.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | root = "/" 4 | bucket = "bucket_name" 5 | endpoint = "https://p7v1.ldn.idrivee2-40.com" 6 | region = "auto" 7 | access_key_id = "xxx" 8 | secret_access_key = "xxx" 9 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/throttle.toml: -------------------------------------------------------------------------------- 1 | path = "s3" 2 | [options] 3 | region = "test_region" 4 | bucket = "bucket_name" 5 | throttle = "20kB,5MiB" 6 | -------------------------------------------------------------------------------- /crates/backend/tests/fixtures/opendal/webdav_owncloud_nextcloud.toml: -------------------------------------------------------------------------------- 1 | path = "webdav" 2 | [options] 3 | endpoint = "https://my-owncloud-or-nextcloud-server.com" 4 | username = "user" 5 | password = "token" 6 | -------------------------------------------------------------------------------- /crates/config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [unreleased] 6 | 7 | ## [0.2.2](https://github.com/rustic-rs/rustic_core/compare/rustic_config-v0.2.1...rustic_config-v0.2.2) - 2024-10-03 8 | 9 | ### Fixed 10 | 11 | - *(docs)* left over from migration to `conflate` crate ([#296](https://github.com/rustic-rs/rustic_core/pull/296)) 12 | 13 | ## [0.2.1](https://github.com/rustic-rs/rustic_core/compare/rustic_config-v0.2.0...rustic_config-v0.2.1) - 2024-09-23 14 | 15 | ### Other 16 | 17 | - remove readme versions in usage section for easier release due to release PR ([#271](https://github.com/rustic-rs/rustic_core/pull/271)) 18 | 19 | ## [0.2.0](https://github.com/rustic-rs/rustic_core/compare/rustic_config-v0.1.0...rustic_config-v0.2.0) - 2024-08-18 20 | 21 | ### Added 22 | - [**breaking**] move clippy lints to cargo manifest and fix upcoming issues all over the workspace ([#176](https://github.com/rustic-rs/rustic_core/pull/176)) 23 | 24 | ### Other 25 | - Update MSRV to 1.76.0 26 | - Update MSRV (needed by opendal) 27 | - Update MSRV to 1.73.0 28 | - add changelog for rustic_config 29 | 30 | ## [rustic_backend-v0.1.0] - 2024-02-04 31 | 32 | - Reserving name on crates.io 33 | 34 | 35 | -------------------------------------------------------------------------------- /crates/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustic_config" 3 | version = "0.2.2" 4 | authors = ["the rustic-rs team"] 5 | categories = ["config"] 6 | documentation = "https://docs.rs/rustic_config" 7 | edition = "2024" 8 | homepage = "https://rustic.cli.rs/" 9 | include = ["src/**/*", "LICENSE-*", "README.md"] 10 | keywords = ["backup", "restic", "deduplication", "encryption", "library"] 11 | license = "Apache-2.0 OR MIT" 12 | publish = true 13 | readme = "README.md" 14 | repository = "https://github.com/rustic-rs/rustic_core" 15 | resolver = "3" 16 | rust-version = { workspace = true } 17 | description = """ 18 | rustic_config - library for configuration support in rustic-rs 19 | """ 20 | 21 | [dependencies] 22 | 23 | [lints] 24 | workspace = true 25 | -------------------------------------------------------------------------------- /crates/config/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Alexander Weiss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/config/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

Library for configuration support in rustic

5 |

6 | 7 | 8 | 9 | 10 |

11 | 12 | ## About 13 | 14 | This library is a part of the [rustic](https://rustic.cli.rs) project and 15 | provides a set of types for the 16 | [`rustic_core`](https://crates.io/crates/rustic_core) library. It is used to 17 | collect everything regarding configuration for backends, CLI, and other related 18 | functionality. 19 | 20 | **Note**: `rustic_config` is in an early development stage and its API is 21 | subject to change in the next releases. If you want to give feedback on that, 22 | please open an [issue](https://github.com/rustic-rs/rustic_core/issues). 23 | 24 | ## Contact 25 | 26 | You can ask questions in the 27 | [Discussions](https://github.com/rustic-rs/rustic/discussions) or have a look at 28 | the [FAQ](https://rustic.cli.rs/docs/FAQ.html). 29 | 30 | | Contact | Where? | 31 | | ------------- | --------------------------------------------------------------------------------------------------------------- | 32 | | Issue Tracker | [GitHub Issues](https://github.com/rustic-rs/rustic_core/issues/choose) | 33 | | Discord | [![Discord](https://dcbadge.vercel.app/api/server/WRUWENZnzQ?style=flat-square)](https://discord.gg/WRUWENZnzQ) | 34 | | Discussions | [GitHub Discussions](https://github.com/rustic-rs/rustic/discussions) | 35 | 36 | ## Usage 37 | 38 | Add this to your `Cargo.toml`: 39 | 40 | ```toml 41 | [dependencies] 42 | rustic_config = "*" 43 | ``` 44 | 45 | 59 | 60 | ## Usage & Examples 61 | 62 | Due to being a support crate for 63 | [`rustic_core`](https://crates.io/crates/rustic_core), there are no examples 64 | here. Please check the examples in the 65 | [`rustic_core`](https://crates.io/crates/rustic_core) crate. 66 | 67 | ## Contributing 68 | 69 | Found a bug? 70 | [Open an issue!](https://github.com/rustic-rs/rustic_core/issues/choose) 71 | 72 | Got an idea for an improvement? Don't keep it to yourself! 73 | 74 | - [Contribute fixes](https://github.com/rustic-rs/rustic_core/contribute) or new 75 | features via a pull requests! 76 | 77 | Please make sure, that you read the 78 | [contribution guide](https://rustic.cli.rs/docs/contributing-to-rustic.html). 79 | 80 | ## Minimum Rust version policy 81 | 82 | This crate's minimum supported `rustc` version is `1.85.0`. 83 | 84 | The current policy is that the minimum Rust version required to use this crate 85 | can be increased in minor version updates. For example, if `crate 1.0` requires 86 | Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust 87 | 1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum 88 | version of Rust. 89 | 90 | In general, this crate will be conservative with respect to the minimum 91 | supported version of Rust. 92 | 93 | ## License 94 | 95 | Licensed under either of: 96 | 97 | - [Apache License, Version 2.0](./LICENSE-APACHE) 98 | - [MIT license](./LICENSE-MIT) 99 | -------------------------------------------------------------------------------- /crates/config/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Unused crate for now 2 | -------------------------------------------------------------------------------- /crates/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustic_core" 3 | version = "0.7.3" 4 | authors = ["the rustic-rs team"] 5 | categories = ["data-structures", "encoding", "filesystem"] 6 | documentation = "https://docs.rs/rustic_core" 7 | edition = "2024" 8 | homepage = "https://rustic.cli.rs/" 9 | include = ["src/**/*", "LICENSE-*", "README.md"] 10 | keywords = ["backup", "restic", "deduplication", "encryption", "library"] 11 | license = "Apache-2.0 OR MIT" 12 | publish = true 13 | readme = "README.md" 14 | repository = "https://github.com/rustic-rs/rustic_core" 15 | resolver = "3" 16 | rust-version = { workspace = true } 17 | description = """ 18 | rustic_core - library for fast, encrypted, deduplicated backups that powers rustic-rs 19 | """ 20 | 21 | [lib] 22 | path = "src/lib.rs" 23 | name = "rustic_core" 24 | test = true 25 | doctest = true 26 | bench = true 27 | doc = true 28 | harness = true 29 | 30 | [features] 31 | default = [] 32 | cli = ["merge", "clap"] 33 | merge = ["dep:conflate"] 34 | clap = ["dep:clap"] 35 | 36 | [package.metadata.docs.rs] 37 | all-features = true 38 | rustdoc-args = ["--document-private-items", "--generate-link-to-definition"] 39 | 40 | [dependencies] 41 | # errors 42 | displaydoc = { workspace = true } 43 | thiserror = { workspace = true } 44 | 45 | # macros 46 | derive_more = { version = "2.0.1", features = ["add", "constructor", "display", "from", "deref", "from_str"] } 47 | derive_setters = "0.1.6" 48 | 49 | # logging 50 | log = { workspace = true } 51 | 52 | # parallelize 53 | crossbeam-channel = "0.5.15" 54 | pariter = "0.5.1" 55 | rayon = "1.10.0" 56 | 57 | # crypto 58 | aes256ctr_poly1305aes = { version = "0.2.1", features = ["std"] } # we need std here for error impls 59 | rand = "0.9.1" 60 | scrypt = { version = "0.11.0", default-features = false, features = ["std"] } # we need std here for error impls 61 | 62 | # serialization / packing 63 | binrw = "0.14.1" 64 | hex = { version = "0.4.3", features = ["serde"] } 65 | integer-sqrt = "0.1.5" 66 | rustic_cdc = "0.3.1" 67 | serde = { version = "1.0.219" } 68 | serde-aux = "4.6.0" 69 | serde_derive = "1.0.219" 70 | serde_json = "1.0.140" 71 | serde_with = { version = "3.12.0", features = ["base64"] } 72 | 73 | # local source/destination 74 | cached = { version = "0.55.1", default-features = false, features = ["proc_macro"] } 75 | dunce = "1.0.5" 76 | filetime = "0.2.25" 77 | ignore = "0.4.23" 78 | nix = { version = "0.29.0", default-features = false, features = ["user", "fs"] } 79 | path-dedot = "3.1.1" 80 | walkdir = "2.5.0" 81 | 82 | # cache 83 | cachedir = "0.3.1" 84 | dirs = "6.0.0" 85 | 86 | # cli support 87 | clap = { version = "4.5.37", optional = true, features = ["derive", "env", "wrap_help"] } 88 | conflate = { version = "0.3.3", optional = true } 89 | 90 | # vfs support 91 | runtime-format = "0.1.3" 92 | 93 | # other dependencies 94 | bytes = { workspace = true } 95 | bytesize = "2.0.1" 96 | chrono = { version = "0.4.40", default-features = false, features = ["clock", "serde"] } 97 | ecow = "0.2.4" 98 | enum-map = { workspace = true } 99 | enum-map-derive = "0.17.0" 100 | enumset = { version = "1.1.5", features = ["serde"] } 101 | gethostname = "1.0.1" 102 | humantime = "2.2.0" 103 | itertools = "0.14.0" 104 | quick_cache = "0.6.13" 105 | shell-words = "1.1.0" 106 | strum = { version = "0.27.1", features = ["derive"] } 107 | zstd = "0.13.3" 108 | 109 | [target.'cfg(not(windows))'.dependencies] 110 | sha2 = { version = "0.10.8", features = ["asm"] } 111 | 112 | [target.'cfg(windows)'.dependencies] 113 | # unfortunately, the asm extensions do not build on Windows, see https://github.com/RustCrypto/asm-hashes/issues/17 114 | # and https://github.com/RustCrypto/asm-hashes/pull/issues/78 115 | sha2 = "0.10.8" 116 | 117 | [target.'cfg(not(any(windows, target_os="openbsd")))'.dependencies] 118 | # for local source/destination 119 | xattr = "1" 120 | 121 | [dev-dependencies] 122 | anyhow = { workspace = true } 123 | expect-test = "1.5.1" 124 | flate2 = "1.1.1" 125 | globset = "0.4.16" 126 | insta = { version = "1.42.2", features = ["redactions", "ron"] } 127 | mockall = "0.13" 128 | pretty_assertions = "1.4.1" 129 | quickcheck = "1.0.3" 130 | quickcheck_macros = "1.0.0" 131 | rstest = { workspace = true } 132 | rustdoc-json = "0.9.5" 133 | # We need to have rustic_backend here, because the doc-tests in lib.rs of rustic_core 134 | rustic_backend = { workspace = true } 135 | rustic_testing = { workspace = true } 136 | rustup-toolchain = "0.1.10" 137 | simplelog = "0.12.2" 138 | tar = "0.4.44" 139 | tempfile = { workspace = true } 140 | toml = "0.8.20" 141 | 142 | [lints] 143 | workspace = true 144 | -------------------------------------------------------------------------------- /crates/core/src/backend/childstdout.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | iter::{Once, once}, 3 | path::PathBuf, 4 | process::{Child, ChildStdout, Command, Stdio}, 5 | sync::Mutex, 6 | }; 7 | 8 | use crate::{ 9 | backend::{ReadSource, ReadSourceEntry}, 10 | error::{ErrorKind, RusticError, RusticResult}, 11 | repository::command_input::{CommandInput, CommandInputErrorKind}, 12 | }; 13 | 14 | /// The `ChildStdoutSource` is a `ReadSource` when spawning a child process and reading its stdout 15 | #[derive(Debug)] 16 | pub struct ChildStdoutSource { 17 | /// The path of the stdin entry. 18 | path: PathBuf, 19 | /// The child process 20 | /// 21 | /// # Note 22 | /// 23 | /// This is in a Mutex as we want to take out `ChildStdout` 24 | /// in the `entries` method - but this method only gets a 25 | /// reference of self. 26 | process: Mutex, 27 | /// the command which is called 28 | command: CommandInput, 29 | } 30 | 31 | impl ChildStdoutSource { 32 | /// Creates a new `ChildSource`. 33 | pub fn new(cmd: &CommandInput, path: PathBuf) -> RusticResult { 34 | let process = Command::new(cmd.command()) 35 | .args(cmd.args()) 36 | .stdout(Stdio::piped()) 37 | .spawn() 38 | .map_err(|err| CommandInputErrorKind::ProcessExecutionFailed { 39 | command: cmd.clone(), 40 | path: path.clone(), 41 | source: err, 42 | }); 43 | 44 | let process = cmd.on_failure().display_result(process)?; 45 | 46 | Ok(Self { 47 | path, 48 | process: Mutex::new(process), 49 | command: cmd.clone(), 50 | }) 51 | } 52 | 53 | /// Finishes the `ChildSource` 54 | pub fn finish(self) -> RusticResult<()> { 55 | let status = self.process.lock().unwrap().wait(); 56 | self.command 57 | .on_failure() 58 | .handle_status(status, "stdin-command", "call")?; 59 | Ok(()) 60 | } 61 | } 62 | 63 | impl ReadSource for ChildStdoutSource { 64 | type Open = ChildStdout; 65 | type Iter = Once>>; 66 | 67 | fn size(&self) -> RusticResult> { 68 | Ok(None) 69 | } 70 | 71 | fn entries(&self) -> Self::Iter { 72 | let open = self.process.lock().unwrap().stdout.take(); 73 | once( 74 | ReadSourceEntry::from_path(self.path.clone(), open).map_err(|err| { 75 | RusticError::with_source( 76 | ErrorKind::Backend, 77 | "Failed to create ReadSourceEntry from ChildStdout", 78 | err, 79 | ) 80 | }), 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/core/src/backend/hotcold.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::{ 6 | backend::{FileType, ReadBackend, WriteBackend}, 7 | error::RusticResult, 8 | id::Id, 9 | }; 10 | 11 | /// A hot/cold backend implementation. 12 | /// 13 | /// # Type Parameters 14 | /// 15 | /// * `BE` - The backend to use. 16 | #[derive(Clone, Debug)] 17 | pub struct HotColdBackend { 18 | /// The backend to use. 19 | be: Arc, 20 | /// The backend to use for hot files. 21 | be_hot: Arc, 22 | } 23 | 24 | impl HotColdBackend { 25 | /// Creates a new `HotColdBackend`. 26 | /// 27 | /// # Type Parameters 28 | /// 29 | /// * `BE` - The backend to use. 30 | /// 31 | /// # Arguments 32 | /// 33 | /// * `be` - The backend to use. 34 | /// * `hot_be` - The backend to use for hot files. 35 | pub fn new(be: BE, hot_be: BE) -> Self { 36 | Self { 37 | be: Arc::new(be), 38 | be_hot: Arc::new(hot_be), 39 | } 40 | } 41 | } 42 | 43 | impl ReadBackend for HotColdBackend { 44 | fn location(&self) -> String { 45 | self.be.location() 46 | } 47 | 48 | fn list_with_size(&self, tpe: FileType) -> RusticResult> { 49 | self.be.list_with_size(tpe) 50 | } 51 | 52 | fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { 53 | self.be_hot.read_full(tpe, id) 54 | } 55 | 56 | fn read_partial( 57 | &self, 58 | tpe: FileType, 59 | id: &Id, 60 | cacheable: bool, 61 | offset: u32, 62 | length: u32, 63 | ) -> RusticResult { 64 | if cacheable || tpe != FileType::Pack { 65 | self.be_hot.read_partial(tpe, id, cacheable, offset, length) 66 | } else { 67 | self.be.read_partial(tpe, id, cacheable, offset, length) 68 | } 69 | } 70 | 71 | fn needs_warm_up(&self) -> bool { 72 | self.be.needs_warm_up() 73 | } 74 | 75 | fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> { 76 | self.be.warm_up(tpe, id) 77 | } 78 | } 79 | 80 | impl WriteBackend for HotColdBackend { 81 | fn create(&self) -> RusticResult<()> { 82 | self.be.create()?; 83 | self.be_hot.create() 84 | } 85 | 86 | fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { 87 | if tpe != FileType::Config && (cacheable || tpe != FileType::Pack) { 88 | self.be_hot.write_bytes(tpe, id, cacheable, buf.clone())?; 89 | } 90 | self.be.write_bytes(tpe, id, cacheable, buf) 91 | } 92 | 93 | fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { 94 | // First remove cold file 95 | self.be.remove(tpe, id, cacheable)?; 96 | if cacheable || tpe != FileType::Pack { 97 | self.be_hot.remove(tpe, id, cacheable)?; 98 | } 99 | Ok(()) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /crates/core/src/backend/stdin.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Stdin, stdin}, 3 | iter::{Once, once}, 4 | path::PathBuf, 5 | }; 6 | 7 | use crate::{ 8 | backend::{ReadSource, ReadSourceEntry}, 9 | error::{ErrorKind, RusticError, RusticResult}, 10 | }; 11 | 12 | /// The `StdinSource` is a `ReadSource` for stdin. 13 | #[derive(Debug, Clone)] 14 | pub struct StdinSource { 15 | /// The path of the stdin entry. 16 | path: PathBuf, 17 | } 18 | 19 | impl StdinSource { 20 | /// Creates a new `StdinSource`. 21 | pub const fn new(path: PathBuf) -> Self { 22 | Self { path } 23 | } 24 | } 25 | 26 | impl ReadSource for StdinSource { 27 | /// The open type. 28 | type Open = Stdin; 29 | /// The iterator type. 30 | type Iter = Once>>; 31 | 32 | /// Returns the size of the source. 33 | fn size(&self) -> RusticResult> { 34 | Ok(None) 35 | } 36 | 37 | /// Returns an iterator over the source. 38 | fn entries(&self) -> Self::Iter { 39 | let open = Some(stdin()); 40 | once( 41 | ReadSourceEntry::from_path(self.path.clone(), open).map_err(|err| { 42 | RusticError::with_source( 43 | ErrorKind::Backend, 44 | "Failed to create ReadSourceEntry from Stdin", 45 | err, 46 | ) 47 | }), 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/core/src/backend/warm_up.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::{ 6 | backend::{FileType, ReadBackend, WriteBackend}, 7 | error::RusticResult, 8 | id::Id, 9 | }; 10 | 11 | /// A backend which warms up files by simply accessing them. 12 | #[derive(Clone, Debug)] 13 | pub struct WarmUpAccessBackend { 14 | /// The backend to use. 15 | be: Arc, 16 | } 17 | 18 | impl WarmUpAccessBackend { 19 | /// Creates a new `WarmUpAccessBackend`. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `be` - The backend to use. 24 | pub fn new_warm_up(be: Arc) -> Arc { 25 | Arc::new(Self { be }) 26 | } 27 | } 28 | 29 | impl ReadBackend for WarmUpAccessBackend { 30 | fn location(&self) -> String { 31 | self.be.location() 32 | } 33 | 34 | fn list_with_size(&self, tpe: FileType) -> RusticResult> { 35 | self.be.list_with_size(tpe) 36 | } 37 | 38 | fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { 39 | self.be.read_full(tpe, id) 40 | } 41 | 42 | fn read_partial( 43 | &self, 44 | tpe: FileType, 45 | id: &Id, 46 | cacheable: bool, 47 | offset: u32, 48 | length: u32, 49 | ) -> RusticResult { 50 | self.be.read_partial(tpe, id, cacheable, offset, length) 51 | } 52 | 53 | fn needs_warm_up(&self) -> bool { 54 | true 55 | } 56 | 57 | fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> { 58 | // warm up files by accessing them - error is ignored as we expect this to error out! 59 | _ = self.be.read_partial(tpe, id, false, 0, 1); 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl WriteBackend for WarmUpAccessBackend { 65 | fn create(&self) -> RusticResult<()> { 66 | self.be.create() 67 | } 68 | 69 | fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> { 70 | self.be.write_bytes(tpe, id, cacheable, buf) 71 | } 72 | 73 | fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> { 74 | // First remove cold file 75 | self.be.remove(tpe, id, cacheable) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/core/src/blob.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod packer; 2 | pub(crate) mod tree; 3 | 4 | use derive_more::Constructor; 5 | use enum_map::{Enum, EnumMap}; 6 | use serde_derive::{Deserialize, Serialize}; 7 | 8 | use crate::define_new_id_struct; 9 | 10 | /// All [`BlobType`]s which are supported by the repository 11 | pub const ALL_BLOB_TYPES: [BlobType; 2] = [BlobType::Tree, BlobType::Data]; 12 | 13 | #[derive( 14 | Serialize, 15 | Deserialize, 16 | Clone, 17 | Copy, 18 | Debug, 19 | PartialEq, 20 | Eq, 21 | PartialOrd, 22 | Ord, 23 | Hash, 24 | Enum, 25 | derive_more::Display, 26 | )] 27 | /// The type a `blob` or a `packfile` can have 28 | pub enum BlobType { 29 | #[serde(rename = "tree")] 30 | /// This is a tree blob 31 | Tree, 32 | #[serde(rename = "data")] 33 | /// This is a data blob 34 | Data, 35 | } 36 | 37 | impl BlobType { 38 | /// Defines the cacheability of a [`BlobType`] 39 | /// 40 | /// # Returns 41 | /// 42 | /// `true` if the [`BlobType`] is cacheable, `false` otherwise 43 | #[must_use] 44 | pub(crate) const fn is_cacheable(self) -> bool { 45 | match self { 46 | Self::Tree => true, 47 | Self::Data => false, 48 | } 49 | } 50 | } 51 | 52 | pub type BlobTypeMap = EnumMap; 53 | 54 | /// Initialize is a new trait to define the method `init()` for a [`BlobTypeMap`] 55 | pub trait Initialize { 56 | /// Initialize a [`BlobTypeMap`] by processing a given function for each [`BlobType`] 57 | fn init T>(init: F) -> BlobTypeMap; 58 | } 59 | 60 | impl Initialize for BlobTypeMap { 61 | /// Initialize a [`BlobTypeMap`] by processing a given function for each [`BlobType`] 62 | /// 63 | /// # Arguments 64 | /// 65 | /// * `init` - The function to process for each [`BlobType`] 66 | /// 67 | /// # Returns 68 | /// 69 | /// A [`BlobTypeMap`] with the result of the function for each [`BlobType`] 70 | fn init T>(mut init: F) -> Self { 71 | let mut btm = Self::default(); 72 | for i in 0..BlobType::LENGTH { 73 | let bt = BlobType::from_usize(i); 74 | btm[bt] = init(bt); 75 | } 76 | btm 77 | } 78 | } 79 | 80 | define_new_id_struct!(BlobId, "blob"); 81 | 82 | /// A marker trait for Ids which identify Blobs in pack files 83 | pub trait PackedId: Copy + Into + From { 84 | /// The `BlobType` of the blob identified by the Id 85 | const TYPE: BlobType; 86 | } 87 | 88 | #[macro_export] 89 | /// Generate newtypes for `Id`s identifying packed blobs 90 | macro_rules! impl_blobid { 91 | ($a:ident, $b: expr) => { 92 | $crate::define_new_id_struct!($a, concat!("blob of type", stringify!($b))); 93 | impl From<$crate::blob::BlobId> for $a { 94 | fn from(id: $crate::blob::BlobId) -> Self { 95 | (*id).into() 96 | } 97 | } 98 | impl From<$a> for $crate::blob::BlobId { 99 | fn from(id: $a) -> Self { 100 | (*id).into() 101 | } 102 | } 103 | impl $crate::blob::PackedId for $a { 104 | const TYPE: $crate::blob::BlobType = $b; 105 | } 106 | }; 107 | } 108 | 109 | impl_blobid!(DataId, BlobType::Data); 110 | 111 | /// A `Blob` is a file that is stored in the backend. 112 | /// 113 | /// It can be a `tree` or a `data` blob. 114 | /// 115 | /// A `tree` blob is a file that contains a list of other blobs. 116 | /// A `data` blob is a file that contains the actual data. 117 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Constructor)] 118 | pub(crate) struct Blob { 119 | /// The type of the blob 120 | tpe: BlobType, 121 | 122 | /// The id of the blob 123 | id: BlobId, 124 | } 125 | -------------------------------------------------------------------------------- /crates/core/src/commands.rs: -------------------------------------------------------------------------------- 1 | //! The commands that can be run by the CLI. 2 | 3 | pub mod backup; 4 | /// The `cat` command. 5 | pub mod cat; 6 | pub mod check; 7 | pub mod config; 8 | /// The `copy` command. 9 | pub mod copy; 10 | /// The `dump` command. 11 | pub mod dump; 12 | pub mod forget; 13 | pub mod init; 14 | pub mod key; 15 | pub mod merge; 16 | pub mod prune; 17 | /// The `repair` command. 18 | pub mod repair; 19 | /// The `repoinfo` command. 20 | pub mod repoinfo; 21 | pub mod restore; 22 | pub mod snapshots; 23 | -------------------------------------------------------------------------------- /crates/core/src/commands/cat.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::{ 6 | backend::{FileType, FindInBackend, decrypt::DecryptReadBackend}, 7 | blob::{BlobId, BlobType, tree::Tree}, 8 | error::{ErrorKind, RusticError, RusticResult}, 9 | index::ReadIndex, 10 | progress::ProgressBars, 11 | repofile::SnapshotFile, 12 | repository::{IndexedFull, IndexedTree, Open, Repository}, 13 | }; 14 | 15 | /// Prints the contents of a file. 16 | /// 17 | /// # Type Parameters 18 | /// 19 | /// * `P` - The progress bar type. 20 | /// * `S` - The state the repository is in. 21 | /// 22 | /// # Arguments 23 | /// 24 | /// * `repo` - The repository to read from. 25 | /// * `tpe` - The type of the file. 26 | /// * `id` - The id of the file. 27 | /// 28 | /// # Errors 29 | /// 30 | /// * If the string is not a valid hexadecimal string 31 | /// * If no id could be found. 32 | /// * If the id is not unique. 33 | /// 34 | /// # Returns 35 | /// 36 | /// The data read. 37 | pub(crate) fn cat_file( 38 | repo: &Repository, 39 | tpe: FileType, 40 | id: &str, 41 | ) -> RusticResult { 42 | let id = repo.dbe().find_id(tpe, id)?; 43 | let data = repo.dbe().read_encrypted_full(tpe, &id)?; 44 | Ok(data) 45 | } 46 | 47 | // TODO: Add documentation! 48 | /// 49 | /// # Type Parameters 50 | /// 51 | /// * `P` - The progress bar type. 52 | /// * `S` - The type of the indexed tree. 53 | /// 54 | /// # Arguments 55 | /// 56 | /// * `repo` - The repository to read from. 57 | /// * `tpe` - The type of the file. 58 | /// * `id` - The id of the file. 59 | /// 60 | /// # Errors 61 | /// 62 | /// * If the string is not a valid hexadecimal string 63 | pub(crate) fn cat_blob( 64 | repo: &Repository, 65 | tpe: BlobType, 66 | id: &str, 67 | ) -> RusticResult { 68 | let id = id.parse()?; 69 | let data = repo.index().blob_from_backend(repo.dbe(), tpe, &id)?; 70 | 71 | Ok(data) 72 | } 73 | 74 | /// Prints the contents of a tree. 75 | /// 76 | /// # Type Parameters 77 | /// 78 | /// * `P` - The progress bar type. 79 | /// * `S` - The type of the indexed tree. 80 | /// 81 | /// # Arguments 82 | /// 83 | /// * `repo` - The repository to read from. 84 | /// * `snap` - The snapshot to read from. 85 | /// * `sn_filter` - The filter to apply to the snapshot. 86 | /// 87 | /// # Errors 88 | /// 89 | /// * If the path is not a directory. 90 | /// 91 | /// # Returns 92 | /// 93 | /// The data read. 94 | pub(crate) fn cat_tree( 95 | repo: &Repository, 96 | snap: &str, 97 | sn_filter: impl FnMut(&SnapshotFile) -> bool + Send + Sync, 98 | ) -> RusticResult { 99 | let (id, path) = snap.split_once(':').unwrap_or((snap, "")); 100 | let snap = SnapshotFile::from_str( 101 | repo.dbe(), 102 | id, 103 | sn_filter, 104 | &repo.pb.progress_counter("getting snapshot..."), 105 | )?; 106 | let node = Tree::node_from_path(repo.dbe(), repo.index(), snap.tree, Path::new(path))?; 107 | let id = node.subtree.ok_or_else(|| { 108 | RusticError::new( 109 | ErrorKind::InvalidInput, 110 | "Path `{path}` in Node subtree is not a directory. Please provide a directory path.", 111 | ) 112 | .attach_context("path", path.to_string()) 113 | })?; 114 | let data = repo 115 | .index() 116 | .blob_from_backend(repo.dbe(), BlobType::Tree, &BlobId::from(*id))?; 117 | Ok(data) 118 | } 119 | -------------------------------------------------------------------------------- /crates/core/src/commands/dump.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{ 4 | backend::node::{Node, NodeType}, 5 | blob::{BlobId, BlobType}, 6 | error::{ErrorKind, RusticError, RusticResult}, 7 | repository::{IndexedFull, Repository}, 8 | }; 9 | 10 | /// Dumps the contents of a file. 11 | /// 12 | /// # Type Parameters 13 | /// 14 | /// * `P` - The progress bar type. 15 | /// * `S` - The type of the indexed tree. 16 | /// 17 | /// # Arguments 18 | /// 19 | /// * `repo` - The repository to read from. 20 | /// * `node` - The node to dump. 21 | /// * `w` - The writer to write to. 22 | /// 23 | /// # Errors 24 | /// 25 | /// * If the node is not a file. 26 | pub(crate) fn dump( 27 | repo: &Repository, 28 | node: &Node, 29 | w: &mut impl Write, 30 | ) -> RusticResult<()> { 31 | if node.node_type != NodeType::File { 32 | return Err(RusticError::new( 33 | ErrorKind::Unsupported, 34 | "Dump is not supported for non-file node types `{node_type}`. You could try to use `cat` instead.", 35 | ) 36 | .attach_context("node_type", node.node_type.to_string())); 37 | } 38 | 39 | for id in node.content.as_ref().unwrap() { 40 | let data = repo.get_blob_cached(&BlobId::from(**id), BlobType::Data)?; 41 | w.write_all(&data).map_err(|err| { 42 | RusticError::with_source( 43 | ErrorKind::InputOutput, 44 | "Failed to write data to writer.", 45 | err, 46 | ) 47 | })?; 48 | } 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /crates/core/src/commands/init.rs: -------------------------------------------------------------------------------- 1 | //! `init` subcommand 2 | 3 | use log::info; 4 | 5 | use crate::{ 6 | backend::WriteBackend, 7 | chunker::random_poly, 8 | commands::{ 9 | config::{ConfigOptions, save_config}, 10 | key::{KeyOptions, init_key}, 11 | }, 12 | crypto::aespoly1305::Key, 13 | error::RusticResult, 14 | id::Id, 15 | repofile::{ConfigFile, configfile::RepositoryId}, 16 | repository::Repository, 17 | }; 18 | 19 | /// Initialize a new repository. 20 | /// 21 | /// # Type Parameters 22 | /// 23 | /// * `P` - The progress bar type. 24 | /// * `S` - The state the repository is in. 25 | /// 26 | /// # Arguments 27 | /// 28 | /// * `repo` - The repository to initialize. 29 | /// * `pass` - The password to encrypt the key with. 30 | /// * `key_opts` - The options to create the key with. 31 | /// * `config_opts` - The options to create the config with. 32 | /// 33 | /// # Errors 34 | /// 35 | /// * If no polynomial could be found in one million tries. 36 | /// 37 | /// # Returns 38 | /// 39 | /// A tuple of the key and the config file. 40 | pub(crate) fn init( 41 | repo: &Repository, 42 | pass: &str, 43 | key_opts: &KeyOptions, 44 | config_opts: &ConfigOptions, 45 | ) -> RusticResult<(Key, ConfigFile)> { 46 | // Create config first to allow catching errors from here without writing anything 47 | let repo_id = RepositoryId::from(Id::random()); 48 | let chunker_poly = random_poly()?; 49 | let mut config = ConfigFile::new(2, repo_id, chunker_poly); 50 | if repo.be_hot.is_some() { 51 | // for hot/cold repository, `config` must be identical to thee config file which is read by the backend, i.e. the one saved in the hot repo. 52 | // Note: init_with_config does handle the is_hot config correctly for the hot and the cold repo. 53 | config.is_hot = Some(true); 54 | } 55 | config_opts.apply(&mut config)?; 56 | 57 | let key = init_with_config(repo, pass, key_opts, &config)?; 58 | info!("repository {repo_id} successfully created."); 59 | 60 | Ok((key, config)) 61 | } 62 | 63 | /// Initialize a new repository with a given config. 64 | /// 65 | /// # Type Parameters 66 | /// 67 | /// * `P` - The progress bar type. 68 | /// * `S` - The state the repository is in. 69 | /// 70 | /// # Arguments 71 | /// 72 | /// * `repo` - The repository to initialize. 73 | /// * `pass` - The password to encrypt the key with. 74 | /// * `key_opts` - The options to create the key with. 75 | /// * `config` - The config to use. 76 | /// 77 | /// # Returns 78 | /// 79 | /// The key used to encrypt the config. 80 | pub(crate) fn init_with_config( 81 | repo: &Repository, 82 | pass: &str, 83 | key_opts: &KeyOptions, 84 | config: &ConfigFile, 85 | ) -> RusticResult { 86 | repo.be.create()?; 87 | let (key, id) = init_key(repo, key_opts, pass)?; 88 | info!("key {id} successfully added."); 89 | save_config(repo, config.clone(), key)?; 90 | 91 | Ok(key) 92 | } 93 | -------------------------------------------------------------------------------- /crates/core/src/commands/key.rs: -------------------------------------------------------------------------------- 1 | //! `key` subcommand 2 | use derive_setters::Setters; 3 | 4 | use crate::{ 5 | backend::{FileType, WriteBackend, decrypt::DecryptWriteBackend}, 6 | crypto::{aespoly1305::Key, hasher::hash}, 7 | error::{ErrorKind, RusticError, RusticResult}, 8 | repofile::{KeyFile, KeyId}, 9 | repository::{Open, Repository}, 10 | }; 11 | 12 | #[cfg_attr(feature = "clap", derive(clap::Parser))] 13 | #[derive(Debug, Clone, Default, Setters)] 14 | #[setters(into)] 15 | #[non_exhaustive] 16 | /// Options for the `key` command. These are used when creating a new key. 17 | pub struct KeyOptions { 18 | /// Set 'hostname' in public key information 19 | #[cfg_attr(feature = "clap", clap(long))] 20 | pub hostname: Option, 21 | 22 | /// Set 'username' in public key information 23 | #[cfg_attr(feature = "clap", clap(long))] 24 | pub username: Option, 25 | 26 | /// Add 'created' date in public key information 27 | #[cfg_attr(feature = "clap", clap(long))] 28 | pub with_created: bool, 29 | } 30 | 31 | /// Add the current key to the repository. 32 | /// 33 | /// # Type Parameters 34 | /// 35 | /// * `P` - The progress bar type 36 | /// * `S` - The state the repository is in 37 | /// 38 | /// # Arguments 39 | /// 40 | /// * `repo` - The repository to add the key to 41 | /// * `opts` - The key options to use 42 | /// * `pass` - The password to encrypt the key with 43 | /// 44 | /// # Errors 45 | /// 46 | /// * If the key could not be serialized 47 | /// 48 | /// # Returns 49 | /// 50 | /// The id of the key. 51 | pub(crate) fn add_current_key_to_repo( 52 | repo: &Repository, 53 | opts: &KeyOptions, 54 | pass: &str, 55 | ) -> RusticResult { 56 | let key = repo.dbe().key(); 57 | add_key_to_repo(repo, opts, pass, *key) 58 | } 59 | 60 | /// Initialize a new key. 61 | /// 62 | /// # Type Parameters 63 | /// 64 | /// * `P` - The progress bar type 65 | /// * `S` - The state the repository is in 66 | /// 67 | /// # Arguments 68 | /// 69 | /// * `repo` - The repository to add the key to 70 | /// * `opts` - The key options to use 71 | /// * `pass` - The password to encrypt the key with 72 | /// 73 | /// # Returns 74 | /// 75 | /// A tuple of the key and the id of the key. 76 | pub(crate) fn init_key( 77 | repo: &Repository, 78 | opts: &KeyOptions, 79 | pass: &str, 80 | ) -> RusticResult<(Key, KeyId)> { 81 | // generate key 82 | let key = Key::new(); 83 | Ok((key, add_key_to_repo(repo, opts, pass, key)?)) 84 | } 85 | 86 | /// Add a key to the repository. 87 | /// 88 | /// # Arguments 89 | /// 90 | /// * `repo` - The repository to add the key to 91 | /// * `opts` - The key options to use 92 | /// * `pass` - The password to encrypt the key with 93 | /// * `key` - The key to add 94 | /// 95 | /// # Errors 96 | /// 97 | /// * If the key could not be serialized. 98 | /// 99 | /// # Returns 100 | /// 101 | /// The id of the key. 102 | pub(crate) fn add_key_to_repo( 103 | repo: &Repository, 104 | opts: &KeyOptions, 105 | pass: &str, 106 | key: Key, 107 | ) -> RusticResult { 108 | let ko = opts.clone(); 109 | let keyfile = KeyFile::generate(key, &pass, ko.hostname, ko.username, ko.with_created)?; 110 | 111 | let data = serde_json::to_vec(&keyfile).map_err(|err| { 112 | RusticError::with_source( 113 | ErrorKind::InputOutput, 114 | "Failed to serialize keyfile to JSON.", 115 | err, 116 | ) 117 | })?; 118 | 119 | let id = KeyId::from(hash(&data)); 120 | 121 | repo.be 122 | .write_bytes(FileType::Key, &id, false, data.into())?; 123 | 124 | Ok(id) 125 | } 126 | -------------------------------------------------------------------------------- /crates/core/src/commands/repair.rs: -------------------------------------------------------------------------------- 1 | pub mod index; 2 | pub mod snapshots; 3 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots.rs: -------------------------------------------------------------------------------- 1 | //! `smapshot` subcommand 2 | 3 | use crate::{ 4 | Progress, 5 | error::RusticResult, 6 | progress::ProgressBars, 7 | repofile::{ 8 | SnapshotFile, 9 | snapshotfile::{SnapshotGroup, SnapshotGroupCriterion}, 10 | }, 11 | repository::{Open, Repository}, 12 | }; 13 | 14 | /// Get the snapshots from the repository. 15 | /// 16 | /// # Type Parameters 17 | /// 18 | /// * `P` - The progress bar type. 19 | /// * `S` - The state the repository is in. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `repo` - The repository to get the snapshots from. 24 | /// * `ids` - The ids of the snapshots to get. 25 | /// * `group_by` - The criterion to group the snapshots by. 26 | /// * `filter` - The filter to apply to the snapshots. 27 | /// 28 | /// # Returns 29 | /// 30 | /// The snapshots grouped by the given criterion. 31 | pub(crate) fn get_snapshot_group( 32 | repo: &Repository, 33 | ids: &[String], 34 | group_by: SnapshotGroupCriterion, 35 | filter: impl FnMut(&SnapshotFile) -> bool, 36 | ) -> RusticResult)>> { 37 | let pb = &repo.pb; 38 | let dbe = repo.dbe(); 39 | let p = pb.progress_counter("getting snapshots..."); 40 | let groups = match ids { 41 | [] => SnapshotFile::group_from_backend(dbe, filter, group_by, &p)?, 42 | [id] if id == "latest" => SnapshotFile::group_from_backend(dbe, filter, group_by, &p)? 43 | .into_iter() 44 | .map(|(group, mut snaps)| { 45 | snaps.sort_unstable(); 46 | let last_idx = snaps.len() - 1; 47 | snaps.swap(0, last_idx); 48 | snaps.truncate(1); 49 | (group, snaps) 50 | }) 51 | .collect::>(), 52 | _ => { 53 | let item = ( 54 | SnapshotGroup::default(), 55 | SnapshotFile::from_ids(dbe, ids, &p)?, 56 | ); 57 | vec![item] 58 | } 59 | }; 60 | p.finish(); 61 | 62 | Ok(groups) 63 | } 64 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__250MiB.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: ids 4 | --- 5 | [ 6 | (Id("17602dc0f514e0ef7cd4d0a19645b6b6201966af54959c4cf469742686d4ff82"), Some(14961973)), 7 | (Id("31c3286dfd8602a3595b620330c98e6f35166db8f2d8744089a44ee800a088f5"), Some(85711317)), 8 | (Id("22ead143cf731beaeb922e105fbb8d408046956994f5e167086b92d095d13556"), Some(16671961)), 9 | (Id("7591c69883e3e3434f410698740bfb6c49ffa697de4df5fc9443e05402648ec5"), Some(51267289)), 10 | (Id("47719fffaeba4dc018087189d7c87e059e6daca80d96af9bc655e101949ad51d"), Some(8456629)), 11 | (Id("2429b9232cd68ea47c3b938ff3142ce81d69c3e57dabd7e428f28ceed2b64c09"), Some(62181616)), 12 | (Id("c707686f4ca82b4b3ee02797b8d562d301bc5b208ef9bf33e12c60ddf8a985d8"), Some(22119271)), 13 | (Id("85f921b05faf386f704ff6c4917198e3cd3d3ecd8c8df9389b7771114b232709"), Some(209417)), 14 | ] 15 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__5%.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: ids 4 | --- 5 | [ 6 | (Id("17602dc0f514e0ef7cd4d0a19645b6b6201966af54959c4cf469742686d4ff82"), Some(14961973)), 7 | (Id("31c3286dfd8602a3595b620330c98e6f35166db8f2d8744089a44ee800a088f5"), Some(85711317)), 8 | (Id("22ead143cf731beaeb922e105fbb8d408046956994f5e167086b92d095d13556"), Some(16671961)), 9 | (Id("7591c69883e3e3434f410698740bfb6c49ffa697de4df5fc9443e05402648ec5"), Some(51267289)), 10 | (Id("47719fffaeba4dc018087189d7c87e059e6daca80d96af9bc655e101949ad51d"), Some(8456629)), 11 | (Id("2429b9232cd68ea47c3b938ff3142ce81d69c3e57dabd7e428f28ceed2b64c09"), Some(62181616)), 12 | (Id("109658f772e6b57f05de29862fdd4e49f77456e49d84879f6d441446fa53219c"), Some(41559096)), 13 | (Id("7bee35e29d5341968b58fb5e47372eb6e2d8d38a944569ec4f3c82c80108a1f7"), Some(25244545)), 14 | (Id("9b3682846efc96f5299d2ee4bd7428bef377455c3948a73574045a786e326d9c"), Some(93191508)), 15 | (Id("11c30ac2ac13bf803c41b811c8659312a1bd525d88da1ee47579c1ad00c0fc83"), Some(72103568)), 16 | (Id("ece9d0d50bd462c36612fd5528a16f9b662b98e4cf50c680b1217acf898d0879"), Some(88106936)), 17 | (Id("57b9bd9fdffe3d3b9313d1a85765713aed899a35ed49d37e5e9404c82f1373c6"), Some(32951659)), 18 | (Id("6ad37a7161db7f6e64275a653a7a0c12cfe97f8ce00032ffed907564dad33838"), Some(23857903)), 19 | (Id("c99e802c92f664273faf99dca687cb452e0b1dc0f173977512ffa68f87847331"), Some(74415130)), 20 | (Id("6d79a0b12b10495dd81e83290ad743f1cf3530fda93d4175a11a6de045b228d1"), Some(29018737)), 21 | (Id("8129a316239f90ddb0aa5dc32c279b4a9b0c026e41fe0925b12f70a32d3bb854"), Some(85971141)), 22 | (Id("c707686f4ca82b4b3ee02797b8d562d301bc5b208ef9bf33e12c60ddf8a985d8"), Some(22119271)), 23 | (Id("cbc68260e15a16d44f8a756152a8687ae021283126e10ccd991dd90645f1a353"), Some(36087660)), 24 | (Id("09850a0f44b8c4eea24915fe5a12e22eccf557b6ef4445f9f8efbb46c16d17f1"), Some(43241828)), 25 | (Id("0faad68832cfda48366923d418154b41ab7a68ad57ee0f24ba71c17487f78710"), Some(56118437)), 26 | (Id("5aed4a6c662be4caf840a78db58ec7128703b051ae82a2efdb0cb45fd190e1c0"), Some(50681488)), 27 | (Id("1361b96442cd45a35adfe04435f630d2cafc1761d792ed1637d8fd6dae40c7c1"), Some(9143530)), 28 | (Id("397bd852a87c60e2c7db161791f95d835c408cce16f21192cd257327c9cf3538"), Some(39608359)), 29 | (Id("2eef6eff1a7389f6b296d9d5e3595c40709f127bd285324e88dd20b988703f67"), Some(43326266)), 30 | (Id("8237fdf61565dc1d0bc95241ff62cc5f24c719f08775e4608ca04efb909c9adf"), Some(95546862)), 31 | (Id("6f0ed063b58e94b4700610151de166d2d84e7ab42dae514a1777cf8ac5d78402"), Some(30522457)), 32 | (Id("56ee14271f6748c68083fa77a4a5504d4fc23e3f4be3747302a266102c4bfb78"), Some(1262681)), 33 | (Id("c7634a265078c2e739e2d5dbffa0382a82bf061e807bcd981afe739c8f3703e6"), Some(2907566)), 34 | (Id("85f921b05faf386f704ff6c4917198e3cd3d3ecd8c8df9389b7771114b232709"), Some(209417)), 35 | (Id("16d3808d5b73197160af65183eb5a04418af8314e10d0ee2c8a9bbbc70f9f3b6"), Some(610561)), 36 | ] 37 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__5__12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: ids 4 | --- 5 | [ 6 | (Id("2d01881ed12533cf7429a193c3eac3887865646a2c11c2c7701e8a72929bd6b9"), Some(34014270)), 7 | (Id("556379de1252c850b5b8d7b763de96ac7d74abe977fe26e8259e0d5dd52c7a3b"), Some(9348751)), 8 | (Id("35bbd7c0cc017ded665583959dfe24835e11e7277d89f5c9242d47bf6efa3498"), Some(26998907)), 9 | (Id("956227a645a590e09ae3f288ae0022456faf6920b8fc9d17dce11e534b0a3356"), Some(60557187)), 10 | (Id("01abe1b59c2e041672eaf52c4225c6b529dd98fcd61bf8d43969597cd80f37f1"), Some(6376122)), 11 | (Id("159c55ac93629fa6e8fb048de9c2cdedd84a7c4883bea062cc0241b9340cc38f"), Some(97066882)), 12 | (Id("0d3598b44e53bff85bcecb3009dd07cf5346fb3eb0d576e871329908db985718"), Some(71965959)), 13 | (Id("117db5cfa26fb27a24a487fef77596d823d134a6b76e57521dc95b139cdb5202"), Some(37054852)), 14 | (Id("f994b68c3889e2a890318f5f889f41916abe65085876fc7ad07f4c7bb93c5460"), Some(59747267)), 15 | (Id("e9e9f2ce925e1287446a451e2afc2e67491fa8b76bb67eb0fec9823506869fd1"), Some(59406570)), 16 | (Id("3537e7eca26fd0807ff22165c4cdcbc5adcdedb1307694964b2d339f722e52e2"), Some(63896801)), 17 | (Id("81c16f9d45ca7c83bd6fdd2783fcb7447738256d2ce747263955f061fff60d89"), Some(7336738)), 18 | (Id("5946f9ce9167654d40d045154ac7c031c715237e74fc52a456555ca13e1905f4"), Some(97136932)), 19 | (Id("d197251cec9aa98f597da01515fecbcc7dccb34f24ca8ebb4cd1356c08cbb790"), Some(13503196)), 20 | (Id("ddf5e4af63be3351fccdea6fdb4db7b88b9706f0759386718798dc4228acc6d4"), Some(23059186)), 21 | (Id("8dce2b9b0168047b637fe243b304258f147df8dca0bada4623a5b45246c4e8dc"), Some(78867635)), 22 | (Id("09850a0f44b8c4eea24915fe5a12e22eccf557b6ef4445f9f8efbb46c16d17f1"), Some(43241828)), 23 | (Id("097967d57496d5aae6d9d9c7eb7fa06c02c84e8b643daafcb37354228fc4938d"), Some(1572194)), 24 | (Id("2921a59ce0b337336fc4d5a686689111ccd427e073d552a897daa29e93e0239d"), Some(37345451)), 25 | (Id("e135420c36e1834dde12f8252baa92387c404b9e0d62277d32f84298fddaa329"), Some(89274719)), 26 | (Id("093fc434d3f0fe9ff0ac70b5928ab7a20e7a2530622622d8613a0b3fe435d9e7"), Some(63145259)), 27 | (Id("39af1807a1e90b1272607c5c212515ebcb3dfac2b4f8e4b9f046ca22f36b7dfe"), Some(44377634)), 28 | (Id("cdc110cea7eb197c60d8a7488f2793a0f87a47d41b81a6117c4550e5767ded1e"), Some(36505267)), 29 | (Id("150472a6db5eac4b5528f9923154813782a6f4d7ccdfd40084fab3ed375c19e8"), Some(35395534)), 30 | (Id("cd80cdebff5a24752aa3b00e3c5d28966714f16a8c9c249513bf5a33a6333f86"), Some(14837616)), 31 | (Id("c58dc5672e40e207f5f9a4ba1b792914d745c71b53006124a66ba27fec00140a"), Some(49408196)), 32 | (Id("1568576c955facb2d0a4ec9cee06aa32d2bd6e102983c22aa6f74a749f40d13d"), Some(27386772)), 33 | (Id("f9133a33f84329657f07d0122e552502db6a8047ba8478b000bd3d2bd1f516e6"), Some(85299654)), 34 | (Id("413d43c7300bf3b8d2d5c26249cf84a2822c710edfe0e9f10eeed8ae74cf318e"), Some(79672527)), 35 | (Id("45961fb82050f8b6a67a7a9312e2ab84a77269056075042b2f43e9d48d5a984f"), Some(71655782)), 36 | (Id("0952e740dd545a036a1b925e34718bd31b0a8139fce6e3e6addd62d5e2ae7689"), Some(38498532)), 37 | (Id("b53d3c0f6e3a43c9cf35c68831079d4ad3043354558d52f9941a9c1dafcb86f7"), Some(91289786)), 38 | (Id("1ddf39c8ced0915a0e9a171cfc615d186ceac796f3e42f9de3ddb48bb60d5b7a"), Some(3536849)), 39 | (Id("11c30ac2ac13bf803c41b811c8659312a1bd525d88da1ee47579c1ad00c0fc83"), Some(72103568)), 40 | (Id("5156f9e1e36942f38e40f15534b42082cb5d7329826fb8646ed7a1b304be0dc5"), Some(53499085)), 41 | (Id("154e9b87451999bf01419e52a813f513d22eb8638d450fc4aee148ba1a7804d8"), Some(87615596)), 42 | ] 43 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_15_month_hours.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((15, 744), (15, 744), (15, 744), (15, 744), (15, 720), (15, 744), (15, 696)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_29_28.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((1, 28), (1, 28), (1, 28), (1, 28), (1, 28), (1, 28), (1, 28)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_4_month_days.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((4, 31), (4, 31), (4, 31), (4, 31), (4, 30), (4, 31), (4, 29)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_5_12.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((5, 12), (5, 12), (5, 12), (5, 12), (5, 12), (5, 12), (5, 12)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_daily_15.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((14, 15), (14, 15), (0, 15), (6, 15), (0, 15), (13, 15), (2, 15)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_daily_month.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((5, 31), (5, 31), (6, 31), (12, 31), (15, 30), (4, 31), (3, 29)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_daily_week.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((4, 7), (4, 7), (5, 7), (4, 7), (0, 7), (3, 7), (4, 7)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_daily_year.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((284, 366), (284, 366), (285, 366), (291, 366), (315, 366), (283, 365), (32, 366)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_hourly_20.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((8, 20), (9, 20), (12, 20), (16, 20), (12, 20), (4, 20), (0, 20)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_hourly_day.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((12, 24), (13, 24), (12, 24), (12, 24), (12, 24), (12, 24), (12, 24)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_hourly_month.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((132, 744), (133, 744), (156, 744), (300, 744), (372, 720), (108, 744), (84, 696)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_hourly_week.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((108, 168), (109, 168), (132, 168), (108, 168), (12, 168), (84, 168), (108, 168)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_hourly_year.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((6828, 8784), (6829, 8784), (6852, 8784), (6996, 8784), (7572, 8784), (6804, 8760), (780, 8784)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_monthly_5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((4, 5), (4, 5), (4, 5), (4, 5), (0, 5), (4, 5), (1, 5)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_monthly_year.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((9, 12), (9, 12), (9, 12), (9, 12), (10, 12), (9, 12), (1, 12)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_weekly_10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((0, 10), (0, 10), (0, 10), (1, 10), (5, 10), (0, 10), (4, 10)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_weekly_month.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((0, 4), (0, 4), (0, 4), (1, 4), (1, 4), (0, 4), (0, 4)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__check__tests__n_m_weekly_year.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/check.rs 3 | expression: "(res, res_1h, res_1d, res_1w, res_1m, res_1y, res2)" 4 | --- 5 | ((40, 52), (40, 52), (40, 52), (41, 52), (45, 52), (40, 52), (4, 52)) 6 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__forget__tests__keep-nonetrue.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/forget.rs 3 | expression: result 4 | --- 5 | ForgetResult([ 6 | ("2016-01-18T12:02:03Z", false, []), 7 | ("2016-01-12T21:08:03Z", false, []), 8 | ("2016-01-12T21:02:03Z", false, []), 9 | ("2016-01-09T21:02:03Z", false, []), 10 | ("2016-01-08T20:02:03Z", false, []), 11 | ("2016-01-07T10:02:03Z", false, []), 12 | ("2016-01-06T08:02:03Z", false, []), 13 | ("2016-01-05T09:02:03Z", false, []), 14 | ("2016-01-04T16:23:03Z", false, []), 15 | ("2016-01-04T12:30:03Z", false, []), 16 | ("2016-01-04T12:28:03Z", false, []), 17 | ("2016-01-04T12:24:03Z", false, []), 18 | ("2016-01-04T12:23:03Z", false, []), 19 | ("2016-01-04T11:23:03Z", false, []), 20 | ("2016-01-04T10:23:03Z", false, []), 21 | ("2016-01-03T07:02:03Z", false, []), 22 | ("2016-01-01T07:08:03Z", false, []), 23 | ("2016-01-01T01:03:03Z", false, []), 24 | ("2016-01-01T01:02:03Z", false, []), 25 | ("2015-11-22T10:20:30Z", false, []), 26 | ("2015-11-21T10:20:30Z", false, []), 27 | ("2015-11-20T10:20:30Z", false, []), 28 | ("2015-11-18T10:20:30Z", false, []), 29 | ("2015-11-15T10:20:30Z", false, []), 30 | ("2015-11-13T10:20:30Z", false, []), 31 | ("2015-11-12T10:20:30Z", false, []), 32 | ("2015-11-10T10:20:30Z", false, []), 33 | ("2015-11-08T10:20:30Z", false, []), 34 | ("2015-10-22T10:20:31Z", false, []), 35 | ("2015-10-22T10:20:31Z", false, []), 36 | ("2015-10-22T10:20:30Z", false, []), 37 | ("2015-10-22T10:20:30Z", false, []), 38 | ("2015-10-20T10:20:30Z", false, []), 39 | ("2015-10-11T10:20:30Z", false, []), 40 | ("2015-10-10T10:20:30Z", false, []), 41 | ("2015-10-09T10:20:30Z", false, []), 42 | ("2015-10-08T10:20:30Z", false, []), 43 | ("2015-10-06T10:20:30Z", false, []), 44 | ("2015-10-05T10:20:30Z", false, []), 45 | ("2015-10-02T10:20:30Z", false, []), 46 | ("2015-10-01T10:20:30Z", false, []), 47 | ("2015-09-22T10:20:30Z", false, []), 48 | ("2015-09-20T10:20:30Z", false, []), 49 | ("2015-09-11T10:20:30Z", false, []), 50 | ("2015-09-10T10:20:30Z", false, []), 51 | ("2015-09-09T10:20:30Z", false, []), 52 | ("2015-09-08T10:20:30Z", false, []), 53 | ("2015-09-06T10:20:30Z", false, []), 54 | ("2015-09-05T10:20:30Z", false, []), 55 | ("2015-09-02T10:20:30Z", false, []), 56 | ("2015-09-01T10:20:30Z", false, []), 57 | ("2015-08-22T10:20:30Z", false, []), 58 | ("2015-08-21T10:20:30Z", false, []), 59 | ("2015-08-20T10:20:30Z", false, []), 60 | ("2015-08-18T10:20:30Z", false, []), 61 | ("2015-08-15T10:20:30Z", false, []), 62 | ("2015-08-13T10:20:30Z", false, []), 63 | ("2015-08-12T10:20:30Z", false, []), 64 | ("2015-08-10T10:20:30Z", false, []), 65 | ("2015-08-08T10:20:30Z", false, []), 66 | ("2014-11-22T10:20:30Z", false, []), 67 | ("2014-11-21T10:20:30Z", false, []), 68 | ("2014-11-20T10:20:30Z", false, []), 69 | ("2014-11-18T10:20:30Z", false, []), 70 | ("2014-11-15T10:20:31Z", false, []), 71 | ("2014-11-13T10:20:31Z", false, []), 72 | ("2014-11-12T10:20:31Z", false, []), 73 | ("2014-11-10T10:20:31Z", false, []), 74 | ("2014-11-08T10:20:31Z", false, []), 75 | ("2014-10-22T10:20:31Z", false, []), 76 | ("2014-10-20T10:20:31Z", false, []), 77 | ("2014-10-11T10:20:31Z", false, []), 78 | ("2014-10-10T10:20:31Z", false, []), 79 | ("2014-10-09T10:20:31Z", false, []), 80 | ("2014-10-08T10:20:31Z", false, []), 81 | ("2014-10-06T10:20:31Z", false, []), 82 | ("2014-10-05T10:20:31Z", false, []), 83 | ("2014-10-02T10:20:31Z", false, []), 84 | ("2014-10-01T10:20:31Z", false, []), 85 | ("2014-09-22T10:20:30Z", false, []), 86 | ("2014-09-20T10:20:30Z", false, []), 87 | ("2014-09-11T10:20:30Z", false, []), 88 | ("2014-09-10T10:20:30Z", false, []), 89 | ("2014-09-09T10:20:30Z", false, []), 90 | ("2014-09-08T10:20:30Z", false, []), 91 | ("2014-09-06T10:20:30Z", false, []), 92 | ("2014-09-05T10:20:30Z", false, []), 93 | ("2014-09-02T10:20:30Z", false, []), 94 | ("2014-09-01T10:29:37Z", true, [ 95 | "snapshot", 96 | ]), 97 | ("2014-09-01T10:28:37Z", false, [ 98 | "snapshot", 99 | ]), 100 | ("2014-09-01T10:25:37Z", true, [ 101 | "snapshot", 102 | ]), 103 | ("2014-09-01T10:20:30Z", false, []), 104 | ("2014-08-22T10:20:30Z", false, []), 105 | ("2014-08-21T10:20:30Z", false, []), 106 | ("2014-08-20T10:20:30Z", false, []), 107 | ("2014-08-18T10:20:30Z", false, []), 108 | ("2014-08-15T10:20:30Z", false, []), 109 | ("2014-08-13T10:20:30Z", false, []), 110 | ("2014-08-12T10:20:30Z", false, []), 111 | ("2014-08-10T10:20:30Z", false, []), 112 | ("2014-08-08T10:20:30Z", false, []), 113 | ]) 114 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__forget__tests__keep-within1day.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/forget.rs 3 | expression: result 4 | --- 5 | ForgetResult([ 6 | ("2016-01-18T12:02:03Z", true, [ 7 | "within", 8 | ]), 9 | ("2016-01-12T21:08:03Z", false, []), 10 | ("2016-01-12T21:02:03Z", false, []), 11 | ("2016-01-09T21:02:03Z", false, []), 12 | ("2016-01-08T20:02:03Z", false, []), 13 | ("2016-01-07T10:02:03Z", false, []), 14 | ("2016-01-06T08:02:03Z", false, []), 15 | ("2016-01-05T09:02:03Z", false, []), 16 | ("2016-01-04T16:23:03Z", false, []), 17 | ("2016-01-04T12:30:03Z", false, []), 18 | ("2016-01-04T12:28:03Z", false, []), 19 | ("2016-01-04T12:24:03Z", false, []), 20 | ("2016-01-04T12:23:03Z", false, []), 21 | ("2016-01-04T11:23:03Z", false, []), 22 | ("2016-01-04T10:23:03Z", false, []), 23 | ("2016-01-03T07:02:03Z", false, []), 24 | ("2016-01-01T07:08:03Z", false, []), 25 | ("2016-01-01T01:03:03Z", false, []), 26 | ("2016-01-01T01:02:03Z", false, []), 27 | ("2015-11-22T10:20:30Z", false, []), 28 | ("2015-11-21T10:20:30Z", false, []), 29 | ("2015-11-20T10:20:30Z", false, []), 30 | ("2015-11-18T10:20:30Z", false, []), 31 | ("2015-11-15T10:20:30Z", false, []), 32 | ("2015-11-13T10:20:30Z", false, []), 33 | ("2015-11-12T10:20:30Z", false, []), 34 | ("2015-11-10T10:20:30Z", false, []), 35 | ("2015-11-08T10:20:30Z", false, []), 36 | ("2015-10-22T10:20:31Z", false, []), 37 | ("2015-10-22T10:20:31Z", false, []), 38 | ("2015-10-22T10:20:30Z", false, []), 39 | ("2015-10-22T10:20:30Z", false, []), 40 | ("2015-10-20T10:20:30Z", false, []), 41 | ("2015-10-11T10:20:30Z", false, []), 42 | ("2015-10-10T10:20:30Z", false, []), 43 | ("2015-10-09T10:20:30Z", false, []), 44 | ("2015-10-08T10:20:30Z", false, []), 45 | ("2015-10-06T10:20:30Z", false, []), 46 | ("2015-10-05T10:20:30Z", false, []), 47 | ("2015-10-02T10:20:30Z", false, []), 48 | ("2015-10-01T10:20:30Z", false, []), 49 | ("2015-09-22T10:20:30Z", false, []), 50 | ("2015-09-20T10:20:30Z", false, []), 51 | ("2015-09-11T10:20:30Z", false, []), 52 | ("2015-09-10T10:20:30Z", false, []), 53 | ("2015-09-09T10:20:30Z", false, []), 54 | ("2015-09-08T10:20:30Z", false, []), 55 | ("2015-09-06T10:20:30Z", false, []), 56 | ("2015-09-05T10:20:30Z", false, []), 57 | ("2015-09-02T10:20:30Z", false, []), 58 | ("2015-09-01T10:20:30Z", false, []), 59 | ("2015-08-22T10:20:30Z", false, []), 60 | ("2015-08-21T10:20:30Z", false, []), 61 | ("2015-08-20T10:20:30Z", false, []), 62 | ("2015-08-18T10:20:30Z", false, []), 63 | ("2015-08-15T10:20:30Z", false, []), 64 | ("2015-08-13T10:20:30Z", false, []), 65 | ("2015-08-12T10:20:30Z", false, []), 66 | ("2015-08-10T10:20:30Z", false, []), 67 | ("2015-08-08T10:20:30Z", false, []), 68 | ("2014-11-22T10:20:30Z", false, []), 69 | ("2014-11-21T10:20:30Z", false, []), 70 | ("2014-11-20T10:20:30Z", false, []), 71 | ("2014-11-18T10:20:30Z", false, []), 72 | ("2014-11-15T10:20:31Z", false, []), 73 | ("2014-11-13T10:20:31Z", false, []), 74 | ("2014-11-12T10:20:31Z", false, []), 75 | ("2014-11-10T10:20:31Z", false, []), 76 | ("2014-11-08T10:20:31Z", false, []), 77 | ("2014-10-22T10:20:31Z", false, []), 78 | ("2014-10-20T10:20:31Z", false, []), 79 | ("2014-10-11T10:20:31Z", false, []), 80 | ("2014-10-10T10:20:31Z", false, []), 81 | ("2014-10-09T10:20:31Z", false, []), 82 | ("2014-10-08T10:20:31Z", false, []), 83 | ("2014-10-06T10:20:31Z", false, []), 84 | ("2014-10-05T10:20:31Z", false, []), 85 | ("2014-10-02T10:20:31Z", false, []), 86 | ("2014-10-01T10:20:31Z", false, []), 87 | ("2014-09-22T10:20:30Z", false, []), 88 | ("2014-09-20T10:20:30Z", false, []), 89 | ("2014-09-11T10:20:30Z", false, []), 90 | ("2014-09-10T10:20:30Z", false, []), 91 | ("2014-09-09T10:20:30Z", false, []), 92 | ("2014-09-08T10:20:30Z", false, []), 93 | ("2014-09-06T10:20:30Z", false, []), 94 | ("2014-09-05T10:20:30Z", false, []), 95 | ("2014-09-02T10:20:30Z", false, []), 96 | ("2014-09-01T10:29:37Z", true, [ 97 | "snapshot", 98 | ]), 99 | ("2014-09-01T10:28:37Z", false, [ 100 | "snapshot", 101 | ]), 102 | ("2014-09-01T10:25:37Z", true, [ 103 | "snapshot", 104 | ]), 105 | ("2014-09-01T10:20:30Z", false, []), 106 | ("2014-08-22T10:20:30Z", false, []), 107 | ("2014-08-21T10:20:30Z", false, []), 108 | ("2014-08-20T10:20:30Z", false, []), 109 | ("2014-08-18T10:20:30Z", false, []), 110 | ("2014-08-15T10:20:30Z", false, []), 111 | ("2014-08-13T10:20:30Z", false, []), 112 | ("2014-08-12T10:20:30Z", false, []), 113 | ("2014-08-10T10:20:30Z", false, []), 114 | ("2014-08-08T10:20:30Z", false, []), 115 | ]) 116 | -------------------------------------------------------------------------------- /crates/core/src/commands/snapshots/rustic_core__commands__forget__tests__keep-within1m.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/src/commands/forget.rs 3 | expression: result 4 | --- 5 | ForgetResult([ 6 | ("2016-01-18T12:02:03Z", true, [ 7 | "within", 8 | ]), 9 | ("2016-01-12T21:08:03Z", false, []), 10 | ("2016-01-12T21:02:03Z", false, []), 11 | ("2016-01-09T21:02:03Z", false, []), 12 | ("2016-01-08T20:02:03Z", false, []), 13 | ("2016-01-07T10:02:03Z", false, []), 14 | ("2016-01-06T08:02:03Z", false, []), 15 | ("2016-01-05T09:02:03Z", false, []), 16 | ("2016-01-04T16:23:03Z", false, []), 17 | ("2016-01-04T12:30:03Z", false, []), 18 | ("2016-01-04T12:28:03Z", false, []), 19 | ("2016-01-04T12:24:03Z", false, []), 20 | ("2016-01-04T12:23:03Z", false, []), 21 | ("2016-01-04T11:23:03Z", false, []), 22 | ("2016-01-04T10:23:03Z", false, []), 23 | ("2016-01-03T07:02:03Z", false, []), 24 | ("2016-01-01T07:08:03Z", false, []), 25 | ("2016-01-01T01:03:03Z", false, []), 26 | ("2016-01-01T01:02:03Z", false, []), 27 | ("2015-11-22T10:20:30Z", false, []), 28 | ("2015-11-21T10:20:30Z", false, []), 29 | ("2015-11-20T10:20:30Z", false, []), 30 | ("2015-11-18T10:20:30Z", false, []), 31 | ("2015-11-15T10:20:30Z", false, []), 32 | ("2015-11-13T10:20:30Z", false, []), 33 | ("2015-11-12T10:20:30Z", false, []), 34 | ("2015-11-10T10:20:30Z", false, []), 35 | ("2015-11-08T10:20:30Z", false, []), 36 | ("2015-10-22T10:20:31Z", false, []), 37 | ("2015-10-22T10:20:31Z", false, []), 38 | ("2015-10-22T10:20:30Z", false, []), 39 | ("2015-10-22T10:20:30Z", false, []), 40 | ("2015-10-20T10:20:30Z", false, []), 41 | ("2015-10-11T10:20:30Z", false, []), 42 | ("2015-10-10T10:20:30Z", false, []), 43 | ("2015-10-09T10:20:30Z", false, []), 44 | ("2015-10-08T10:20:30Z", false, []), 45 | ("2015-10-06T10:20:30Z", false, []), 46 | ("2015-10-05T10:20:30Z", false, []), 47 | ("2015-10-02T10:20:30Z", false, []), 48 | ("2015-10-01T10:20:30Z", false, []), 49 | ("2015-09-22T10:20:30Z", false, []), 50 | ("2015-09-20T10:20:30Z", false, []), 51 | ("2015-09-11T10:20:30Z", false, []), 52 | ("2015-09-10T10:20:30Z", false, []), 53 | ("2015-09-09T10:20:30Z", false, []), 54 | ("2015-09-08T10:20:30Z", false, []), 55 | ("2015-09-06T10:20:30Z", false, []), 56 | ("2015-09-05T10:20:30Z", false, []), 57 | ("2015-09-02T10:20:30Z", false, []), 58 | ("2015-09-01T10:20:30Z", false, []), 59 | ("2015-08-22T10:20:30Z", false, []), 60 | ("2015-08-21T10:20:30Z", false, []), 61 | ("2015-08-20T10:20:30Z", false, []), 62 | ("2015-08-18T10:20:30Z", false, []), 63 | ("2015-08-15T10:20:30Z", false, []), 64 | ("2015-08-13T10:20:30Z", false, []), 65 | ("2015-08-12T10:20:30Z", false, []), 66 | ("2015-08-10T10:20:30Z", false, []), 67 | ("2015-08-08T10:20:30Z", false, []), 68 | ("2014-11-22T10:20:30Z", false, []), 69 | ("2014-11-21T10:20:30Z", false, []), 70 | ("2014-11-20T10:20:30Z", false, []), 71 | ("2014-11-18T10:20:30Z", false, []), 72 | ("2014-11-15T10:20:31Z", false, []), 73 | ("2014-11-13T10:20:31Z", false, []), 74 | ("2014-11-12T10:20:31Z", false, []), 75 | ("2014-11-10T10:20:31Z", false, []), 76 | ("2014-11-08T10:20:31Z", false, []), 77 | ("2014-10-22T10:20:31Z", false, []), 78 | ("2014-10-20T10:20:31Z", false, []), 79 | ("2014-10-11T10:20:31Z", false, []), 80 | ("2014-10-10T10:20:31Z", false, []), 81 | ("2014-10-09T10:20:31Z", false, []), 82 | ("2014-10-08T10:20:31Z", false, []), 83 | ("2014-10-06T10:20:31Z", false, []), 84 | ("2014-10-05T10:20:31Z", false, []), 85 | ("2014-10-02T10:20:31Z", false, []), 86 | ("2014-10-01T10:20:31Z", false, []), 87 | ("2014-09-22T10:20:30Z", false, []), 88 | ("2014-09-20T10:20:30Z", false, []), 89 | ("2014-09-11T10:20:30Z", false, []), 90 | ("2014-09-10T10:20:30Z", false, []), 91 | ("2014-09-09T10:20:30Z", false, []), 92 | ("2014-09-08T10:20:30Z", false, []), 93 | ("2014-09-06T10:20:30Z", false, []), 94 | ("2014-09-05T10:20:30Z", false, []), 95 | ("2014-09-02T10:20:30Z", false, []), 96 | ("2014-09-01T10:29:37Z", true, [ 97 | "snapshot", 98 | ]), 99 | ("2014-09-01T10:28:37Z", false, [ 100 | "snapshot", 101 | ]), 102 | ("2014-09-01T10:25:37Z", true, [ 103 | "snapshot", 104 | ]), 105 | ("2014-09-01T10:20:30Z", false, []), 106 | ("2014-08-22T10:20:30Z", false, []), 107 | ("2014-08-21T10:20:30Z", false, []), 108 | ("2014-08-20T10:20:30Z", false, []), 109 | ("2014-08-18T10:20:30Z", false, []), 110 | ("2014-08-15T10:20:30Z", false, []), 111 | ("2014-08-13T10:20:30Z", false, []), 112 | ("2014-08-12T10:20:30Z", false, []), 113 | ("2014-08-10T10:20:30Z", false, []), 114 | ("2014-08-08T10:20:30Z", false, []), 115 | ]) 116 | -------------------------------------------------------------------------------- /crates/core/src/crypto.rs: -------------------------------------------------------------------------------- 1 | use crate::RusticResult; 2 | 3 | pub(crate) mod aespoly1305; 4 | pub(crate) mod hasher; 5 | 6 | /// A trait for encrypting and decrypting data. 7 | pub trait CryptoKey: Clone + Copy + Sized + Send + Sync + 'static { 8 | /// Decrypt the given data. 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `data` - The data to decrypt. 13 | /// 14 | /// # Returns 15 | /// 16 | /// A vector containing the decrypted data. 17 | fn decrypt_data(&self, data: &[u8]) -> RusticResult>; 18 | 19 | /// Encrypt the given data. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `data` - The data to encrypt. 24 | /// 25 | /// # Returns 26 | /// 27 | /// A vector containing the encrypted data. 28 | fn encrypt_data(&self, data: &[u8]) -> RusticResult>; 29 | } 30 | -------------------------------------------------------------------------------- /crates/core/src/crypto/hasher.rs: -------------------------------------------------------------------------------- 1 | use sha2::{Digest, Sha256}; 2 | 3 | use crate::id::Id; 4 | 5 | /// Hashes the given data. 6 | /// 7 | /// # Arguments 8 | /// 9 | /// * `data` - The data to hash. 10 | /// 11 | /// # Returns 12 | /// 13 | /// The hash Id of the data. 14 | #[must_use] 15 | pub fn hash(data: &[u8]) -> Id { 16 | Id::new(Sha256::digest(data).into()) 17 | } 18 | -------------------------------------------------------------------------------- /crates/core/src/progress.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use log::info; 4 | 5 | /// Trait to report progress information for any rustic action which supports that. 6 | /// 7 | /// Implement this trait when you want to display this progress to your users. 8 | pub trait Progress: Send + Sync + Clone { 9 | /// Check if progress is hidden 10 | fn is_hidden(&self) -> bool; 11 | 12 | /// Set total length for this progress 13 | /// 14 | /// # Arguments 15 | /// 16 | /// * `len` - The total length of this progress 17 | fn set_length(&self, len: u64); 18 | 19 | /// Set title for this progress 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `title` - The title of this progress 24 | fn set_title(&self, title: &'static str); 25 | 26 | /// Advance progress by given increment 27 | /// 28 | /// # Arguments 29 | /// 30 | /// * `inc` - The increment to advance this progress 31 | fn inc(&self, inc: u64); 32 | 33 | /// Finish the progress 34 | fn finish(&self); 35 | } 36 | 37 | /// Trait to start progress information report progress information for any rustic action which supports that. 38 | /// 39 | /// Implement this trait when you want to display this progress to your users. 40 | pub trait ProgressBars { 41 | /// The actual type which is able to show the progress 42 | type P: Progress; 43 | 44 | /// Start a new progress, which is hidden 45 | fn progress_hidden(&self) -> Self::P; 46 | 47 | /// Start a new progress spinner. Note that this progress doesn't get a length and is not advanced, only finished. 48 | /// 49 | /// # Arguments 50 | /// 51 | /// * `prefix` - The prefix of the progress 52 | fn progress_spinner(&self, prefix: impl Into>) -> Self::P; 53 | 54 | /// Start a new progress which counts something 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `prefix` - The prefix of the progress 59 | fn progress_counter(&self, prefix: impl Into>) -> Self::P; 60 | 61 | /// Start a new progress which counts bytes 62 | /// 63 | /// # Arguments 64 | /// 65 | /// * `prefix` - The prefix of the progress 66 | fn progress_bytes(&self, prefix: impl Into>) -> Self::P; 67 | } 68 | 69 | /// A dummy struct which shows no progress but only logs titles and end of a progress. 70 | #[derive(Clone, Copy, Debug)] 71 | pub struct NoProgress; 72 | 73 | impl Progress for NoProgress { 74 | fn is_hidden(&self) -> bool { 75 | true 76 | } 77 | fn set_length(&self, _len: u64) {} 78 | fn set_title(&self, title: &'static str) { 79 | info!("{title}"); 80 | } 81 | fn inc(&self, _inc: u64) {} 82 | fn finish(&self) { 83 | info!("finished."); 84 | } 85 | } 86 | 87 | /// Don't show progress bars, only log rudimentary progress information. 88 | #[derive(Clone, Copy, Debug)] 89 | pub struct NoProgressBars; 90 | 91 | impl ProgressBars for NoProgressBars { 92 | type P = NoProgress; 93 | fn progress_spinner(&self, prefix: impl Into>) -> Self::P { 94 | info!("{}", prefix.into()); 95 | NoProgress 96 | } 97 | fn progress_counter(&self, prefix: impl Into>) -> Self::P { 98 | info!("{}", prefix.into()); 99 | NoProgress 100 | } 101 | fn progress_hidden(&self) -> Self::P { 102 | NoProgress 103 | } 104 | fn progress_bytes(&self, prefix: impl Into>) -> Self::P { 105 | info!("{}", prefix.into()); 106 | NoProgress 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /crates/core/src/repofile.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use serde::{Serialize, de::DeserializeOwned}; 4 | 5 | pub(crate) mod configfile; 6 | pub(crate) mod indexfile; 7 | pub(crate) mod keyfile; 8 | pub(crate) mod packfile; 9 | pub(crate) mod snapshotfile; 10 | 11 | /// Marker trait for repository files which are stored as encrypted JSON 12 | pub trait RepoFile: Serialize + DeserializeOwned + Sized + Send + Sync + 'static { 13 | /// The [`FileType`] associated with the repository file 14 | const TYPE: FileType; 15 | /// The Id type associated with the repository file 16 | type Id: From + Send; 17 | } 18 | 19 | /// Marker trait for Ids which identify repository files 20 | pub trait RepoId: Deref + From + Sized + Send + Sync + 'static { 21 | /// The [`FileType`] associated with Id type 22 | const TYPE: FileType; 23 | } 24 | 25 | #[macro_export] 26 | /// Generate newtypes for `Id`s identifying Repository files 27 | macro_rules! impl_repoid { 28 | ($a:ident, $b: expr) => { 29 | $crate::define_new_id_struct!($a, concat!("repository file of type", stringify!($b))); 30 | impl $crate::repofile::RepoId for $a { 31 | const TYPE: FileType = $b; 32 | } 33 | }; 34 | } 35 | 36 | #[macro_export] 37 | /// Generate newtypes for `Id`s identifying Repository files implementing `RepoFile` 38 | macro_rules! impl_repofile { 39 | ($a:ident, $b: expr, $c: ty) => { 40 | $crate::impl_repoid!($a, $b); 41 | impl RepoFile for $c { 42 | const TYPE: FileType = $b; 43 | type Id = $a; 44 | } 45 | }; 46 | } 47 | 48 | // Part of public API 49 | use crate::Id; 50 | 51 | pub use { 52 | crate::{ 53 | backend::{ 54 | ALL_FILE_TYPES, FileType, 55 | node::{Metadata, Node, NodeType}, 56 | }, 57 | blob::{ALL_BLOB_TYPES, BlobType, tree::Tree}, 58 | }, 59 | configfile::ConfigFile, 60 | indexfile::{IndexBlob, IndexFile, IndexId, IndexPack}, 61 | keyfile::{KeyFile, KeyId}, 62 | packfile::{HeaderEntry, PackHeader, PackHeaderLength, PackHeaderRef, PackId}, 63 | snapshotfile::{DeleteOption, PathList, SnapshotFile, SnapshotId, SnapshotSummary, StringList}, 64 | }; 65 | -------------------------------------------------------------------------------- /crates/core/src/vfs/format.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::repofile::SnapshotFile; 4 | 5 | use runtime_format::{FormatKey, FormatKeyError}; 6 | 7 | /// A formatted snapshot. 8 | /// 9 | /// To be formatted with [`runtime_format`]. 10 | /// 11 | /// The following keys are available: 12 | /// * `id`: the snapshot id 13 | /// * `long_id`: the snapshot id as a string 14 | /// * `time`: the snapshot time 15 | /// * `username`: the snapshot username 16 | /// * `hostname`: the snapshot hostname 17 | /// * `label`: the snapshot label 18 | /// * `tags`: the snapshot tags 19 | /// * `backup_start`: the snapshot backup start time 20 | /// * `backup_end`: the snapshot backup end time 21 | #[derive(Debug)] 22 | pub(crate) struct FormattedSnapshot<'a> { 23 | /// The snapshot file. 24 | pub(crate) snap: &'a SnapshotFile, 25 | /// The time format to use. 26 | pub(crate) time_format: &'a str, 27 | } 28 | 29 | impl FormatKey for FormattedSnapshot<'_> { 30 | fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { 31 | match key { 32 | "id" => write!(f, "{}", self.snap.id), 33 | "long_id" => write!(f, "{:?}", self.snap.id), 34 | "time" => write!(f, "{}", self.snap.time.format(self.time_format)), 35 | "username" => write!(f, "{}", self.snap.username), 36 | "hostname" => write!(f, "{}", self.snap.hostname), 37 | "label" => write!(f, "{}", self.snap.label), 38 | "tags" => write!(f, "{}", self.snap.tags), 39 | "backup_start" => { 40 | if let Some(summary) = &self.snap.summary { 41 | write!(f, "{}", summary.backup_start.format(self.time_format)) 42 | } else { 43 | write!(f, "no_backup_start") 44 | } 45 | } 46 | "backup_end" => { 47 | if let Some(summary) = &self.snap.summary { 48 | write!(f, "{}", summary.backup_end.format(self.time_format)) 49 | } else { 50 | write!(f, "no_backup_end") 51 | } 52 | } 53 | 54 | _ => return Err(FormatKeyError::UnknownKey), 55 | } 56 | .map_err(FormatKeyError::Fmt) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/core/tests/command_input.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | #[cfg(not(windows))] 3 | use std::fs::File; 4 | 5 | use anyhow::Result; 6 | use rustic_core::CommandInput; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[cfg(not(windows))] 10 | use tempfile::tempdir; 11 | 12 | #[test] 13 | fn from_str() -> Result<()> { 14 | let cmd: CommandInput = "echo test".parse()?; 15 | assert_eq!(cmd.command(), "echo"); 16 | assert_eq!(cmd.args(), ["test"]); 17 | 18 | let cmd: CommandInput = r#"echo "test test" test"#.parse()?; 19 | assert_eq!(cmd.command(), "echo"); 20 | assert_eq!(cmd.args(), ["test test", "test"]); 21 | Ok(()) 22 | } 23 | 24 | #[cfg(not(windows))] 25 | #[test] 26 | fn from_str_failed() { 27 | let failed_cmd: std::result::Result = "echo \"test test".parse(); 28 | assert!(failed_cmd.is_err()); 29 | } 30 | 31 | #[test] 32 | fn toml() -> Result<()> { 33 | #[derive(Deserialize, Serialize)] 34 | struct Test { 35 | command1: CommandInput, 36 | command2: CommandInput, 37 | command3: CommandInput, 38 | command4: CommandInput, 39 | } 40 | 41 | let test = toml::from_str::( 42 | r#" 43 | command1 = "echo test" 44 | command2 = {command = "echo", args = ["test test"], on-failure = "error"} 45 | command3 = {command = "echo", args = ["test test", "test"]} 46 | command4 = {command = "echo", args = ["test test", "test"], on-failure = "warn"} 47 | "#, 48 | )?; 49 | 50 | assert_eq!(test.command1.command(), "echo"); 51 | assert_eq!(test.command1.args(), ["test"]); 52 | assert_eq!(test.command2.command(), "echo"); 53 | assert_eq!(test.command2.args(), ["test test"]); 54 | assert_eq!(test.command3.command(), "echo"); 55 | assert_eq!(test.command3.args(), ["test test", "test"]); 56 | 57 | let test_ser = toml::to_string(&test)?; 58 | assert_eq!( 59 | test_ser, 60 | r#"command1 = "echo test" 61 | command2 = "echo 'test test'" 62 | command3 = "echo 'test test' test" 63 | 64 | [command4] 65 | command = "echo" 66 | args = ["test test", "test"] 67 | on-failure = "warn" 68 | "# 69 | ); 70 | Ok(()) 71 | } 72 | 73 | #[test] 74 | fn run_empty() -> Result<()> { 75 | // empty command 76 | let command: CommandInput = "".parse()?; 77 | dbg!(&command); 78 | assert!(!command.is_set()); 79 | command.run("test", "empty")?; 80 | Ok(()) 81 | } 82 | 83 | #[cfg(not(windows))] 84 | #[test] 85 | fn run_deletey() -> Result<()> { 86 | // create a tmp file which will be removed by 87 | let dir = tempdir()?; 88 | let filename = dir.path().join("file"); 89 | let _ = File::create(&filename)?; 90 | assert!(filename.exists()); 91 | 92 | let command: CommandInput = format!("rm {}", filename.to_str().unwrap()).parse()?; 93 | assert!(command.is_set()); 94 | command.run("test", "test-call")?; 95 | assert!(!filename.exists()); 96 | 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /crates/core/tests/errors.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | use rstest::{fixture, rstest}; 3 | 4 | use rustic_core::{ErrorKind, RusticError, Severity, Status}; 5 | 6 | #[fixture] 7 | fn error() -> Box { 8 | RusticError::with_source( 9 | ErrorKind::InputOutput, 10 | "A file could not be read, make sure the file at `{path}` is existing and readable by the system.", 11 | std::io::Error::new(std::io::ErrorKind::ConnectionReset, "Networking Error"), 12 | ) 13 | .attach_context("path", "/path/to/file") 14 | .attach_context("called", "used s3 backend") 15 | .attach_status(Status::Permanent) 16 | .attach_severity(Severity::Error) 17 | .attach_error_code("C001") 18 | .append_guidance_line("Appended guidance line") 19 | .prepend_guidance_line("Prepended guidance line") 20 | .attach_existing_issue_url("https://github.com/rustic-rs/rustic_core/issues/209") 21 | .ask_report() 22 | } 23 | 24 | #[fixture] 25 | fn minimal_error() -> Box { 26 | RusticError::new( 27 | ErrorKind::InputOutput, 28 | "A file could not be read, make sure the file at `{path}` is existing and readable by the system.", 29 | ) 30 | .attach_context("path", "/path/to/file") 31 | } 32 | 33 | #[rstest] 34 | #[allow(clippy::boxed_local)] 35 | fn test_error_display(error: Box) { 36 | insta::assert_snapshot!(error); 37 | } 38 | 39 | #[rstest] 40 | #[allow(clippy::boxed_local)] 41 | fn test_error_debug(error: Box) { 42 | insta::assert_debug_snapshot!(error); 43 | } 44 | 45 | #[rstest] 46 | #[allow(clippy::boxed_local)] 47 | fn test_log_output_passes(error: Box) { 48 | insta::assert_snapshot!(error.display_log()); 49 | } 50 | 51 | #[rstest] 52 | #[allow(clippy::boxed_local)] 53 | fn test_log_output_minimal_passes(minimal_error: Box) { 54 | insta::assert_snapshot!(minimal_error.display_log()); 55 | } 56 | -------------------------------------------------------------------------------- /crates/core/tests/fixtures/backup-data.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustic-rs/rustic_core/213665e7a5d1a27df91b29b122b35aac24a76a83/crates/core/tests/fixtures/backup-data.tar.gz -------------------------------------------------------------------------------- /crates/core/tests/fixtures/config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustic-rs/rustic_core/213665e7a5d1a27df91b29b122b35aac24a76a83/crates/core/tests/fixtures/config -------------------------------------------------------------------------------- /crates/core/tests/fixtures/key-failing: -------------------------------------------------------------------------------- 1 | {"kdf":"scrypt","N":262144,"r":1,"p":1,"salt":"onIvDlHSFQHnmhBHIGD0WNY3GceOFTaxzxy7YgeSiqva+GhPRrH1+jxuxwcWiEg+WW55C3KurmLNhgZ1DJ75GA==","data":"PZUCr1IJQuOrgE2wKC26phAkP/CIFnvBJ+JEGub8DB4FRJ/nGvVPFr+pQRAE9GUbwHbdKYoZVVciYc0E8S5ggurAcu8VSdoRIg8z4iabKBi1YrTkG0+EZY+rlpm95LMfoDgeY6XxQ6egSd3GSk0WfT//YH7duDkCK3rYIczhOXWOCqzKR85z11YW1pB7uUDc2yiRXm+B5rrivz+ntb0wIg=="} 2 | -------------------------------------------------------------------------------- /crates/core/tests/fixtures/key1: -------------------------------------------------------------------------------- 1 | {"kdf":"scrypt","N":32768,"r":8,"p":6,"salt":"onIvDlHSFQHnmhBHIGD0WNY3GceOFTaxzxy7YgeSiqva+GhPRrH1+jxuxwcWiEg+WW55C3KurmLNhgZ1DJ75GA==","data":"PZUCr1IJQuOrgE2wKC26phAkP/CIFnvBJ+JEGub8DB4FRJ/nGvVPFr+pQRAE9GUbwHbdKYoZVVciYc0E8S5ggurAcu8VSdoRIg8z4iabKBi1YrTkG0+EZY+rlpm95LMfoDgeY6XxQ6egSd3GSk0WfT//YH7duDkCK3rYIczhOXWOCqzKR85z11YW1pB7uUDc2yiRXm+B5rrivz+ntb0wIg=="} 2 | -------------------------------------------------------------------------------- /crates/core/tests/fixtures/key2: -------------------------------------------------------------------------------- 1 | {"kdf":"scrypt","N":131072,"r":8,"p":1,"data":"WrMLrTa/yGbDAZmftGe10/34wf7YQmzgex78J2U23ngNS/V5alQ0yxeoKghzUgWUja2K2KlnXtrFG9H/SNyNCyiCjZePZyaP21KtqyTJEPWw44WnX60iN0sOvKaoPgutIUMjyIvGej6zCoocu777ZiLbmOWRa1uF3rAcnyja8S1QTP+mST8A18TWTblcBjRPcRmyW/fZmNA7OrccQqz6kA==","salt":"vxXAZGxn56VRe2Zy/MBV1I97Wgi0xVhye89qz9oXruorYgWyewle5ZqkepZwXWhsyIzuWKWIVNsZigAzx064Hg=="} 2 | -------------------------------------------------------------------------------- /crates/core/tests/integration/find.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | path::{Path, PathBuf}, 3 | str::FromStr, 4 | }; 5 | 6 | use anyhow::Result; 7 | use globset::Glob; 8 | use rstest::rstest; 9 | 10 | use rustic_core::{ 11 | BackupOptions, FindMatches, FindNode, 12 | repofile::{Node, SnapshotFile}, 13 | }; 14 | 15 | use super::{RepoOpen, TestSource, assert_with_win, set_up_repo, tar_gz_testdata}; 16 | 17 | #[rstest] 18 | fn test_find(tar_gz_testdata: Result, set_up_repo: Result) -> Result<()> { 19 | // Fixtures 20 | let (source, repo) = (tar_gz_testdata?, set_up_repo?.to_indexed_ids()?); 21 | let paths = &source.path_list(); 22 | 23 | // we use as_path to not depend on the actual tempdir 24 | let opts = BackupOptions::default().as_path(PathBuf::from_str("test")?); 25 | // backup test-data 26 | let snapshot = repo.backup(&opts, paths, SnapshotFile::default())?; 27 | 28 | // re-read index 29 | let repo = repo.to_indexed_ids()?; 30 | 31 | // test non-existing path 32 | let not_found = repo.find_nodes_from_path(vec![snapshot.tree], Path::new("not_existing"))?; 33 | assert_with_win("find-nodes-not-found", not_found); 34 | // test non-existing match 35 | let glob = Glob::new("not_existing")?.compile_matcher(); 36 | let not_found = 37 | repo.find_matching_nodes(vec![snapshot.tree], &|path, _| glob.is_match(path))?; 38 | assert_with_win("find-matching-nodes-not-found", not_found); 39 | 40 | // test existing path 41 | let FindNode { matches, .. } = 42 | repo.find_nodes_from_path(vec![snapshot.tree], Path::new("test/0/tests/testfile"))?; 43 | assert_with_win("find-nodes-existing", matches); 44 | // test existing match 45 | let glob = Glob::new("testfile")?.compile_matcher(); 46 | let match_func = |path: &Path, _: &Node| { 47 | glob.is_match(path) || path.file_name().is_some_and(|f| glob.is_match(f)) 48 | }; 49 | let FindMatches { paths, matches, .. } = 50 | repo.find_matching_nodes(vec![snapshot.tree], &match_func)?; 51 | assert_with_win("find-matching-existing", (paths, matches)); 52 | // test existing match 53 | let glob = Glob::new("testfile*")?.compile_matcher(); 54 | let match_func = |path: &Path, _: &Node| { 55 | glob.is_match(path) || path.file_name().is_some_and(|f| glob.is_match(f)) 56 | }; 57 | let FindMatches { paths, matches, .. } = 58 | repo.find_matching_nodes(vec![snapshot.tree], &match_func)?; 59 | assert_with_win("find-matching-wildcard-existing", (paths, matches)); 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /crates/core/tests/integration/ls.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, ffi::OsStr}; 2 | use std::{path::PathBuf, str::FromStr}; 3 | 4 | use anyhow::Result; 5 | use insta::Settings; 6 | use rstest::rstest; 7 | 8 | use rustic_core::{ 9 | BackupOptions, LsOptions, RusticResult, 10 | repofile::{Metadata, Node, SnapshotFile}, 11 | }; 12 | 13 | use super::{ 14 | RepoOpen, TestSource, assert_with_win, insta_node_redaction, set_up_repo, tar_gz_testdata, 15 | }; 16 | 17 | #[rstest] 18 | fn test_ls( 19 | tar_gz_testdata: Result, 20 | set_up_repo: Result, 21 | insta_node_redaction: Settings, 22 | ) -> Result<()> { 23 | // Fixtures 24 | let (source, repo) = (tar_gz_testdata?, set_up_repo?.to_indexed_ids()?); 25 | let paths = &source.path_list(); 26 | 27 | // we use as_path to not depend on the actual tempdir 28 | let opts = BackupOptions::default().as_path(PathBuf::from_str("test")?); 29 | // backup test-data 30 | let snapshot = repo.backup(&opts, paths, SnapshotFile::default())?; 31 | 32 | // test non-existing entries 33 | let mut node = Node::new_node( 34 | OsStr::new(""), 35 | rustic_core::repofile::NodeType::Dir, 36 | Metadata::default(), 37 | ); 38 | node.subtree = Some(snapshot.tree); 39 | 40 | // re-read index 41 | let repo = repo.to_indexed_ids()?; 42 | 43 | let entries: BTreeMap<_, _> = repo 44 | .ls(&node, &LsOptions::default())? 45 | .collect::>()?; 46 | 47 | insta_node_redaction.bind(|| { 48 | assert_with_win("ls", &entries); 49 | }); 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /crates/core/tests/integration/prune.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::Result; 4 | use rstest::rstest; 5 | 6 | use rustic_core::{ 7 | BackupOptions, CheckOptions, LimitOption, PathList, PruneOptions, repofile::SnapshotFile, 8 | }; 9 | 10 | use super::{RepoOpen, TestSource, set_up_repo, tar_gz_testdata}; 11 | 12 | #[rstest] 13 | fn test_prune( 14 | tar_gz_testdata: Result, 15 | set_up_repo: Result, 16 | #[values(true, false)] instant_delete: bool, 17 | #[values( 18 | LimitOption::Percentage(0), 19 | LimitOption::Percentage(50), 20 | LimitOption::Unlimited 21 | )] 22 | max_unused: LimitOption, 23 | ) -> Result<()> { 24 | // Fixtures 25 | let (source, repo) = (tar_gz_testdata?, set_up_repo?.to_indexed_ids()?); 26 | 27 | let opts = BackupOptions::default(); 28 | 29 | // first backup 30 | let paths = PathList::from_iter(Some(source.0.path().join("0/0/9"))); 31 | let snapshot1 = repo.backup(&opts, &paths, SnapshotFile::default())?; 32 | 33 | // re-read index 34 | let repo = repo.to_indexed_ids()?; 35 | // second backup 36 | let paths = PathList::from_iter(Some(source.0.path().join("0/0/9/2"))); 37 | let _ = repo.backup(&opts, &paths, SnapshotFile::default())?; 38 | 39 | // re-read index 40 | let repo = repo.to_indexed_ids()?; 41 | // third backup 42 | let paths = PathList::from_iter(Some(source.0.path().join("0/0/9/3"))); 43 | let _ = repo.backup(&opts, &paths, SnapshotFile::default())?; 44 | 45 | // drop index 46 | let repo = repo.drop_index(); 47 | repo.delete_snapshots(&[snapshot1.id])?; 48 | 49 | // get prune plan 50 | let prune_opts = PruneOptions::default() 51 | .instant_delete(instant_delete) 52 | .max_unused(max_unused) 53 | .keep_delete(Duration::ZERO); 54 | let plan = repo.prune_plan(&prune_opts)?; 55 | // TODO: Snapshot-test the plan (currently doesn't impl Serialize) 56 | // assert_ron_snapshot!("prune", plan); 57 | repo.prune(&prune_opts, plan)?; 58 | 59 | // run check 60 | let check_opts = CheckOptions::default().read_data(true); 61 | repo.check(check_opts)?; 62 | 63 | if !instant_delete { 64 | // re-run if we only marked pack files. As keep-delete = 0, they should be removed here 65 | let plan = repo.prune_plan(&prune_opts)?; 66 | repo.prune(&prune_opts, plan)?; 67 | repo.check(check_opts)?; 68 | } 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /crates/core/tests/integration/restore.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /crates/core/tests/integration/vfs.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, str::FromStr}; 2 | 3 | use anyhow::Result; 4 | use bytes::Bytes; 5 | use insta::Settings; 6 | use pretty_assertions::assert_eq; 7 | use rstest::rstest; 8 | 9 | use rustic_core::{BackupOptions, repofile::SnapshotFile, vfs::Vfs}; 10 | 11 | use super::{ 12 | RepoOpen, TestSource, assert_with_win, insta_node_redaction, set_up_repo, tar_gz_testdata, 13 | }; 14 | 15 | #[rstest] 16 | fn test_vfs( 17 | tar_gz_testdata: Result, 18 | set_up_repo: Result, 19 | insta_node_redaction: Settings, 20 | ) -> Result<()> { 21 | // Fixtures 22 | let (source, repo) = (tar_gz_testdata?, set_up_repo?.to_indexed_ids()?); 23 | let paths = &source.path_list(); 24 | 25 | // we use as_path to not depend on the actual tempdir 26 | let opts = BackupOptions::default().as_path(PathBuf::from_str("test")?); 27 | // backup test-data 28 | let snapshot = repo.backup(&opts, paths, SnapshotFile::default())?; 29 | 30 | // re-read index 31 | let repo = repo.to_indexed()?; 32 | // create Vfs 33 | let node = repo.node_from_snapshot_and_path(&snapshot, "")?; 34 | let vfs = Vfs::from_dir_node(&node); 35 | 36 | // test reading a directory using vfs 37 | let path: PathBuf = ["test", "0", "tests"].iter().collect(); 38 | let entries = vfs.dir_entries_from_path(&repo, &path)?; 39 | insta_node_redaction.bind(|| { 40 | assert_with_win("vfs", &entries); 41 | }); 42 | 43 | // test reading a file from the repository 44 | let path: PathBuf = ["test", "0", "tests", "testfile"].iter().collect(); 45 | let node = vfs.node_from_path(&repo, &path)?; 46 | let file = repo.open_file(&node)?; 47 | 48 | let data = repo.read_file_at(&file, 0, 21)?; // read full content 49 | assert_eq!(Bytes::from("This is a test file.\n"), &data); 50 | assert_eq!(data, repo.read_file_at(&file, 0, 4096)?); // read beyond file end 51 | assert_eq!(Bytes::new(), repo.read_file_at(&file, 25, 1)?); // offset beyond file end 52 | assert_eq!(Bytes::from("test"), repo.read_file_at(&file, 10, 4)?); // read partial content 53 | 54 | // test reading an empty file from the repository 55 | let path: PathBuf = ["test", "0", "tests", "empty-file"].iter().collect(); 56 | let node = vfs.node_from_path(&repo, &path)?; 57 | let file = repo.open_file(&node)?; 58 | assert_eq!(Bytes::new(), repo.read_file_at(&file, 0, 0)?); // empty files 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /crates/core/tests/keys.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | use std::{fs::File, io::Read, sync::Arc}; 3 | 4 | use anyhow::Result; 5 | use rstest::rstest; 6 | use rustic_core::{FileType, Id, Repository, RepositoryBackends, RepositoryOptions, WriteBackend}; 7 | use rustic_testing::backend::in_memory_backend::InMemoryBackend; 8 | use sha2::{Digest, Sha256}; 9 | 10 | #[rstest] 11 | #[case("test", true)] 12 | #[case("test2", true)] 13 | #[case("wrong", false)] 14 | fn test_working_keys_passes(#[case] password: &str, #[case] should_work: bool) -> Result<()> { 15 | let be = InMemoryBackend::new(); 16 | add_to_be(&be, FileType::Config, "tests/fixtures/config")?; 17 | add_to_be(&be, FileType::Key, "tests/fixtures/key1")?; 18 | add_to_be(&be, FileType::Key, "tests/fixtures/key2")?; 19 | 20 | let be = RepositoryBackends::new(Arc::new(be), None); 21 | let options = RepositoryOptions::default().password(password); 22 | let repo = Repository::new(&options, &be)?; 23 | if should_work { 24 | assert!(repo.open().is_ok()); 25 | } else { 26 | assert!(repo.open().is_err_and(|err| err.is_incorrect_password())); 27 | } 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | // using an invalid keyfile: Here the scrypt params are not valid 33 | fn test_keys_failing_passes() -> Result<()> { 34 | let be = InMemoryBackend::new(); 35 | add_to_be(&be, FileType::Config, "tests/fixtures/config")?; 36 | add_to_be(&be, FileType::Key, "tests/fixtures/key-failing")?; 37 | 38 | let be = RepositoryBackends::new(Arc::new(be), None); 39 | let options = RepositoryOptions::default().password("test"); 40 | let repo = Repository::new(&options, &be)?; 41 | assert!(repo.open().is_err_and(|err| !err.is_incorrect_password())); 42 | Ok(()) 43 | } 44 | 45 | fn add_to_be(be: &impl WriteBackend, tpe: FileType, file: &str) -> Result<()> { 46 | let mut bytes = Vec::new(); 47 | _ = File::open(file)?.read_to_end(&mut bytes)?; 48 | let id = Id::new(Sha256::digest(&bytes).into()); 49 | be.write_bytes(tpe, &id, true, bytes.into())?; 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/errors__error_debug.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/errors.rs 3 | expression: error 4 | --- 5 | RusticError { 6 | kind: InputOutput, 7 | guidance: "Prepended guidance line\nA file could not be read, make sure the file at `{path}` is existing and readable by the system.\nAppended guidance line", 8 | docs_url: None, 9 | error_code: Some( 10 | "C001", 11 | ), 12 | ask_report: true, 13 | existing_issue_urls: [ 14 | "https://github.com/rustic-rs/rustic_core/issues/209", 15 | ], 16 | new_issue_url: None, 17 | context: [ 18 | ( 19 | "path", 20 | "/path/to/file", 21 | ), 22 | ( 23 | "called", 24 | "used s3 backend", 25 | ), 26 | ], 27 | source: Some( 28 | Custom { 29 | kind: ConnectionReset, 30 | error: "Networking Error", 31 | }, 32 | ), 33 | severity: Some( 34 | Error, 35 | ), 36 | status: Some( 37 | Permanent, 38 | ), 39 | backtrace: Some( 40 | , 41 | ), 42 | } 43 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/errors__error_display.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/errors.rs 3 | expression: error 4 | --- 5 | `rustic_core` experienced an error related to `input/output operations`. 6 | 7 | Message: 8 | Prepended guidance line 9 | A file could not be read, make sure the file at `/path/to/file` is existing and readable by the system. 10 | Appended guidance line 11 | 12 | For more information, see: https://rustic.cli.rs/docs/errors/C001 13 | 14 | Related issues: 15 | - https://github.com/rustic-rs/rustic_core/issues/209 16 | 17 | We believe this is a bug, please report it by opening an issue at: 18 | https://github.com/rustic-rs/rustic_core/issues/new 19 | 20 | If you can, please attach an anonymized debug log to the issue. 21 | 22 | Thank you for helping us improve rustic! 23 | 24 | 25 | Some additional details ... 26 | 27 | Context: 28 | - called: used s3 backend 29 | 30 | Caused by: 31 | Networking Error 32 | 33 | 34 | Severity: Error 35 | 36 | Status: Permanent 37 | 38 | Backtrace: 39 | disabled backtrace (set 'RUST_BACKTRACE="1"' environment variable to enable) 40 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/errors__log_output_minimal_passes.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/errors.rs 3 | expression: minimal_error.to_log_output() 4 | --- 5 | Error: A file could not be read, make sure the file at `/path/to/file` is existing and readable by the system. (kind: related to input/output operations) 6 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/errors__log_output_passes.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/errors.rs 3 | expression: error.to_log_output() 4 | --- 5 | Error: Prepended guidance line 6 | A file could not be read, make sure the file at `/path/to/file` is existing and readable by the system. 7 | Appended guidance line (kind: related to input/output operations, code: C001): caused by: Networking Error 8 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-groups-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | (SnapshotGroup( 7 | tags: Some(StringList([])), 8 | ), [ 9 | SnapshotFile( 10 | time: "[time]", 11 | program_version: "rustic [rustic_core_version]", 12 | tree: "[tree_id]", 13 | paths: StringList([ 14 | "test", 15 | ]), 16 | hostname: "[hostname]", 17 | username: "", 18 | uid: 0, 19 | gid: 0, 20 | tags: StringList([]), 21 | original: "[original]", 22 | summary: Some(SnapshotSummary( 23 | files_new: 73, 24 | files_changed: 0, 25 | files_unmodified: 0, 26 | total_files_processed: 73, 27 | total_bytes_processed: 1125674, 28 | dirs_new: 6, 29 | dirs_changed: 0, 30 | dirs_unmodified: 0, 31 | total_dirs_processed: 6, 32 | total_dirsize_processed: "[total_dirsize_processed]", 33 | data_blobs: 70, 34 | tree_blobs: 6, 35 | data_added: "[data_added]", 36 | data_added_packed: "[data_added_packed]", 37 | data_added_files: 1125653, 38 | data_added_files_packed: 78740, 39 | data_added_trees: "[data_added_trees]", 40 | data_added_trees_packed: "[data_added_trees_packed]", 41 | command: "[command]", 42 | backup_start: "[backup_start]", 43 | backup_end: "[backup_end]", 44 | backup_duration: "[backup_duration]", 45 | total_duration: "[total_duration]", 46 | )), 47 | id: "[id]", 48 | ), 49 | SnapshotFile( 50 | time: "[time]", 51 | program_version: "rustic [rustic_core_version]", 52 | parent: "[some]", 53 | tree: "[tree_id]", 54 | paths: StringList([ 55 | "test", 56 | ]), 57 | hostname: "[hostname]", 58 | username: "", 59 | uid: 0, 60 | gid: 0, 61 | tags: StringList([]), 62 | original: "[original]", 63 | summary: Some(SnapshotSummary( 64 | files_new: 0, 65 | files_changed: 0, 66 | files_unmodified: 73, 67 | total_files_processed: 73, 68 | total_bytes_processed: 1125682, 69 | dirs_new: 0, 70 | dirs_changed: 0, 71 | dirs_unmodified: 6, 72 | total_dirs_processed: 6, 73 | total_dirsize_processed: "[total_dirsize_processed]", 74 | data_blobs: 0, 75 | tree_blobs: 0, 76 | data_added: "[data_added]", 77 | data_added_packed: "[data_added_packed]", 78 | data_added_files: 0, 79 | data_added_files_packed: 0, 80 | data_added_trees: "[data_added_trees]", 81 | data_added_trees_packed: "[data_added_trees_packed]", 82 | command: "[command]", 83 | backup_start: "[backup_start]", 84 | backup_end: "[backup_end]", 85 | backup_duration: "[backup_duration]", 86 | total_duration: "[total_duration]", 87 | )), 88 | id: "[id]", 89 | ), 90 | ]), 91 | (SnapshotGroup( 92 | tags: Some(StringList([ 93 | "a", 94 | "b", 95 | ])), 96 | ), [ 97 | SnapshotFile( 98 | time: "[time]", 99 | program_version: "rustic [rustic_core_version]", 100 | parent: "[some]", 101 | tree: "[tree_id]", 102 | paths: StringList([ 103 | "test", 104 | ]), 105 | hostname: "[hostname]", 106 | username: "", 107 | uid: 0, 108 | gid: 0, 109 | tags: StringList([ 110 | "a", 111 | "b", 112 | ]), 113 | original: "[original]", 114 | summary: Some(SnapshotSummary( 115 | files_new: 0, 116 | files_changed: 0, 117 | files_unmodified: 73, 118 | total_files_processed: 73, 119 | total_bytes_processed: 1125682, 120 | dirs_new: 0, 121 | dirs_changed: 0, 122 | dirs_unmodified: 6, 123 | total_dirs_processed: 6, 124 | total_dirsize_processed: "[total_dirsize_processed]", 125 | data_blobs: 0, 126 | tree_blobs: 0, 127 | data_added: "[data_added]", 128 | data_added_packed: "[data_added_packed]", 129 | data_added_files: 0, 130 | data_added_files_packed: 0, 131 | data_added_trees: "[data_added_trees]", 132 | data_added_trees_packed: "[data_added_trees_packed]", 133 | command: "[command]", 134 | backup_start: "[backup_start]", 135 | backup_end: "[backup_end]", 136 | backup_duration: "[backup_duration]", 137 | total_duration: "[total_duration]", 138 | )), 139 | id: "[id]", 140 | ), 141 | ]), 142 | ] 143 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-groups-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | (SnapshotGroup( 7 | tags: Some(StringList([])), 8 | ), [ 9 | SnapshotFile( 10 | time: "[time]", 11 | program_version: "rustic [rustic_core_version]", 12 | tree: "[tree_id]", 13 | paths: StringList([ 14 | "test", 15 | ]), 16 | hostname: "[hostname]", 17 | username: "", 18 | uid: 0, 19 | gid: 0, 20 | tags: StringList([]), 21 | original: "[original]", 22 | summary: Some(SnapshotSummary( 23 | files_new: 73, 24 | files_changed: 0, 25 | files_unmodified: 0, 26 | total_files_processed: 73, 27 | total_bytes_processed: 1125674, 28 | dirs_new: 6, 29 | dirs_changed: 0, 30 | dirs_unmodified: 0, 31 | total_dirs_processed: 6, 32 | total_dirsize_processed: "[total_dirsize_processed]", 33 | data_blobs: 70, 34 | tree_blobs: 6, 35 | data_added: "[data_added]", 36 | data_added_packed: "[data_added_packed]", 37 | data_added_files: 1125653, 38 | data_added_files_packed: 78740, 39 | data_added_trees: "[data_added_trees]", 40 | data_added_trees_packed: "[data_added_trees_packed]", 41 | command: "[command]", 42 | backup_start: "[backup_start]", 43 | backup_end: "[backup_end]", 44 | backup_duration: "[backup_duration]", 45 | total_duration: "[total_duration]", 46 | )), 47 | id: "[id]", 48 | ), 49 | SnapshotFile( 50 | time: "[time]", 51 | program_version: "rustic [rustic_core_version]", 52 | parent: "[some]", 53 | tree: "[tree_id]", 54 | paths: StringList([ 55 | "test", 56 | ]), 57 | hostname: "[hostname]", 58 | username: "", 59 | uid: 0, 60 | gid: 0, 61 | tags: StringList([]), 62 | original: "[original]", 63 | summary: Some(SnapshotSummary( 64 | files_new: 0, 65 | files_changed: 0, 66 | files_unmodified: 73, 67 | total_files_processed: 73, 68 | total_bytes_processed: 1125674, 69 | dirs_new: 0, 70 | dirs_changed: 0, 71 | dirs_unmodified: 6, 72 | total_dirs_processed: 6, 73 | total_dirsize_processed: "[total_dirsize_processed]", 74 | data_blobs: 0, 75 | tree_blobs: 0, 76 | data_added: "[data_added]", 77 | data_added_packed: "[data_added_packed]", 78 | data_added_files: 0, 79 | data_added_files_packed: 0, 80 | data_added_trees: "[data_added_trees]", 81 | data_added_trees_packed: "[data_added_trees_packed]", 82 | command: "[command]", 83 | backup_start: "[backup_start]", 84 | backup_end: "[backup_end]", 85 | backup_duration: "[backup_duration]", 86 | total_duration: "[total_duration]", 87 | )), 88 | id: "[id]", 89 | ), 90 | ]), 91 | (SnapshotGroup( 92 | tags: Some(StringList([ 93 | "a", 94 | "b", 95 | ])), 96 | ), [ 97 | SnapshotFile( 98 | time: "[time]", 99 | program_version: "rustic [rustic_core_version]", 100 | parent: "[some]", 101 | tree: "[tree_id]", 102 | paths: StringList([ 103 | "test", 104 | ]), 105 | hostname: "[hostname]", 106 | username: "", 107 | uid: 0, 108 | gid: 0, 109 | tags: StringList([ 110 | "a", 111 | "b", 112 | ]), 113 | original: "[original]", 114 | summary: Some(SnapshotSummary( 115 | files_new: 0, 116 | files_changed: 0, 117 | files_unmodified: 73, 118 | total_files_processed: 73, 119 | total_bytes_processed: 1125674, 120 | dirs_new: 0, 121 | dirs_changed: 0, 122 | dirs_unmodified: 6, 123 | total_dirs_processed: 6, 124 | total_dirsize_processed: "[total_dirsize_processed]", 125 | data_blobs: 0, 126 | tree_blobs: 0, 127 | data_added: "[data_added]", 128 | data_added_packed: "[data_added_packed]", 129 | data_added_files: 0, 130 | data_added_files_packed: 0, 131 | data_added_trees: "[data_added_trees]", 132 | data_added_trees_packed: "[data_added_trees_packed]", 133 | command: "[command]", 134 | backup_start: "[backup_start]", 135 | backup_end: "[backup_end]", 136 | backup_duration: "[backup_duration]", 137 | total_duration: "[total_duration]", 138 | )), 139 | id: "[id]", 140 | ), 141 | ]), 142 | ] 143 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-matching-snaps-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | SnapshotFile( 7 | time: "[time]", 8 | program_version: "rustic [rustic_core_version]", 9 | parent: "[some]", 10 | tree: "[tree_id]", 11 | paths: StringList([ 12 | "test", 13 | ]), 14 | hostname: "[hostname]", 15 | username: "", 16 | uid: 0, 17 | gid: 0, 18 | tags: StringList([ 19 | "a", 20 | "b", 21 | ]), 22 | original: "[original]", 23 | summary: Some(SnapshotSummary( 24 | files_new: 0, 25 | files_changed: 0, 26 | files_unmodified: 73, 27 | total_files_processed: 73, 28 | total_bytes_processed: 1125682, 29 | dirs_new: 0, 30 | dirs_changed: 0, 31 | dirs_unmodified: 6, 32 | total_dirs_processed: 6, 33 | total_dirsize_processed: "[total_dirsize_processed]", 34 | data_blobs: 0, 35 | tree_blobs: 0, 36 | data_added: "[data_added]", 37 | data_added_packed: "[data_added_packed]", 38 | data_added_files: 0, 39 | data_added_files_packed: 0, 40 | data_added_trees: "[data_added_trees]", 41 | data_added_trees_packed: "[data_added_trees_packed]", 42 | command: "[command]", 43 | backup_start: "[backup_start]", 44 | backup_end: "[backup_end]", 45 | backup_duration: "[backup_duration]", 46 | total_duration: "[total_duration]", 47 | )), 48 | id: "[id]", 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-matching-snaps-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | SnapshotFile( 7 | time: "[time]", 8 | program_version: "rustic [rustic_core_version]", 9 | parent: "[some]", 10 | tree: "[tree_id]", 11 | paths: StringList([ 12 | "test", 13 | ]), 14 | hostname: "[hostname]", 15 | username: "", 16 | uid: 0, 17 | gid: 0, 18 | tags: StringList([ 19 | "a", 20 | "b", 21 | ]), 22 | original: "[original]", 23 | summary: Some(SnapshotSummary( 24 | files_new: 0, 25 | files_changed: 0, 26 | files_unmodified: 73, 27 | total_files_processed: 73, 28 | total_bytes_processed: 1125674, 29 | dirs_new: 0, 30 | dirs_changed: 0, 31 | dirs_unmodified: 6, 32 | total_dirs_processed: 6, 33 | total_dirsize_processed: "[total_dirsize_processed]", 34 | data_blobs: 0, 35 | tree_blobs: 0, 36 | data_added: "[data_added]", 37 | data_added_packed: "[data_added_packed]", 38 | data_added_files: 0, 39 | data_added_files_packed: 0, 40 | data_added_trees: "[data_added_trees]", 41 | data_added_trees_packed: "[data_added_trees_packed]", 42 | command: "[command]", 43 | backup_start: "[backup_start]", 44 | backup_end: "[backup_end]", 45 | backup_duration: "[backup_duration]", 46 | total_duration: "[total_duration]", 47 | )), 48 | id: "[id]", 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-summary-first-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | tree: "[tree_id]", 9 | paths: StringList([ 10 | "test", 11 | ]), 12 | hostname: "[hostname]", 13 | username: "", 14 | uid: 0, 15 | gid: 0, 16 | tags: StringList([]), 17 | summary: Some(SnapshotSummary( 18 | files_new: 73, 19 | files_changed: 0, 20 | files_unmodified: 0, 21 | total_files_processed: 73, 22 | total_bytes_processed: 1125674, 23 | dirs_new: 6, 24 | dirs_changed: 0, 25 | dirs_unmodified: 0, 26 | total_dirs_processed: 6, 27 | total_dirsize_processed: "[total_dirsize_processed]", 28 | data_blobs: 70, 29 | tree_blobs: 6, 30 | data_added: "[data_added]", 31 | data_added_packed: "[data_added_packed]", 32 | data_added_files: 1125653, 33 | data_added_files_packed: 78740, 34 | data_added_trees: "[data_added_trees]", 35 | data_added_trees_packed: "[data_added_trees_packed]", 36 | command: "[command]", 37 | backup_start: "[backup_start]", 38 | backup_end: "[backup_end]", 39 | backup_duration: "[backup_duration]", 40 | total_duration: "[total_duration]", 41 | )), 42 | id: "[id]", 43 | ) 44 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-summary-first-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | tree: "[tree_id]", 9 | paths: StringList([ 10 | "test", 11 | ]), 12 | hostname: "[hostname]", 13 | username: "", 14 | uid: 0, 15 | gid: 0, 16 | tags: StringList([]), 17 | summary: Some(SnapshotSummary( 18 | files_new: 73, 19 | files_changed: 0, 20 | files_unmodified: 0, 21 | total_files_processed: 73, 22 | total_bytes_processed: 1125674, 23 | dirs_new: 6, 24 | dirs_changed: 0, 25 | dirs_unmodified: 0, 26 | total_dirs_processed: 6, 27 | total_dirsize_processed: "[total_dirsize_processed]", 28 | data_blobs: 70, 29 | tree_blobs: 6, 30 | data_added: "[data_added]", 31 | data_added_packed: "[data_added_packed]", 32 | data_added_files: 1125653, 33 | data_added_files_packed: 78740, 34 | data_added_trees: "[data_added_trees]", 35 | data_added_trees_packed: "[data_added_trees_packed]", 36 | command: "[command]", 37 | backup_start: "[backup_start]", 38 | backup_end: "[backup_end]", 39 | backup_duration: "[backup_duration]", 40 | total_duration: "[total_duration]", 41 | )), 42 | id: "[id]", 43 | ) 44 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-summary-second-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | parent: "[some]", 9 | tree: "[tree_id]", 10 | paths: StringList([ 11 | "test", 12 | ]), 13 | hostname: "[hostname]", 14 | username: "", 15 | uid: 0, 16 | gid: 0, 17 | tags: StringList([]), 18 | summary: Some(SnapshotSummary( 19 | files_new: 0, 20 | files_changed: 0, 21 | files_unmodified: 73, 22 | total_files_processed: 73, 23 | total_bytes_processed: 1125682, 24 | dirs_new: 0, 25 | dirs_changed: 0, 26 | dirs_unmodified: 6, 27 | total_dirs_processed: 6, 28 | total_dirsize_processed: "[total_dirsize_processed]", 29 | data_blobs: 0, 30 | tree_blobs: 0, 31 | data_added: "[data_added]", 32 | data_added_packed: "[data_added_packed]", 33 | data_added_files: 0, 34 | data_added_files_packed: 0, 35 | data_added_trees: "[data_added_trees]", 36 | data_added_trees_packed: "[data_added_trees_packed]", 37 | command: "[command]", 38 | backup_start: "[backup_start]", 39 | backup_end: "[backup_end]", 40 | backup_duration: "[backup_duration]", 41 | total_duration: "[total_duration]", 42 | )), 43 | id: "[id]", 44 | ) 45 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-summary-second-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | parent: "[some]", 9 | tree: "[tree_id]", 10 | paths: StringList([ 11 | "test", 12 | ]), 13 | hostname: "[hostname]", 14 | username: "", 15 | uid: 0, 16 | gid: 0, 17 | tags: StringList([]), 18 | summary: Some(SnapshotSummary( 19 | files_new: 0, 20 | files_changed: 0, 21 | files_unmodified: 73, 22 | total_files_processed: 73, 23 | total_bytes_processed: 1125674, 24 | dirs_new: 0, 25 | dirs_changed: 0, 26 | dirs_unmodified: 6, 27 | total_dirs_processed: 6, 28 | total_dirsize_processed: "[total_dirsize_processed]", 29 | data_blobs: 0, 30 | tree_blobs: 0, 31 | data_added: "[data_added]", 32 | data_added_packed: "[data_added_packed]", 33 | data_added_files: 0, 34 | data_added_files_packed: 0, 35 | data_added_trees: "[data_added_trees]", 36 | data_added_trees_packed: "[data_added_trees_packed]", 37 | command: "[command]", 38 | backup_start: "[backup_start]", 39 | backup_end: "[backup_end]", 40 | backup_duration: "[backup_duration]", 41 | total_duration: "[total_duration]", 42 | )), 43 | id: "[id]", 44 | ) 45 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-summary-third-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | parent: "[some]", 9 | tree: "[tree_id]", 10 | paths: StringList([ 11 | "test", 12 | ]), 13 | hostname: "[hostname]", 14 | username: "", 15 | uid: 0, 16 | gid: 0, 17 | tags: StringList([ 18 | "a", 19 | "b", 20 | ]), 21 | summary: Some(SnapshotSummary( 22 | files_new: 0, 23 | files_changed: 0, 24 | files_unmodified: 73, 25 | total_files_processed: 73, 26 | total_bytes_processed: 1125682, 27 | dirs_new: 0, 28 | dirs_changed: 0, 29 | dirs_unmodified: 6, 30 | total_dirs_processed: 6, 31 | total_dirsize_processed: "[total_dirsize_processed]", 32 | data_blobs: 0, 33 | tree_blobs: 0, 34 | data_added: "[data_added]", 35 | data_added_packed: "[data_added_packed]", 36 | data_added_files: 0, 37 | data_added_files_packed: 0, 38 | data_added_trees: "[data_added_trees]", 39 | data_added_trees_packed: "[data_added_trees_packed]", 40 | command: "[command]", 41 | backup_start: "[backup_start]", 42 | backup_end: "[backup_end]", 43 | backup_duration: "[backup_duration]", 44 | total_duration: "[total_duration]", 45 | )), 46 | id: "[id]", 47 | ) 48 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-summary-third-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | parent: "[some]", 9 | tree: "[tree_id]", 10 | paths: StringList([ 11 | "test", 12 | ]), 13 | hostname: "[hostname]", 14 | username: "", 15 | uid: 0, 16 | gid: 0, 17 | tags: StringList([ 18 | "a", 19 | "b", 20 | ]), 21 | summary: Some(SnapshotSummary( 22 | files_new: 0, 23 | files_changed: 0, 24 | files_unmodified: 73, 25 | total_files_processed: 73, 26 | total_bytes_processed: 1125674, 27 | dirs_new: 0, 28 | dirs_changed: 0, 29 | dirs_unmodified: 6, 30 | total_dirs_processed: 6, 31 | total_dirsize_processed: "[total_dirsize_processed]", 32 | data_blobs: 0, 33 | tree_blobs: 0, 34 | data_added: "[data_added]", 35 | data_added_packed: "[data_added_packed]", 36 | data_added_files: 0, 37 | data_added_files_packed: 0, 38 | data_added_trees: "[data_added_trees]", 39 | data_added_trees_packed: "[data_added_trees_packed]", 40 | command: "[command]", 41 | backup_start: "[backup_start]", 42 | backup_end: "[backup_end]", 43 | backup_duration: "[backup_duration]", 44 | total_duration: "[total_duration]", 45 | )), 46 | id: "[id]", 47 | ) 48 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-tree-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: tree 4 | --- 5 | Tree( 6 | nodes: [ 7 | { 8 | "name": "empty-file", 9 | "type": "file", 10 | "mode": "[some]", 11 | "mtime": "[some]", 12 | "atime": "[some]", 13 | "ctime": "[some]", 14 | "uid": "[uid]", 15 | "gid": "[gid]", 16 | "user": "[user]", 17 | "group": "[group]", 18 | "inode": "[inode]", 19 | "device_id": "[device_id]", 20 | "links": 1, 21 | "content": Some([]), 22 | }, 23 | { 24 | "name": "testfile", 25 | "type": "file", 26 | "mode": "[some]", 27 | "mtime": "[some]", 28 | "atime": "[some]", 29 | "ctime": "[some]", 30 | "uid": "[uid]", 31 | "gid": "[gid]", 32 | "user": "[user]", 33 | "group": "[group]", 34 | "inode": "[inode]", 35 | "device_id": "[device_id]", 36 | "size": 21, 37 | "links": 2, 38 | "content": Some([ 39 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 40 | ]), 41 | }, 42 | { 43 | "name": "testfile-hardlink", 44 | "type": "file", 45 | "mode": "[some]", 46 | "mtime": "[some]", 47 | "atime": "[some]", 48 | "ctime": "[some]", 49 | "uid": "[uid]", 50 | "gid": "[gid]", 51 | "user": "[user]", 52 | "group": "[group]", 53 | "inode": "[inode]", 54 | "device_id": "[device_id]", 55 | "size": 21, 56 | "links": 2, 57 | "content": Some([ 58 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 59 | ]), 60 | }, 61 | { 62 | "name": "testfile-symlink", 63 | "type": "symlink", 64 | "linktarget": "testfile", 65 | "mode": "[some]", 66 | "mtime": "[some]", 67 | "atime": "[some]", 68 | "ctime": "[some]", 69 | "uid": "[uid]", 70 | "gid": "[gid]", 71 | "user": "[user]", 72 | "group": "[group]", 73 | "inode": "[inode]", 74 | "device_id": "[device_id]", 75 | "size": 8, 76 | "links": 1, 77 | "content": None, 78 | }, 79 | ], 80 | ) 81 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__backup-tar-tree-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: tree 4 | --- 5 | Tree( 6 | nodes: [ 7 | { 8 | "name": "empty-file", 9 | "type": "file", 10 | "mtime": "[some]", 11 | "atime": "[some]", 12 | "ctime": "[some]", 13 | "content": Some([]), 14 | }, 15 | { 16 | "name": "testfile", 17 | "type": "file", 18 | "mtime": "[some]", 19 | "atime": "[some]", 20 | "ctime": "[some]", 21 | "size": 21, 22 | "content": Some([ 23 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 24 | ]), 25 | }, 26 | { 27 | "name": "testfile-hardlink", 28 | "type": "file", 29 | "mtime": "[some]", 30 | "atime": "[some]", 31 | "ctime": "[some]", 32 | "size": 21, 33 | "content": Some([ 34 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 35 | ]), 36 | }, 37 | { 38 | "name": "testfile-symlink", 39 | "type": "symlink", 40 | "linktarget": "testfile", 41 | "mtime": "[some]", 42 | "atime": "[some]", 43 | "ctime": "[some]", 44 | "content": None, 45 | }, 46 | ], 47 | ) 48 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__dryrun-tar-summary-first-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | tree: "[tree_id]", 9 | paths: StringList([ 10 | "test", 11 | ]), 12 | hostname: "[hostname]", 13 | username: "", 14 | uid: 0, 15 | gid: 0, 16 | tags: StringList([]), 17 | summary: Some(SnapshotSummary( 18 | files_new: 73, 19 | files_changed: 0, 20 | files_unmodified: 0, 21 | total_files_processed: 73, 22 | total_bytes_processed: 1125674, 23 | dirs_new: 6, 24 | dirs_changed: 0, 25 | dirs_unmodified: 0, 26 | total_dirs_processed: 6, 27 | total_dirsize_processed: "[total_dirsize_processed]", 28 | data_blobs: 70, 29 | tree_blobs: 6, 30 | data_added: "[data_added]", 31 | data_added_packed: "[data_added_packed]", 32 | data_added_files: 1125653, 33 | data_added_files_packed: 78740, 34 | data_added_trees: "[data_added_trees]", 35 | data_added_trees_packed: "[data_added_trees_packed]", 36 | command: "[command]", 37 | backup_start: "[backup_start]", 38 | backup_end: "[backup_end]", 39 | backup_duration: "[backup_duration]", 40 | total_duration: "[total_duration]", 41 | )), 42 | ) 43 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__dryrun-tar-summary-first-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | tree: "[tree_id]", 9 | paths: StringList([ 10 | "test", 11 | ]), 12 | hostname: "[hostname]", 13 | username: "", 14 | uid: 0, 15 | gid: 0, 16 | tags: StringList([]), 17 | summary: Some(SnapshotSummary( 18 | files_new: 73, 19 | files_changed: 0, 20 | files_unmodified: 0, 21 | total_files_processed: 73, 22 | total_bytes_processed: 1125674, 23 | dirs_new: 6, 24 | dirs_changed: 0, 25 | dirs_unmodified: 0, 26 | total_dirs_processed: 6, 27 | total_dirsize_processed: "[total_dirsize_processed]", 28 | data_blobs: 70, 29 | tree_blobs: 6, 30 | data_added: "[data_added]", 31 | data_added_packed: "[data_added_packed]", 32 | data_added_files: 1125653, 33 | data_added_files_packed: 78740, 34 | data_added_trees: "[data_added_trees]", 35 | data_added_trees_packed: "[data_added_trees_packed]", 36 | command: "[command]", 37 | backup_start: "[backup_start]", 38 | backup_end: "[backup_end]", 39 | backup_duration: "[backup_duration]", 40 | total_duration: "[total_duration]", 41 | )), 42 | ) 43 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__dryrun-tar-summary-second-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | parent: "[some]", 9 | tree: "[tree_id]", 10 | paths: StringList([ 11 | "test", 12 | ]), 13 | hostname: "[hostname]", 14 | username: "", 15 | uid: 0, 16 | gid: 0, 17 | tags: StringList([]), 18 | summary: Some(SnapshotSummary( 19 | files_new: 0, 20 | files_changed: 0, 21 | files_unmodified: 73, 22 | total_files_processed: 73, 23 | total_bytes_processed: 1125682, 24 | dirs_new: 0, 25 | dirs_changed: 0, 26 | dirs_unmodified: 6, 27 | total_dirs_processed: 6, 28 | total_dirsize_processed: "[total_dirsize_processed]", 29 | data_blobs: 0, 30 | tree_blobs: 0, 31 | data_added: "[data_added]", 32 | data_added_packed: "[data_added_packed]", 33 | data_added_files: 0, 34 | data_added_files_packed: 0, 35 | data_added_trees: "[data_added_trees]", 36 | data_added_trees_packed: "[data_added_trees_packed]", 37 | command: "[command]", 38 | backup_start: "[backup_start]", 39 | backup_end: "[backup_end]", 40 | backup_duration: "[backup_duration]", 41 | total_duration: "[total_duration]", 42 | )), 43 | ) 44 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__dryrun-tar-summary-second-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | parent: "[some]", 9 | tree: "[tree_id]", 10 | paths: StringList([ 11 | "test", 12 | ]), 13 | hostname: "[hostname]", 14 | username: "", 15 | uid: 0, 16 | gid: 0, 17 | tags: StringList([]), 18 | summary: Some(SnapshotSummary( 19 | files_new: 0, 20 | files_changed: 0, 21 | files_unmodified: 73, 22 | total_files_processed: 73, 23 | total_bytes_processed: 1125674, 24 | dirs_new: 0, 25 | dirs_changed: 0, 26 | dirs_unmodified: 6, 27 | total_dirs_processed: 6, 28 | total_dirsize_processed: "[total_dirsize_processed]", 29 | data_blobs: 0, 30 | tree_blobs: 0, 31 | data_added: "[data_added]", 32 | data_added_packed: "[data_added_packed]", 33 | data_added_files: 0, 34 | data_added_files_packed: 0, 35 | data_added_trees: "[data_added_trees]", 36 | data_added_trees_packed: "[data_added_trees_packed]", 37 | command: "[command]", 38 | backup_start: "[backup_start]", 39 | backup_end: "[backup_end]", 40 | backup_duration: "[backup_duration]", 41 | total_duration: "[total_duration]", 42 | )), 43 | ) 44 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__dryrun-tar-tree-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: tree 4 | --- 5 | Tree( 6 | nodes: [ 7 | { 8 | "name": "empty-file", 9 | "type": "file", 10 | "mode": "[some]", 11 | "mtime": "[some]", 12 | "atime": "[some]", 13 | "ctime": "[some]", 14 | "uid": "[uid]", 15 | "gid": "[gid]", 16 | "user": "[user]", 17 | "group": "[group]", 18 | "inode": "[inode]", 19 | "device_id": "[device_id]", 20 | "links": 1, 21 | "content": Some([]), 22 | }, 23 | { 24 | "name": "testfile", 25 | "type": "file", 26 | "mode": "[some]", 27 | "mtime": "[some]", 28 | "atime": "[some]", 29 | "ctime": "[some]", 30 | "uid": "[uid]", 31 | "gid": "[gid]", 32 | "user": "[user]", 33 | "group": "[group]", 34 | "inode": "[inode]", 35 | "device_id": "[device_id]", 36 | "size": 21, 37 | "links": 2, 38 | "content": Some([ 39 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 40 | ]), 41 | }, 42 | { 43 | "name": "testfile-hardlink", 44 | "type": "file", 45 | "mode": "[some]", 46 | "mtime": "[some]", 47 | "atime": "[some]", 48 | "ctime": "[some]", 49 | "uid": "[uid]", 50 | "gid": "[gid]", 51 | "user": "[user]", 52 | "group": "[group]", 53 | "inode": "[inode]", 54 | "device_id": "[device_id]", 55 | "size": 21, 56 | "links": 2, 57 | "content": Some([ 58 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 59 | ]), 60 | }, 61 | { 62 | "name": "testfile-symlink", 63 | "type": "symlink", 64 | "linktarget": "testfile", 65 | "mode": "[some]", 66 | "mtime": "[some]", 67 | "atime": "[some]", 68 | "ctime": "[some]", 69 | "uid": "[uid]", 70 | "gid": "[gid]", 71 | "user": "[user]", 72 | "group": "[group]", 73 | "inode": "[inode]", 74 | "device_id": "[device_id]", 75 | "size": 8, 76 | "links": 1, 77 | "content": None, 78 | }, 79 | ], 80 | ) 81 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__dryrun-tar-tree-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: tree 4 | --- 5 | Tree( 6 | nodes: [ 7 | { 8 | "name": "empty-file", 9 | "type": "file", 10 | "mtime": "[some]", 11 | "atime": "[some]", 12 | "ctime": "[some]", 13 | "content": Some([]), 14 | }, 15 | { 16 | "name": "testfile", 17 | "type": "file", 18 | "mtime": "[some]", 19 | "atime": "[some]", 20 | "ctime": "[some]", 21 | "size": 21, 22 | "content": Some([ 23 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 24 | ]), 25 | }, 26 | { 27 | "name": "testfile-hardlink", 28 | "type": "file", 29 | "mtime": "[some]", 30 | "atime": "[some]", 31 | "ctime": "[some]", 32 | "size": 21, 33 | "content": Some([ 34 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 35 | ]), 36 | }, 37 | { 38 | "name": "testfile-symlink", 39 | "type": "symlink", 40 | "linktarget": "testfile", 41 | "mtime": "[some]", 42 | "atime": "[some]", 43 | "ctime": "[some]", 44 | "content": None, 45 | }, 46 | ], 47 | ) 48 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-matching-existing-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | ([ 6 | "test/0/tests/testfile", 7 | ], [ 8 | [ 9 | (0, 0), 10 | ], 11 | ]) 12 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-matching-existing-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | ([ 6 | "test\\0\\tests\\testfile", 7 | ], [ 8 | [ 9 | (0, 0), 10 | ], 11 | ]) 12 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-matching-nodes-not-found-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | FindMatches( 6 | paths: [], 7 | nodes: [], 8 | matches: [ 9 | [], 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-matching-nodes-not-found-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | FindMatches( 6 | paths: [], 7 | nodes: [], 8 | matches: [ 9 | [], 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-matching-wildcard-existing-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | ([ 6 | "test/0/tests/testfile", 7 | "test/0/tests/testfile-hardlink", 8 | "test/0/tests/testfile-symlink", 9 | ], [ 10 | [ 11 | (0, 0), 12 | (1, 1), 13 | (2, 2), 14 | ], 15 | ]) 16 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-matching-wildcard-existing-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | ([ 6 | "test\\0\\tests\\testfile", 7 | "test\\0\\tests\\testfile-hardlink", 8 | "test\\0\\tests\\testfile-symlink", 9 | ], [ 10 | [ 11 | (0, 0), 12 | (1, 1), 13 | (2, 2), 14 | ], 15 | ]) 16 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-nodes-existing-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | Some(0), 7 | ] 8 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-nodes-existing-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | Some(0), 7 | ] 8 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-nodes-not-found-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | FindNode( 6 | nodes: [], 7 | matches: [ 8 | None, 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__find-nodes-not-found-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | FindNode( 6 | nodes: [], 7 | matches: [ 8 | None, 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__stdin-command-summary-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | tree: "[tree_id]", 9 | paths: StringList([ 10 | "test", 11 | ]), 12 | hostname: "[hostname]", 13 | username: "", 14 | uid: 0, 15 | gid: 0, 16 | tags: StringList([]), 17 | summary: Some(SnapshotSummary( 18 | files_new: 1, 19 | files_changed: 0, 20 | files_unmodified: 0, 21 | total_files_processed: 1, 22 | total_bytes_processed: 5, 23 | dirs_new: 1, 24 | dirs_changed: 0, 25 | dirs_unmodified: 0, 26 | total_dirs_processed: 1, 27 | total_dirsize_processed: "[total_dirsize_processed]", 28 | data_blobs: 1, 29 | tree_blobs: 1, 30 | data_added: "[data_added]", 31 | data_added_packed: "[data_added_packed]", 32 | data_added_files: 5, 33 | data_added_files_packed: 46, 34 | data_added_trees: "[data_added_trees]", 35 | data_added_trees_packed: "[data_added_trees_packed]", 36 | command: "[command]", 37 | backup_start: "[backup_start]", 38 | backup_end: "[backup_end]", 39 | backup_duration: "[backup_duration]", 40 | total_duration: "[total_duration]", 41 | )), 42 | id: "[id]", 43 | ) 44 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__stdin-command-summary-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | SnapshotFile( 6 | time: "[time]", 7 | program_version: "rustic [rustic_core_version]", 8 | tree: "[tree_id]", 9 | paths: StringList([ 10 | "test", 11 | ]), 12 | hostname: "[hostname]", 13 | username: "", 14 | uid: 0, 15 | gid: 0, 16 | tags: StringList([]), 17 | summary: Some(SnapshotSummary( 18 | files_new: 1, 19 | files_changed: 0, 20 | files_unmodified: 0, 21 | total_files_processed: 1, 22 | total_bytes_processed: 5, 23 | dirs_new: 1, 24 | dirs_changed: 0, 25 | dirs_unmodified: 0, 26 | total_dirs_processed: 1, 27 | total_dirsize_processed: "[total_dirsize_processed]", 28 | data_blobs: 1, 29 | tree_blobs: 1, 30 | data_added: "[data_added]", 31 | data_added_packed: "[data_added_packed]", 32 | data_added_files: 5, 33 | data_added_files_packed: 46, 34 | data_added_trees: "[data_added_trees]", 35 | data_added_trees_packed: "[data_added_trees_packed]", 36 | command: "[command]", 37 | backup_start: "[backup_start]", 38 | backup_end: "[backup_end]", 39 | backup_duration: "[backup_duration]", 40 | total_duration: "[total_duration]", 41 | )), 42 | id: "[id]", 43 | ) 44 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__vfs-nix.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | { 7 | "name": "empty-file", 8 | "type": "file", 9 | "mode": "[some]", 10 | "mtime": "[some]", 11 | "atime": "[some]", 12 | "ctime": "[some]", 13 | "uid": "[uid]", 14 | "gid": "[gid]", 15 | "user": "[user]", 16 | "group": "[group]", 17 | "inode": "[inode]", 18 | "device_id": "[device_id]", 19 | "links": 1, 20 | "content": Some([]), 21 | }, 22 | { 23 | "name": "testfile", 24 | "type": "file", 25 | "mode": "[some]", 26 | "mtime": "[some]", 27 | "atime": "[some]", 28 | "ctime": "[some]", 29 | "uid": "[uid]", 30 | "gid": "[gid]", 31 | "user": "[user]", 32 | "group": "[group]", 33 | "inode": "[inode]", 34 | "device_id": "[device_id]", 35 | "size": 21, 36 | "links": 2, 37 | "content": Some([ 38 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 39 | ]), 40 | }, 41 | { 42 | "name": "testfile-hardlink", 43 | "type": "file", 44 | "mode": "[some]", 45 | "mtime": "[some]", 46 | "atime": "[some]", 47 | "ctime": "[some]", 48 | "uid": "[uid]", 49 | "gid": "[gid]", 50 | "user": "[user]", 51 | "group": "[group]", 52 | "inode": "[inode]", 53 | "device_id": "[device_id]", 54 | "size": 21, 55 | "links": 2, 56 | "content": Some([ 57 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 58 | ]), 59 | }, 60 | { 61 | "name": "testfile-symlink", 62 | "type": "symlink", 63 | "linktarget": "testfile", 64 | "mode": "[some]", 65 | "mtime": "[some]", 66 | "atime": "[some]", 67 | "ctime": "[some]", 68 | "uid": "[uid]", 69 | "gid": "[gid]", 70 | "user": "[user]", 71 | "group": "[group]", 72 | "inode": "[inode]", 73 | "device_id": "[device_id]", 74 | "size": 8, 75 | "links": 1, 76 | "content": None, 77 | }, 78 | ] 79 | -------------------------------------------------------------------------------- /crates/core/tests/snapshots/integration__vfs-windows.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: crates/core/tests/integration.rs 3 | expression: snap 4 | --- 5 | [ 6 | { 7 | "name": "empty-file", 8 | "type": "file", 9 | "mtime": "[some]", 10 | "atime": "[some]", 11 | "ctime": "[some]", 12 | "content": Some([]), 13 | }, 14 | { 15 | "name": "testfile", 16 | "type": "file", 17 | "mtime": "[some]", 18 | "atime": "[some]", 19 | "ctime": "[some]", 20 | "size": 21, 21 | "content": Some([ 22 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 23 | ]), 24 | }, 25 | { 26 | "name": "testfile-hardlink", 27 | "type": "file", 28 | "mtime": "[some]", 29 | "atime": "[some]", 30 | "ctime": "[some]", 31 | "size": 21, 32 | "content": Some([ 33 | Id("649b8b471e7d7bc175eec758a7006ac693c434c8297c07db15286788c837154a"), 34 | ]), 35 | }, 36 | { 37 | "name": "testfile-symlink", 38 | "type": "symlink", 39 | "linktarget": "testfile", 40 | "mtime": "[some]", 41 | "atime": "[some]", 42 | "ctime": "[some]", 43 | "content": None, 44 | }, 45 | ] 46 | -------------------------------------------------------------------------------- /crates/testing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.3.2](https://github.com/rustic-rs/rustic_core/compare/rustic_testing-v0.3.1...rustic_testing-v0.3.2) - 2024-11-27 10 | 11 | ### Other 12 | 13 | - update Cargo.toml dependencies 14 | 15 | ## [0.3.1](https://github.com/rustic-rs/rustic_core/compare/rustic_testing-v0.3.0...rustic_testing-v0.3.1) - 2024-11-24 16 | 17 | ### Other 18 | 19 | - Revert "feat(async): add `async_compatible` methods to identify backend compatibility ([#355](https://github.com/rustic-rs/rustic_core/pull/355))" 20 | 21 | ## [0.3.0](https://github.com/rustic-rs/rustic_core/compare/rustic_testing-v0.2.3...rustic_testing-v0.3.0) - 2024-11-18 22 | 23 | ### Added 24 | 25 | - *(async)* add `async_compatible` methods to identify backend compatibility ([#355](https://github.com/rustic-rs/rustic_core/pull/355)) 26 | 27 | ### Other 28 | 29 | - *(errors)* [**breaking**] Improve error handling, display and clean up codebase ([#321](https://github.com/rustic-rs/rustic_core/pull/321)) 30 | 31 | ## [0.2.3](https://github.com/rustic-rs/rustic_core/compare/rustic_testing-v0.2.2...rustic_testing-v0.2.3) - 2024-10-25 32 | 33 | ### Other 34 | 35 | - *(deps)* remove unused dependency once_cell ([#341](https://github.com/rustic-rs/rustic_core/pull/341)) 36 | 37 | ## [0.2.2](https://github.com/rustic-rs/rustic_core/compare/rustic_testing-v0.2.1...rustic_testing-v0.2.2) - 2024-10-02 38 | 39 | ### Other 40 | 41 | - *(deps)* update dependencies ([#292](https://github.com/rustic-rs/rustic_core/pull/292)) 42 | 43 | ## [0.2.1](https://github.com/rustic-rs/rustic_core/compare/rustic_testing-v0.2.0...rustic_testing-v0.2.1) - 2024-09-06 44 | 45 | ### Fixed 46 | - dprint 47 | 48 | ### Other 49 | - use rustic_core from workspace 50 | -------------------------------------------------------------------------------- /crates/testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustic_testing" 3 | version = "0.3.2" 4 | edition = "2024" 5 | license = "Apache-2.0 OR MIT" 6 | publish = true 7 | resolver = "3" 8 | rust-version = { workspace = true } 9 | description = """ 10 | rustic_testuing - library for test support in rustic-rs 11 | """ 12 | 13 | [dependencies] 14 | aho-corasick = { workspace = true } 15 | anyhow = { workspace = true } 16 | bytes = { workspace = true } 17 | enum-map = { workspace = true } 18 | rustic_core = { workspace = true } 19 | tempfile = { workspace = true } 20 | 21 | [lints] 22 | workspace = true 23 | -------------------------------------------------------------------------------- /crates/testing/src/backend.rs: -------------------------------------------------------------------------------- 1 | /// In-memory backend to be used for testing 2 | pub mod in_memory_backend { 3 | use std::{collections::BTreeMap, sync::RwLock}; 4 | 5 | use bytes::Bytes; 6 | use enum_map::EnumMap; 7 | 8 | use rustic_core::{ 9 | ErrorKind, FileType, Id, ReadBackend, RusticError, RusticResult, WriteBackend, 10 | }; 11 | 12 | #[derive(Debug)] 13 | /// In-Memory backend to be used for testing 14 | pub struct InMemoryBackend(RwLock>>); 15 | 16 | impl InMemoryBackend { 17 | /// Create a new (empty) `InMemoryBackend` 18 | #[must_use] 19 | pub fn new() -> Self { 20 | Self(RwLock::new(EnumMap::from_fn(|_| BTreeMap::new()))) 21 | } 22 | } 23 | 24 | impl Default for InMemoryBackend { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | 30 | impl ReadBackend for InMemoryBackend { 31 | fn location(&self) -> String { 32 | "test".to_string() 33 | } 34 | 35 | fn list_with_size(&self, tpe: FileType) -> RusticResult> { 36 | Ok(self.0.read().unwrap()[tpe] 37 | .iter() 38 | .map(|(id, byte)| { 39 | ( 40 | *id, 41 | u32::try_from(byte.len()).expect("byte length is too large"), 42 | ) 43 | }) 44 | .collect()) 45 | } 46 | 47 | fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult { 48 | Ok(self.0.read().unwrap()[tpe][id].clone()) 49 | } 50 | 51 | fn read_partial( 52 | &self, 53 | tpe: FileType, 54 | id: &Id, 55 | _cacheable: bool, 56 | offset: u32, 57 | length: u32, 58 | ) -> RusticResult { 59 | Ok(self.0.read().unwrap()[tpe][id].slice(offset as usize..(offset + length) as usize)) 60 | } 61 | } 62 | 63 | impl WriteBackend for InMemoryBackend { 64 | fn create(&self) -> RusticResult<()> { 65 | Ok(()) 66 | } 67 | 68 | fn write_bytes( 69 | &self, 70 | tpe: FileType, 71 | id: &Id, 72 | _cacheable: bool, 73 | buf: Bytes, 74 | ) -> RusticResult<()> { 75 | if self.0.write().unwrap()[tpe].insert(*id, buf).is_some() { 76 | return Err( 77 | RusticError::new(ErrorKind::Backend, "ID `{id}` already exists.") 78 | .attach_context("id", id.to_string()), 79 | ); 80 | } 81 | 82 | Ok(()) 83 | } 84 | 85 | fn remove(&self, tpe: FileType, id: &Id, _cacheable: bool) -> RusticResult<()> { 86 | if self.0.write().unwrap()[tpe].remove(id).is_none() { 87 | return Err( 88 | RusticError::new(ErrorKind::Backend, "ID `{id}` does not exist.") 89 | .attach_context("id", id.to_string()), 90 | ); 91 | } 92 | Ok(()) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/testing/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Testing utilities for the `rustic` ecosystem. 2 | 3 | // formatting args are used for error messages 4 | #![allow(clippy::literal_string_with_formatting_args)] 5 | 6 | /// Backends to be used solely for testing. 7 | pub mod backend; 8 | 9 | use aho_corasick::{AhoCorasick, PatternID}; 10 | use std::{error::Error, ffi::OsStr}; 11 | use tempfile::NamedTempFile; 12 | 13 | /// A test result. 14 | pub type TestResult = Result>; 15 | 16 | /// Get the matches for the given patterns and output. 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `patterns` - The patterns to search for. 21 | /// * `output` - The output to search in. 22 | /// 23 | /// # Errors 24 | /// 25 | /// If the patterns are invalid. 26 | /// 27 | /// # Returns 28 | /// 29 | /// The matches for the given patterns and output. 30 | pub fn get_matches(patterns: I, output: &str) -> TestResult> 31 | where 32 | I: IntoIterator, 33 | P: AsRef<[u8]>, 34 | { 35 | let ac = AhoCorasick::new(patterns)?; 36 | let mut matches = vec![]; 37 | for mat in ac.find_iter(output) { 38 | add_match_to_vector(&mut matches, mat); 39 | } 40 | Ok(matches) 41 | } 42 | 43 | /// Add a match to the given vector. 44 | /// 45 | /// # Arguments 46 | /// 47 | /// * `matches` - The vector to add the match to. 48 | /// * `mat` - The `aho_corasick::Match` to add. 49 | pub fn add_match_to_vector(matches: &mut Vec<(PatternID, usize)>, mat: aho_corasick::Match) { 50 | matches.push((mat.pattern(), mat.end() - mat.start())); 51 | } 52 | 53 | /// Get a temporary file. 54 | /// 55 | /// # Errors 56 | /// 57 | /// If the temporary file could not be created. 58 | /// 59 | /// # Returns 60 | /// 61 | /// A temporary file. 62 | pub fn get_temp_file() -> TestResult { 63 | Ok(NamedTempFile::new()?) 64 | } 65 | 66 | /// Check if the given files differ. 67 | /// 68 | /// # Arguments 69 | /// 70 | /// * `path_left` - The left file to compare. 71 | /// * `path_right` - The right file to compare. 72 | /// 73 | /// # Errors 74 | /// 75 | /// If the files could not be compared. 76 | /// 77 | /// # Returns 78 | /// 79 | /// `true` if the files differ, `false` otherwise. 80 | pub fn files_differ( 81 | path_left: impl AsRef, 82 | path_right: impl AsRef, 83 | ) -> TestResult { 84 | // diff the directories 85 | #[cfg(not(windows))] 86 | { 87 | let proc = std::process::Command::new("diff") 88 | .arg(path_left) 89 | .arg(path_right) 90 | .output()?; 91 | 92 | if proc.stdout.is_empty() { 93 | return Ok(false); 94 | } 95 | } 96 | 97 | #[cfg(windows)] 98 | { 99 | let proc = std::process::Command::new("fc.exe") 100 | .arg("/L") 101 | .arg(path_left) 102 | .arg(path_right) 103 | .output()?; 104 | 105 | let output = String::from_utf8(proc.stdout)?; 106 | 107 | dbg!(&output); 108 | 109 | let patterns = &["FC: no differences encountered"]; 110 | let ac = AhoCorasick::new(patterns)?; 111 | let mut matches = vec![]; 112 | 113 | for mat in ac.find_iter(output.as_str()) { 114 | matches.push((mat.pattern(), mat.end() - mat.start())); 115 | } 116 | 117 | if matches == vec![(PatternID::must(0), 30)] { 118 | return Ok(false); 119 | } 120 | } 121 | 122 | Ok(true) 123 | } 124 | -------------------------------------------------------------------------------- /docs/Readme.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Our documentation can be found at: 4 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "lineWidth": 80, 3 | "markdown": { 4 | "lineWidth": 80, 5 | "emphasisKind": "asterisks", 6 | "strongKind": "asterisks", 7 | "textWrap": "always" 8 | }, 9 | "toml": { 10 | "lineWidth": 80 11 | }, 12 | "json": { 13 | "lineWidth": 80, 14 | "indentWidth": 4 15 | }, 16 | "includes": [ 17 | "**/*.{md}", 18 | "**/*.{toml}", 19 | "**/*.{json}" 20 | ], 21 | "excludes": [ 22 | "target/**/*", 23 | "CHANGELOG.md" 24 | ], 25 | "plugins": [ 26 | "https://plugins.dprint.dev/markdown-0.17.8.wasm", 27 | "https://plugins.dprint.dev/toml-0.6.3.wasm", 28 | "https://plugins.dprint.dev/json-0.19.3.wasm" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/backup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "backup" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "backup" 17 | -------------------------------------------------------------------------------- /examples/backup/examples/backup.rs: -------------------------------------------------------------------------------- 1 | //! `backup` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{BackupOptions, PathList, Repository, RepositoryOptions, SnapshotOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .repo_hot("/tmp/repo2") 15 | .to_backends()?; 16 | 17 | // Open repository 18 | let repo_opts = RepositoryOptions::default().password("test"); 19 | 20 | let repo = Repository::new(&repo_opts, &backends)? 21 | .open()? 22 | .to_indexed_ids()?; 23 | 24 | let backup_opts = BackupOptions::default(); 25 | let source = PathList::from_string(".")?.sanitize()?; 26 | let snap = SnapshotOptions::default() 27 | .add_tags("tag1,tag2")? 28 | .to_snapshot()?; 29 | 30 | // Create snapshot 31 | let snap = repo.backup(&backup_opts, &source, snap)?; 32 | 33 | println!("successfully created snapshot:\n{snap:#?}"); 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "check" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "check" 17 | -------------------------------------------------------------------------------- /examples/check/examples/check.rs: -------------------------------------------------------------------------------- 1 | //! `check` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{CheckOptions, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | let repo = Repository::new(&repo_opts, &backends)?.open()?; 19 | 20 | // Check repository with standard options but omitting cache checks 21 | let opts = CheckOptions::default().trust_cache(true); 22 | repo.check(opts)?; 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "config" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "config" 17 | -------------------------------------------------------------------------------- /examples/config/examples/config.rs: -------------------------------------------------------------------------------- 1 | //! `config` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{max_compression_level, ConfigOptions, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | let repo = Repository::new(&repo_opts, &backends)?.open()?; 19 | 20 | // Set Config, e.g. Compression level 21 | let config_opts = ConfigOptions::default().set_compression(max_compression_level()); 22 | repo.apply_config(&config_opts)?; 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/copy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "copy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "copy" 17 | -------------------------------------------------------------------------------- /examples/copy/examples/copy.rs: -------------------------------------------------------------------------------- 1 | //! `copy` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{CopySnapshot, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize src backends 12 | let src_backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let src_repo_opts = RepositoryOptions::default().password("test"); 18 | 19 | let src_repo = Repository::new(&src_repo_opts, &src_backends)? 20 | .open()? 21 | .to_indexed()?; 22 | 23 | // Initialize dst backends 24 | let dst_backends = BackendOptions::default() 25 | .repository("/tmp/repo") 26 | .to_backends()?; 27 | 28 | let dst_repo_opts = RepositoryOptions::default().password("test"); 29 | 30 | let dst_repo = Repository::new(&dst_repo_opts, &dst_backends)? 31 | .open()? 32 | .to_indexed_ids()?; 33 | 34 | // get snapshots which are missing in dst_repo 35 | let snapshots = src_repo.get_all_snapshots()?; 36 | let snaps = dst_repo.relevant_copy_snapshots(|_| true, &snapshots)?; 37 | 38 | // copy only relevant snapshots 39 | src_repo.copy( 40 | &dst_repo, 41 | snaps 42 | .iter() 43 | .filter_map(|CopySnapshot { relevant, sn }| relevant.then_some(sn)), 44 | )?; 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /examples/find/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "find" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | globset = "0.4.16" 12 | rustic_backend = { workspace = true } 13 | rustic_core = { workspace = true } 14 | simplelog = { workspace = true } 15 | 16 | [[example]] 17 | name = "find" 18 | -------------------------------------------------------------------------------- /examples/find/examples/find.rs: -------------------------------------------------------------------------------- 1 | //! `ls` example 2 | use globset::Glob; 3 | use rustic_backend::BackendOptions; 4 | use rustic_core::{FindMatches, Repository, RepositoryOptions}; 5 | use simplelog::{Config, LevelFilter, SimpleLogger}; 6 | use std::error::Error; 7 | 8 | fn main() -> Result<(), Box> { 9 | // Display info logs 10 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 11 | 12 | // Initialize Backends 13 | let backends = BackendOptions::default() 14 | .repository("/tmp/repo") 15 | .to_backends()?; 16 | 17 | // Open repository 18 | let repo_opts = RepositoryOptions::default().password("test"); 19 | 20 | let repo = Repository::new(&repo_opts, &backends)? 21 | .open()? 22 | .to_indexed()?; 23 | 24 | let mut snapshots = repo.get_all_snapshots()?; 25 | snapshots.sort_unstable(); 26 | let tree_ids = snapshots.iter().map(|sn| sn.tree); 27 | 28 | let glob = Glob::new("*.rs")?.compile_matcher(); 29 | let FindMatches { 30 | paths, 31 | nodes, 32 | matches, 33 | } = repo.find_matching_nodes(tree_ids, &|path, _| glob.is_match(path))?; 34 | for (snap, matches) in snapshots.iter().zip(matches) { 35 | println!("results in {snap:?}"); 36 | for (path_idx, node_idx) in matches { 37 | println!("path: {:?}, node: {:?}", paths[path_idx], nodes[node_idx]); 38 | } 39 | } 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/forget/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "forget" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "forget" 17 | -------------------------------------------------------------------------------- /examples/forget/examples/forget.rs: -------------------------------------------------------------------------------- 1 | //! `forget` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{KeepOptions, Repository, RepositoryOptions, SnapshotGroupCriterion}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | 19 | let repo = Repository::new(&repo_opts, &backends)?.open()?; 20 | 21 | // Check repository with standard options 22 | let group_by = SnapshotGroupCriterion::default(); 23 | let keep = KeepOptions::default().keep_daily(5).keep_weekly(10); 24 | let snaps = repo.get_forget_snapshots(&keep, group_by, |_| true)?; 25 | println!("{snaps:?}"); 26 | // to remove the snapshots-to-forget, uncomment this line: 27 | // repo.delete_snapshots(&snaps.into_forget_ids())? 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /examples/init/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "init" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "init" 17 | -------------------------------------------------------------------------------- /examples/init/examples/init.rs: -------------------------------------------------------------------------------- 1 | //! `init` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{ConfigOptions, KeyOptions, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Init repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | let key_opts = KeyOptions::default(); 19 | let config_opts = ConfigOptions::default(); 20 | let _repo = Repository::new(&repo_opts, &backends)?.init(&key_opts, &config_opts)?; 21 | 22 | // -> use _repo for any operation on an open repository 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/key/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "key" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "key" 17 | -------------------------------------------------------------------------------- /examples/key/examples/key.rs: -------------------------------------------------------------------------------- 1 | //! `key` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{KeyOptions, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | 19 | let repo = Repository::new(&repo_opts, &backends)?.open()?; 20 | 21 | // Add a new key with the given password 22 | let key_opts = KeyOptions::default(); 23 | repo.add_key("new_password", &key_opts)?; 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /examples/ls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ls" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "ls" 17 | -------------------------------------------------------------------------------- /examples/ls/examples/ls.rs: -------------------------------------------------------------------------------- 1 | //! `ls` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{LsOptions, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | 19 | let repo = Repository::new(&repo_opts, &backends)? 20 | .open()? 21 | .to_indexed()?; 22 | 23 | // use latest snapshot without filtering snapshots 24 | let node = repo.node_from_snapshot_path("latest", |_| true)?; 25 | 26 | // recursively list the snapshot contents using no additional filtering 27 | let ls_opts = LsOptions::default(); 28 | for item in repo.ls(&node, &ls_opts)? { 29 | let (path, _) = item?; 30 | println!("{path:?} "); 31 | } 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/merge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merge" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "merge" 17 | -------------------------------------------------------------------------------- /examples/merge/examples/merge.rs: -------------------------------------------------------------------------------- 1 | //! `merge` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{last_modified_node, repofile::SnapshotFile, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | 19 | let repo = Repository::new(&repo_opts, &backends)? 20 | .open()? 21 | .to_indexed_ids()?; 22 | 23 | // Merge all snapshots using the latest entry for duplicate entries 24 | let snaps = repo.get_all_snapshots()?; 25 | // This creates a new snapshot without removing the used ones 26 | let snap = repo.merge_snapshots(&snaps, &last_modified_node, SnapshotFile::default())?; 27 | 28 | println!("successfully created snapshot:\n{snap:#?}"); 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /examples/prune/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prune" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "prune" 17 | -------------------------------------------------------------------------------- /examples/prune/examples/prune.rs: -------------------------------------------------------------------------------- 1 | //! `prune` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{PruneOptions, Repository, RepositoryOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | 19 | let repo = Repository::new(&repo_opts, &backends)?.open()?; 20 | 21 | let prune_opts = PruneOptions::default(); 22 | let prune_plan = repo.prune_plan(&prune_opts)?; 23 | println!("{:?}", prune_plan.stats); 24 | println!("to repack: {:?}", prune_plan.repack_packs()); 25 | // to run the plan uncomment this line: 26 | // prune_plan.do_prune(&repo, &prune_opts)?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/restore/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "restore" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "restore" 17 | -------------------------------------------------------------------------------- /examples/restore/examples/restore.rs: -------------------------------------------------------------------------------- 1 | //! `restore` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{LocalDestination, LsOptions, Repository, RepositoryOptions, RestoreOptions}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | 7 | fn main() -> Result<(), Box> { 8 | // Display info logs 9 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 10 | 11 | // Initialize Backends 12 | let backends = BackendOptions::default() 13 | .repository("/tmp/repo") 14 | .to_backends()?; 15 | 16 | // Open repository 17 | let repo_opts = RepositoryOptions::default().password("test"); 18 | let repo = Repository::new(&repo_opts, &backends)? 19 | .open()? 20 | .to_indexed()?; 21 | 22 | // use latest snapshot without filtering snapshots 23 | let node = repo.node_from_snapshot_path("latest", |_| true)?; 24 | 25 | // use list of the snapshot contents using no additional filtering 26 | let streamer_opts = LsOptions::default(); 27 | let ls = repo.ls(&node, &streamer_opts)?; 28 | 29 | let destination = "./restore/"; // restore to this destination dir 30 | let create = true; // create destination dir, if it doesn't exist 31 | let dest = LocalDestination::new(destination, create, !node.is_dir())?; 32 | 33 | let opts = RestoreOptions::default(); 34 | let dry_run = false; 35 | // create restore infos. Note: this also already creates needed dirs in the destination 36 | let restore_infos = repo.prepare_restore(&opts, ls.clone(), &dest, dry_run)?; 37 | 38 | repo.restore(restore_infos, &opts, ls, &dest)?; 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /examples/tag/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tag" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rustic_backend = { workspace = true } 12 | rustic_core = { workspace = true } 13 | simplelog = { workspace = true } 14 | 15 | [[example]] 16 | name = "tag" 17 | -------------------------------------------------------------------------------- /examples/tag/examples/tag.rs: -------------------------------------------------------------------------------- 1 | //! `tag` example 2 | use rustic_backend::BackendOptions; 3 | use rustic_core::{Repository, RepositoryOptions, StringList}; 4 | use simplelog::{Config, LevelFilter, SimpleLogger}; 5 | use std::error::Error; 6 | use std::str::FromStr; 7 | 8 | fn main() -> Result<(), Box> { 9 | // Display info logs 10 | let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); 11 | 12 | // Initialize Backends 13 | let backends = BackendOptions::default() 14 | .repository("/tmp/repo") 15 | .to_backends()?; 16 | 17 | // Open repository 18 | let repo_opts = RepositoryOptions::default().password("test"); 19 | let repo = Repository::new(&repo_opts, &backends)?.open()?; 20 | 21 | // Set tag "test" to all snapshots, filtering out unchanged (i.e. tag was already preset) snapshots 22 | let snaps = repo.get_all_snapshots()?; 23 | let tags = vec![StringList::from_str("test")?]; 24 | let snaps: Vec<_> = snaps 25 | .into_iter() 26 | .filter_map(|mut sn| sn.add_tags(tags.clone()).then_some(sn)) // can also use set_tags or remove_tags 27 | .collect(); 28 | let old_snap_ids: Vec<_> = snaps.iter().map(|sn| sn.id).collect(); 29 | 30 | // remove old snapshots and save changed ones 31 | repo.save_snapshots(snaps)?; 32 | repo.delete_snapshots(&old_snap_ids)?; 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | # configuration spec can be found here https://release-plz.ieni.dev/docs/config 2 | 3 | [workspace] 4 | pr_draft = true 5 | # dependencies_update = true # We don't want to update dependencies automatically, as currently our dependencies tree is broken somewhere 6 | # changelog_config = "cliff.toml" # Don't use this for now, as it will override the default changelog config 7 | 8 | [changelog] 9 | protect_breaking_commits = true 10 | --------------------------------------------------------------------------------