mirror of
https://github.com/restic/restic.git
synced 2025-03-16 00:00:05 +01:00
Merge remote-tracking branch 'origin/master' into add-smb-backend
This commit is contained in:
commit
02381b8eeb
328 changed files with 6674 additions and 3095 deletions
35
.github/ISSUE_TEMPLATE/Bug.md
vendored
35
.github/ISSUE_TEMPLATE/Bug.md
vendored
|
@ -32,23 +32,30 @@ Output of `restic version`
|
|||
--------------------------
|
||||
|
||||
|
||||
How did you run restic exactly?
|
||||
-------------------------------
|
||||
|
||||
What backend/service did you use to store the repository?
|
||||
---------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Problem description / Steps to reproduce
|
||||
----------------------------------------
|
||||
|
||||
<!--
|
||||
This section should include at least:
|
||||
|
||||
* A description of the problem you are having with restic.
|
||||
|
||||
* The complete command line and any environment variables you used to
|
||||
configure restic's backend access. Make sure to replace sensitive values!
|
||||
|
||||
* The output of the commands, what restic prints gives may give us much
|
||||
information to diagnose the problem!
|
||||
|
||||
* The more time you spend describing an easy way to reproduce the behavior (if
|
||||
this is possible), the easier it is for the project developers to fix it!
|
||||
-->
|
||||
|
||||
What backend/server/service did you use to store the repository?
|
||||
----------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Expected behavior
|
||||
-----------------
|
||||
|
@ -65,22 +72,12 @@ In this section, please try to concentrate on observations, so only describe
|
|||
what you observed directly.
|
||||
-->
|
||||
|
||||
Steps to reproduce the behavior
|
||||
-------------------------------
|
||||
|
||||
<!--
|
||||
The more time you spend describing an easy way to reproduce the behavior (if
|
||||
this is possible), the easier it is for the project developers to fix it!
|
||||
-->
|
||||
|
||||
Do you have any idea what may have caused this?
|
||||
-----------------------------------------------
|
||||
|
||||
|
||||
|
||||
Do you have an idea how to solve the issue?
|
||||
-------------------------------------------
|
||||
|
||||
<!--
|
||||
Did something noteworthy happen on your system, Internet connection, backend services, etc?
|
||||
-->
|
||||
|
||||
|
||||
Did restic help you today? Did it make you happy in any way?
|
||||
|
|
15
.github/workflows/docker.yml
vendored
15
.github/workflows/docker.yml
vendored
|
@ -22,10 +22,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
uses: docker/login-action@3d58c274f17dffee475a5520cbe67f0a882c4dbb
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
@ -42,10 +42,17 @@ jobs:
|
|||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226
|
||||
|
||||
- name: Ensure consistent binaries
|
||||
run: |
|
||||
echo "removing git directory for consistency with release binaries"
|
||||
rm -rf .git
|
||||
# remove VCS information from release builds, keep VCS for nightly builds on master
|
||||
if: github.ref != 'refs/heads/master'
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
|
|
43
.github/workflows/tests.yml
vendored
43
.github/workflows/tests.yml
vendored
|
@ -13,7 +13,7 @@ permissions:
|
|||
contents: read
|
||||
|
||||
env:
|
||||
latest_go: "1.20.x"
|
||||
latest_go: "1.21.x"
|
||||
GO111MODULE: on
|
||||
|
||||
jobs:
|
||||
|
@ -23,18 +23,18 @@ jobs:
|
|||
# list of jobs to run:
|
||||
include:
|
||||
- job_name: Windows
|
||||
go: 1.20.x
|
||||
go: 1.21.x
|
||||
os: windows-latest
|
||||
test_smb: true
|
||||
|
||||
- job_name: macOS
|
||||
go: 1.20.x
|
||||
go: 1.21.x
|
||||
os: macOS-latest
|
||||
test_fuse: false
|
||||
test_smb: true
|
||||
|
||||
- job_name: Linux
|
||||
go: 1.20.x
|
||||
go: 1.21.x
|
||||
os: ubuntu-latest
|
||||
test_cloud_backends: true
|
||||
test_fuse: true
|
||||
|
@ -42,20 +42,20 @@ jobs:
|
|||
check_changelog: true
|
||||
|
||||
- job_name: Linux (race)
|
||||
go: 1.20.x
|
||||
go: 1.21.x
|
||||
os: ubuntu-latest
|
||||
test_fuse: true
|
||||
test_smb: true
|
||||
test_opts: "-race"
|
||||
|
||||
- job_name: Linux
|
||||
go: 1.19.x
|
||||
go: 1.20.x
|
||||
os: ubuntu-latest
|
||||
test_fuse: true
|
||||
test_smb: true
|
||||
|
||||
- job_name: Linux
|
||||
go: 1.18.x
|
||||
go: 1.19.x
|
||||
os: ubuntu-latest
|
||||
test_fuse: true
|
||||
test_smb: true
|
||||
|
@ -252,7 +252,7 @@ jobs:
|
|||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build with build.go
|
||||
run: |
|
||||
|
@ -349,7 +349,7 @@ jobs:
|
|||
go-version: ${{ env.latest_go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cross-compile for subset ${{ matrix.subset }}
|
||||
run: |
|
||||
|
@ -367,13 +367,13 @@ jobs:
|
|||
go-version: ${{ env.latest_go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.52.2
|
||||
version: v1.55.2
|
||||
args: --verbose --timeout 5m
|
||||
|
||||
# only run golangci-lint for pull requests, otherwise ALL hints get
|
||||
|
@ -387,12 +387,27 @@ jobs:
|
|||
go mod tidy
|
||||
git diff --exit-code go.mod go.sum
|
||||
|
||||
analyze:
|
||||
name: Analyze results
|
||||
needs: [test, cross_compile, lint]
|
||||
if: always()
|
||||
|
||||
permissions: # no need to access code
|
||||
contents: none
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
docker:
|
||||
name: docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
|
@ -412,10 +427,10 @@ jobs:
|
|||
type=sha
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
|
|
22
.readthedocs.yaml
Normal file
22
.readthedocs.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
# Build HTMLZip
|
||||
formats:
|
||||
- htmlzip
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: doc/conf.py
|
||||
|
||||
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
python:
|
||||
install:
|
||||
- requirements: doc/requirements.txt
|
815
CHANGELOG.md
815
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -61,7 +61,7 @@ uploading it somewhere or post only the parts that are really relevant.
|
|||
If restic gets stuck, please also include a stacktrace in the description.
|
||||
On non-Windows systems, you can send a SIGQUIT signal to restic or press
|
||||
`Ctrl-\` to achieve the same result. This causes restic to print a stacktrace
|
||||
and then exit immediatelly. This will not damage your repository, however,
|
||||
and then exit immediately. This will not damage your repository, however,
|
||||
it might be necessary to manually clean up stale lock files using
|
||||
`restic unlock`.
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ release. Instructions on how to do that are contained in the
|
|||
News
|
||||
----
|
||||
|
||||
You can follow the restic project on Twitter [@resticbackup](https://twitter.com/resticbackup) or by subscribing to
|
||||
You can follow the restic project on Mastodon [@resticbackup](https://fosstodon.org/@restic) or by subscribing to
|
||||
the [project blog](https://restic.net/blog/).
|
||||
|
||||
License
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.15.2
|
||||
0.16.2
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Bugfix: Don't abort the stats command when data blobs are missing
|
||||
|
||||
Runing the stats command in the blobs-per-file mode on a repository with
|
||||
Running the stats command in the blobs-per-file mode on a repository with
|
||||
missing data blobs previously resulted in a crash.
|
||||
|
||||
https://github.com/restic/restic/pull/2668
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Bugfix: Mark repository files as read-only when using the local backend
|
||||
|
||||
Files stored in a local repository were marked as writeable on the
|
||||
Files stored in a local repository were marked as writable on the
|
||||
filesystem for non-Windows systems, which did not prevent accidental file
|
||||
modifications outside of restic. In addition, the local backend did not work
|
||||
with certain filesystems and network mounts which do not permit modifications
|
||||
|
|
|
@ -5,7 +5,7 @@ another process using an exclusive lock through a filesystem snapshot. Restic
|
|||
was unable to backup those files before. This update enables backing up these
|
||||
files.
|
||||
|
||||
This needs to be enabled explicitely using the --use-fs-snapshot option of the
|
||||
This needs to be enabled explicitly using the --use-fs-snapshot option of the
|
||||
backup command.
|
||||
|
||||
https://github.com/restic/restic/issues/340
|
||||
|
|
|
@ -2,7 +2,7 @@ Enhancement: Parallelize scan of snapshot content in `copy` and `prune`
|
|||
|
||||
The `copy` and `prune` commands used to traverse the directories of
|
||||
snapshots one by one to find used data. This snapshot traversal is
|
||||
now parallized which can speed up this step several times.
|
||||
now parallelized which can speed up this step several times.
|
||||
|
||||
In addition the `check` command now reports how many snapshots have
|
||||
already been processed.
|
||||
|
|
|
@ -3,7 +3,7 @@ Enhancement: Allow limiting IO concurrency for local and SFTP backend
|
|||
Restic did not support limiting the IO concurrency / number of connections for
|
||||
accessing repositories stored using the local or SFTP backends. The number of
|
||||
connections is now limited as for other backends, and can be configured via the
|
||||
the `-o local.connections=2` and `-o sftp.connections=5` options. This ensures
|
||||
that restic does not overwhelm the backend with concurrent IO operations.
|
||||
`-o local.connections=2` and `-o sftp.connections=5` options. This ensures that
|
||||
restic does not overwhelm the backend with concurrent IO operations.
|
||||
|
||||
https://github.com/restic/restic/pull/3475
|
||||
|
|
7
changelog/0.16.0_2023-07-31/issue-1495
Normal file
7
changelog/0.16.0_2023-07-31/issue-1495
Normal file
|
@ -0,0 +1,7 @@
|
|||
Enhancement: Sort snapshots by timestamp in `restic find`
|
||||
|
||||
The `find` command used to print snapshots in an arbitrary order. Restic now
|
||||
prints snapshots sorted by timestamp.
|
||||
|
||||
https://github.com/restic/restic/issues/1495
|
||||
https://github.com/restic/restic/pull/4409
|
|
@ -4,12 +4,13 @@ The `rebuild-index` command has been renamed to `repair index`. The old name
|
|||
will still work, but is deprecated.
|
||||
|
||||
When a snapshot was damaged, the only option up to now was to completely forget
|
||||
the snapshot, even if only some unimportant file was damaged.
|
||||
the snapshot, even if only some unimportant files in it were damaged and other
|
||||
files were still fine.
|
||||
|
||||
We've added a `repair snapshots` command, which can repair snapshots by removing
|
||||
damaged directories and missing files contents. Note that using this command
|
||||
can lead to data loss! Please see the "Troubleshooting" section in the documentation
|
||||
for more details.
|
||||
Restic now has a `repair snapshots` command, which can salvage any non-damaged
|
||||
files and parts of files in the snapshots by removing damaged directories and
|
||||
missing file contents. Please note that the damaged data may still be lost
|
||||
and see the "Troubleshooting" section in the documentation for more details.
|
||||
|
||||
https://github.com/restic/restic/issues/1759
|
||||
https://github.com/restic/restic/issues/1714
|
8
changelog/0.16.0_2023-07-31/issue-1926
Normal file
8
changelog/0.16.0_2023-07-31/issue-1926
Normal file
|
@ -0,0 +1,8 @@
|
|||
Enhancement: Allow certificate paths to be passed through environment variables
|
||||
|
||||
Restic will now read paths to certificates from the environment variables
|
||||
`RESTIC_CACERT` or `RESTIC_TLS_CLIENT_CERT` if `--cacert` or `--tls-client-cert`
|
||||
are not specified.
|
||||
|
||||
https://github.com/restic/restic/issues/1926
|
||||
https://github.com/restic/restic/pull/4384
|
|
@ -1,9 +1,9 @@
|
|||
Enhancement: Provide multi-platform Docker containers
|
||||
Enhancement: Provide multi-platform Docker images
|
||||
|
||||
The official Docker containers are now built for the architectures linux/386,
|
||||
The official Docker images are now built for the architectures linux/386,
|
||||
linux/amd64, linux/arm and linux/arm64.
|
||||
|
||||
As an alternative to the Docker Hub, the Docker containers are now also
|
||||
As an alternative to the Docker Hub, the Docker images are also
|
||||
available on ghcr.io, the GitHub Container Registry.
|
||||
|
||||
https://github.com/restic/restic/issues/2359
|
|
@ -1,10 +1,10 @@
|
|||
Enhancement: Add support for non-global Azure clouds
|
||||
|
||||
Restic backups on Azure only supported storages using the global domain
|
||||
The `azure` backend previously only supported storages using the global domain
|
||||
`core.windows.net`. This meant that backups to other domains such as Azure
|
||||
China (`core.chinacloudapi.cn') or Azure Germany (`core.cloudapi.de`) were
|
||||
China (`core.chinacloudapi.cn`) or Azure Germany (`core.cloudapi.de`) were
|
||||
not supported. Restic now allows overriding the global domain using the
|
||||
environment variable `AZURE_ENDPOINT_SUFFIX'.
|
||||
environment variable `AZURE_ENDPOINT_SUFFIX`.
|
||||
|
||||
https://github.com/restic/restic/issues/2468
|
||||
https://github.com/restic/restic/pull/4387
|
10
changelog/0.16.0_2023-07-31/issue-2565
Normal file
10
changelog/0.16.0_2023-07-31/issue-2565
Normal file
|
@ -0,0 +1,10 @@
|
|||
Bugfix: Support "unlimited" in `forget --keep-*` options
|
||||
|
||||
Restic would previously forget snapshots that should have been kept when a
|
||||
negative value was passed to the `--keep-*` options. Negative values are now
|
||||
forbidden. To keep all snapshots, the special value `unlimited` is now
|
||||
supported. For example, `--keep-monthly unlimited` will keep all monthly
|
||||
snapshots.
|
||||
|
||||
https://github.com/restic/restic/issues/2565
|
||||
https://github.com/restic/restic/pull/4234
|
12
changelog/0.16.0_2023-07-31/issue-3311
Normal file
12
changelog/0.16.0_2023-07-31/issue-3311
Normal file
|
@ -0,0 +1,12 @@
|
|||
Bugfix: Support non-UTF8 paths as symlink target
|
||||
|
||||
Earlier restic versions did not correctly `backup` and `restore` symlinks that
|
||||
contain a non-UTF8 target. Note that this only affected systems that still use
|
||||
a non-Unicode encoding for filesystem paths.
|
||||
|
||||
The repository format is now extended to add support for such symlinks. Please
|
||||
note that snapshots must have been created with at least restic version 0.16.0
|
||||
for `restore` to correctly handle non-UTF8 symlink targets when restoring them.
|
||||
|
||||
https://github.com/restic/restic/issues/3311
|
||||
https://github.com/restic/restic/pull/3802
|
|
@ -1,5 +1,9 @@
|
|||
Enhancement: Reduce memory usage by up to 25%
|
||||
|
||||
The in-memory index has been optimized to be more garbage collection friendly.
|
||||
Restic now defaults to `GOGC=50` to run the Go garbage collector more
|
||||
frequently.
|
||||
|
||||
https://github.com/restic/restic/issues/3328
|
||||
https://github.com/restic/restic/pull/4352
|
||||
https://github.com/restic/restic/pull/4353
|
11
changelog/0.16.0_2023-07-31/issue-3397
Normal file
11
changelog/0.16.0_2023-07-31/issue-3397
Normal file
|
@ -0,0 +1,11 @@
|
|||
Enhancement: Improve accuracy of ETA displayed during backup
|
||||
|
||||
Restic's `backup` command displayed an ETA that did not adapt when the rate of
|
||||
progress made during the backup changed during the course of the backup.
|
||||
|
||||
Restic now uses recent progress when computing the ETA. It is important to
|
||||
realize that the estimate may still be wrong, because restic cannot predict
|
||||
the future, but the hope is that the ETA will be more accurate in most cases.
|
||||
|
||||
https://github.com/restic/restic/issues/3397
|
||||
https://github.com/restic/restic/pull/3563
|
|
@ -1,8 +1,8 @@
|
|||
Enhancement: Keep oldest snapshot when there are not enough snapshots
|
||||
|
||||
The `forget` command now additionally preserves the oldest snapshot if fewer
|
||||
snapshots are kept than allowed by the `--keep-*` parameters. This maximizes
|
||||
amount of history kept while the specified limits are not yet reached.
|
||||
snapshots than allowed by the `--keep-*` parameters would otherwise be kept.
|
||||
This maximizes the amount of history kept within the specified limits.
|
||||
|
||||
https://github.com/restic/restic/issues/3624
|
||||
https://github.com/restic/restic/pull/4366
|
|
@ -1,7 +1,7 @@
|
|||
Enhancement: Add support for Managed / Worload Identity to azure backend
|
||||
Enhancement: Add support for Managed / Workload Identity to `azure` backend
|
||||
|
||||
Restic now additionally supports authenticating to Azure using Workload
|
||||
Identity or Managed Identity credentials which are automatically injected in
|
||||
Identity or Managed Identity credentials, which are automatically injected in
|
||||
several environments such as a managed Kubernetes cluster.
|
||||
|
||||
https://github.com/restic/restic/issues/3698
|
22
changelog/0.16.0_2023-07-31/issue-3871
Normal file
22
changelog/0.16.0_2023-07-31/issue-3871
Normal file
|
@ -0,0 +1,22 @@
|
|||
Enhancement: Support `<snapshot>:<subfolder>` syntax to select subfolders
|
||||
|
||||
Commands like `diff` or `restore` always worked with the full snapshot. This
|
||||
did not allow comparing only a specific subfolder or only restoring that folder
|
||||
(`restore --include subfolder` filters the restored files, but still creates the
|
||||
directories included in `subfolder`).
|
||||
|
||||
The commands `diff`, `dump`, `ls` and `restore` now support the
|
||||
`<snapshot>:<subfolder>` syntax, where `snapshot` is the ID of a snapshot (or
|
||||
the string `latest`) and `subfolder` is a path within the snapshot. The
|
||||
commands will then only work with the specified path of the snapshot. The
|
||||
`subfolder` must be a path to a folder as returned by `ls`. Two examples:
|
||||
|
||||
`restic restore -t target latest:/some/path`
|
||||
`restic diff 12345678:/some/path 90abcef:/some/path`
|
||||
|
||||
For debugging purposes, the `cat` command now supports `cat tree
|
||||
<snapshot>:<subfolder>` to return the directory metadata for the given
|
||||
subfolder.
|
||||
|
||||
https://github.com/restic/restic/issues/3871
|
||||
https://github.com/restic/restic/pull/4334
|
17
changelog/0.16.0_2023-07-31/issue-3941
Normal file
17
changelog/0.16.0_2023-07-31/issue-3941
Normal file
|
@ -0,0 +1,17 @@
|
|||
Enhancement: Support `--group-by` for backup parent selection
|
||||
|
||||
Previously, the `backup` command by default selected the parent snapshot based
|
||||
on the hostname and the backup targets. When the backup path list changed, the
|
||||
`backup` command was unable to determine a suitable parent snapshot and had to
|
||||
read all files again.
|
||||
|
||||
The new `--group-by` option for the `backup` command allows filtering snapshots
|
||||
for the parent selection by `host`, `paths` and `tags`. It defaults to
|
||||
`host,paths` which selects the latest snapshot with hostname and paths matching
|
||||
those of the backup run. This matches the behavior of prior restic versions.
|
||||
|
||||
The new `--group-by` option should be set to the same value as passed to
|
||||
`forget --group-by`.
|
||||
|
||||
https://github.com/restic/restic/issues/3941
|
||||
https://github.com/restic/restic/pull/4081
|
9
changelog/0.16.0_2023-07-31/issue-4130
Normal file
9
changelog/0.16.0_2023-07-31/issue-4130
Normal file
|
@ -0,0 +1,9 @@
|
|||
Enhancement: Cancel current command if cache becomes unusable
|
||||
|
||||
If the cache directory was removed or ran out of space while restic was
|
||||
running, this would previously cause further caching attempts to fail and
|
||||
thereby drastically slow down the command execution. Now, the currently running
|
||||
command is instead canceled.
|
||||
|
||||
https://github.com/restic/restic/issues/4130
|
||||
https://github.com/restic/restic/pull/4166
|
12
changelog/0.16.0_2023-07-31/issue-4159
Normal file
12
changelog/0.16.0_2023-07-31/issue-4159
Normal file
|
@ -0,0 +1,12 @@
|
|||
Enhancement: Add `--human-readable` option to `ls` and `find` commands
|
||||
|
||||
Previously, when using the `-l` option with the `ls` and `find` commands, the
|
||||
displayed size was always in bytes, without an option for a more human readable
|
||||
format such as MiB or GiB.
|
||||
|
||||
The new `--human-readable` option will convert longer size values into more
|
||||
human friendly values with an appropriate suffix depending on the output size.
|
||||
For example, a size of `14680064` will be shown as `14.000 MiB`.
|
||||
|
||||
https://github.com/restic/restic/issues/4159
|
||||
https://github.com/restic/restic/pull/4351
|
8
changelog/0.16.0_2023-07-31/issue-4188
Normal file
8
changelog/0.16.0_2023-07-31/issue-4188
Normal file
|
@ -0,0 +1,8 @@
|
|||
Enhancement: Include restic version in snapshot metadata
|
||||
|
||||
The restic version used to backup a snapshot is now included in its metadata
|
||||
and shown when inspecting a snapshot using `restic cat snapshot <snapshotID>`
|
||||
or `restic snapshots --json`.
|
||||
|
||||
https://github.com/restic/restic/issues/4188
|
||||
https://github.com/restic/restic/pull/4378
|
9
changelog/0.16.0_2023-07-31/issue-4199
Normal file
9
changelog/0.16.0_2023-07-31/issue-4199
Normal file
|
@ -0,0 +1,9 @@
|
|||
Bugfix: Avoid lock refresh issues on slow network connections
|
||||
|
||||
On network connections with a low upload speed, backups and other operations
|
||||
could fail with the error message `Fatal: failed to refresh lock in time`.
|
||||
|
||||
This has now been fixed by reworking the lock refresh handling.
|
||||
|
||||
https://github.com/restic/restic/issues/4199
|
||||
https://github.com/restic/restic/pull/4304
|
|
@ -2,7 +2,7 @@ Enhancement: Show progress bar during restore
|
|||
|
||||
The `restore` command now shows a progress report while restoring files.
|
||||
|
||||
Example: [0:42] 5.76% 23 files 12.98 MiB, total 3456 files 23.54 GiB
|
||||
Example: `[0:42] 5.76% 23 files 12.98 MiB, total 3456 files 23.54 GiB`
|
||||
|
||||
JSON output is now also supported.
|
||||
|
11
changelog/0.16.0_2023-07-31/issue-4274
Normal file
11
changelog/0.16.0_2023-07-31/issue-4274
Normal file
|
@ -0,0 +1,11 @@
|
|||
Bugfix: Improve lock refresh handling after standby
|
||||
|
||||
If the restic process was stopped or the host running restic entered standby
|
||||
during a long running operation such as a backup, this previously resulted in
|
||||
the operation failing with `Fatal: failed to refresh lock in time`.
|
||||
|
||||
This has now been fixed such that restic first checks whether it is safe to
|
||||
continue the current operation and only throws an error if not.
|
||||
|
||||
https://github.com/restic/restic/issues/4274
|
||||
https://github.com/restic/restic/pull/4374
|
8
changelog/0.16.0_2023-07-31/issue-719
Normal file
8
changelog/0.16.0_2023-07-31/issue-719
Normal file
|
@ -0,0 +1,8 @@
|
|||
Enhancement: Add `--retry-lock` option
|
||||
|
||||
This option allows specifying a duration for which restic will wait if the
|
||||
repository is already locked.
|
||||
|
||||
https://github.com/restic/restic/issues/719
|
||||
https://github.com/restic/restic/pull/2214
|
||||
https://github.com/restic/restic/pull/4107
|
|
@ -2,8 +2,6 @@ Change: Require Go 1.20 for Solaris builds
|
|||
|
||||
Building restic on Solaris now requires Go 1.20, as the library used to access
|
||||
Azure uses the mmap syscall, which is only available on Solaris starting from
|
||||
Go 1.20.
|
||||
|
||||
All other platforms continue to build with Go 1.18.
|
||||
Go 1.20. All other platforms however continue to build with Go 1.18.
|
||||
|
||||
https://github.com/restic/restic/pull/4201
|
6
changelog/0.16.0_2023-07-31/pull-4220
Normal file
6
changelog/0.16.0_2023-07-31/pull-4220
Normal file
|
@ -0,0 +1,6 @@
|
|||
Enhancement: Add `jq` binary to Docker image
|
||||
|
||||
The Docker image now contains `jq`, which can be useful to process JSON data
|
||||
output by restic.
|
||||
|
||||
https://github.com/restic/restic/pull/4220
|
7
changelog/0.16.0_2023-07-31/pull-4226
Normal file
7
changelog/0.16.0_2023-07-31/pull-4226
Normal file
|
@ -0,0 +1,7 @@
|
|||
Enhancement: Allow specifying region of new buckets in the `gs` backend
|
||||
|
||||
Previously, buckets used by the Google Cloud Storage backend would always get
|
||||
created in the "us" region. It is now possible to specify the region where a
|
||||
bucket should be created by using the `-o gs.region=us` option.
|
||||
|
||||
https://github.com/restic/restic/pull/4226
|
|
@ -2,7 +2,7 @@ Bugfix: Correctly clean up status bar output of the `backup` command
|
|||
|
||||
Due to a regression in restic 0.15.2, the status bar of the `backup` command
|
||||
could leave some output behind. This happened if filenames were printed that
|
||||
are wider than the current terminal width. This has been fixed.
|
||||
are wider than the current terminal width. This has now been fixed.
|
||||
|
||||
https://github.com/restic/restic/issues/4319
|
||||
https://github.com/restic/restic/pull/4318
|
8
changelog/0.16.0_2023-07-31/pull-4400
Normal file
8
changelog/0.16.0_2023-07-31/pull-4400
Normal file
|
@ -0,0 +1,8 @@
|
|||
Bugfix: Ignore missing folders in `rest` backend
|
||||
|
||||
If a repository accessed via the REST backend was missing folders, then restic
|
||||
would fail with an error while trying to list the data in the repository. This
|
||||
has been now fixed.
|
||||
|
||||
https://github.com/restic/restic/pull/4400
|
||||
https://github.com/restic/rest-server/issues/235
|
9
changelog/0.16.1_2023-10-24/issue-4128
Normal file
9
changelog/0.16.1_2023-10-24/issue-4128
Normal file
|
@ -0,0 +1,9 @@
|
|||
Enhancement: Automatically set `GOMAXPROCS` in resource-constrained containers
|
||||
|
||||
When running restic in a Linux container with CPU-usage limits, restic now
|
||||
automatically adjusts `GOMAXPROCS`. This helps to reduce the memory consumption
|
||||
on hosts with many CPU cores.
|
||||
|
||||
https://github.com/restic/restic/issues/4128
|
||||
https://github.com/restic/restic/pull/4485
|
||||
https://github.com/restic/restic/pull/4531
|
8
changelog/0.16.1_2023-10-24/issue-4513
Normal file
8
changelog/0.16.1_2023-10-24/issue-4513
Normal file
|
@ -0,0 +1,8 @@
|
|||
Bugfix: Make `key list` command honor `--no-lock`
|
||||
|
||||
The `key list` command now supports the `--no-lock` options. This allows
|
||||
determining which keys a repo can be accessed by without the need for having
|
||||
write access (e.g., read-only sftp access, filesystem snapshot).
|
||||
|
||||
https://github.com/restic/restic/issues/4513
|
||||
https://github.com/restic/restic/pull/4514
|
8
changelog/0.16.1_2023-10-24/issue-4516
Normal file
8
changelog/0.16.1_2023-10-24/issue-4516
Normal file
|
@ -0,0 +1,8 @@
|
|||
Bugfix: Do not try to load password on command line autocomplete
|
||||
|
||||
The command line autocompletion previously tried to load the repository
|
||||
password. This could cause the autocompletion not to work. Now, this step gets
|
||||
skipped.
|
||||
|
||||
https://github.com/restic/restic/issues/4516
|
||||
https://github.com/restic/restic/pull/4526
|
22
changelog/0.16.1_2023-10-24/issue-4523
Normal file
22
changelog/0.16.1_2023-10-24/issue-4523
Normal file
|
@ -0,0 +1,22 @@
|
|||
Bugfix: Update zstd library to fix possible data corruption at max. compression
|
||||
|
||||
In restic 0.16.0, backups where the compression level was set to `max` (using
|
||||
`--compression max`) could in rare and very specific circumstances result in
|
||||
data corruption due to a bug in the library used for compressing data.
|
||||
|
||||
Restic now uses the latest version of the library used to compress data, which
|
||||
includes a fix for this issue. Please note that the `auto` compression level
|
||||
(which restic uses by default) was never affected, and even if you used `max`
|
||||
compression, chances of being affected by this issue were very small.
|
||||
|
||||
To check a repository for any corruption, run `restic check --read-data`. This
|
||||
will download and verify the whole repository and can be used at any time to
|
||||
completely verify the integrity of a repository. If the `check` command detects
|
||||
anomalies, follow the suggested steps.
|
||||
|
||||
To simplify any needed repository repair and minimize data loss, there is also
|
||||
a new and experimental `repair packs` command that salvages all valid data from
|
||||
the affected pack files (see `restic help repair packs` for more information).
|
||||
|
||||
https://github.com/restic/restic/issues/4523
|
||||
https://github.com/restic/restic/pull/4530
|
7
changelog/0.16.1_2023-10-24/pull-299
Normal file
7
changelog/0.16.1_2023-10-24/pull-299
Normal file
|
@ -0,0 +1,7 @@
|
|||
Enhancement: Show progress bar while loading the index
|
||||
|
||||
Restic did not provide any feedback while loading index files. Now, there is a
|
||||
progress bar that shows the index loading progress.
|
||||
|
||||
https://github.com/restic/restic/issues/229
|
||||
https://github.com/restic/restic/pull/4419
|
11
changelog/0.16.1_2023-10-24/pull-4480
Normal file
11
changelog/0.16.1_2023-10-24/pull-4480
Normal file
|
@ -0,0 +1,11 @@
|
|||
Enhancement: Allow setting REST password and username via environment variables
|
||||
|
||||
Previously, it was only possible to specify the REST-server username and
|
||||
password in the repository URL, or by using the `--repository-file` option.
|
||||
This meant it was not possible to use authentication in contexts where the
|
||||
repository URL is stored in publicly accessible way.
|
||||
|
||||
Restic now allows setting the username and password using the
|
||||
`RESTIC_REST_USERNAME` and `RESTIC_REST_PASSWORD` variables.
|
||||
|
||||
https://github.com/restic/restic/pull/4480
|
7
changelog/0.16.1_2023-10-24/pull-4511
Normal file
7
changelog/0.16.1_2023-10-24/pull-4511
Normal file
|
@ -0,0 +1,7 @@
|
|||
Enhancement: Include inode numbers in JSON output for `find` and `ls` commands
|
||||
|
||||
Restic used to omit the inode numbers in the JSON messages emitted for nodes by
|
||||
the `ls` command as well as for matches by the `find` command. It now includes
|
||||
those values whenever they are available.
|
||||
|
||||
https://github.com/restic/restic/pull/4511
|
12
changelog/0.16.1_2023-10-24/pull-4519
Normal file
12
changelog/0.16.1_2023-10-24/pull-4519
Normal file
|
@ -0,0 +1,12 @@
|
|||
Enhancement: Add config option to set SFTP command arguments
|
||||
|
||||
When using the `sftp` backend, scenarios where a custom identity file was
|
||||
needed for the SSH connection, required the full command to be specified:
|
||||
`-o sftp.command='ssh user@host:port -i /ssh/my_private_key -s sftp'`
|
||||
|
||||
Now, the `-o sftp.args=...` option can be passed to restic to specify
|
||||
custom arguments for the SSH command executed by the SFTP backend.
|
||||
This simplifies the above example to `-o sftp.args='-i /ssh/my_private_key'`.
|
||||
|
||||
https://github.com/restic/restic/pull/4519
|
||||
https://github.com/restic/restic/issues/4241
|
8
changelog/0.16.1_2023-10-24/pull-4532
Normal file
8
changelog/0.16.1_2023-10-24/pull-4532
Normal file
|
@ -0,0 +1,8 @@
|
|||
Change: Update dependencies and require Go 1.19 or newer
|
||||
|
||||
We have updated all dependencies. Since some libraries require newer Go
|
||||
standard library features, support for Go 1.18 has been dropped, which means
|
||||
that restic now requires at least Go 1.19 to build.
|
||||
|
||||
https://github.com/restic/restic/pull/4532
|
||||
https://github.com/restic/restic/pull/4533
|
9
changelog/0.16.2_2023-10-29/issue-4540
Normal file
9
changelog/0.16.2_2023-10-29/issue-4540
Normal file
|
@ -0,0 +1,9 @@
|
|||
Bugfix: Restore ARMv5 support for ARM binaries
|
||||
|
||||
The official release binaries for restic 0.16.1 were accidentally built to
|
||||
require ARMv7. The build process is now updated to restore support for ARMv5.
|
||||
|
||||
Please note that restic 0.17.0 will drop support for ARMv5 and require at least
|
||||
ARMv6.
|
||||
|
||||
https://github.com/restic/restic/issues/4540
|
8
changelog/0.16.2_2023-10-29/pull-4545
Normal file
8
changelog/0.16.2_2023-10-29/pull-4545
Normal file
|
@ -0,0 +1,8 @@
|
|||
Bugfix: Repair documentation build on Read the Docs
|
||||
|
||||
For restic 0.16.1, no documentation was available at
|
||||
https://restic.readthedocs.io/ .
|
||||
|
||||
The documentation build process is now updated to work again.
|
||||
|
||||
https://github.com/restic/restic/pull/4545
|
|
@ -3,7 +3,7 @@ Enhancement: Add local metadata cache
|
|||
We've added a local cache for metadata so that restic doesn't need to load
|
||||
all metadata (snapshots, indexes, ...) from the repo each time it starts. By
|
||||
default the cache is active, but there's a new global option `--no-cache`
|
||||
that can be used to disable the cache. By deafult, the cache a standard
|
||||
that can be used to disable the cache. By default, the cache a standard
|
||||
cache folder for the OS, which can be overridden with `--cache-dir`. The
|
||||
cache will automatically populate, indexes and snapshots are saved as they
|
||||
are loaded. Cache directories for repos that haven't been used recently can
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Enhancement: Make `check` print `no errors found` explicitly
|
||||
|
||||
The `check` command now explicetly prints `No errors were found` when no errors
|
||||
The `check` command now explicitly prints `No errors were found` when no errors
|
||||
could be found.
|
||||
|
||||
https://github.com/restic/restic/pull/1319
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Bugfix: Limit bandwith at the http.RoundTripper for HTTP based backends
|
||||
Bugfix: Limit bandwidth at the http.RoundTripper for HTTP based backends
|
||||
|
||||
https://github.com/restic/restic/issues/1506
|
||||
https://github.com/restic/restic/pull/1511
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Bugfix: backup: Remove bandwidth display
|
||||
|
||||
This commit removes the bandwidth displayed during backup process. It is
|
||||
misleading and seldomly correct, because it's neither the "read
|
||||
misleading and seldom correct, because it's neither the "read
|
||||
bandwidth" (only for the very first backup) nor the "upload bandwidth".
|
||||
Many users are confused about (and rightly so), c.f. #1581, #1033, #1591
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ that means making a request (e.g. via HTTP) and returning an error when the
|
|||
file already exists.
|
||||
|
||||
This is not accurate, the file could have been created between the HTTP request
|
||||
testing for it, and when writing starts, so we've relaxed this requeriment,
|
||||
testing for it, and when writing starts, so we've relaxed this requirement,
|
||||
which saves one additional HTTP request per newly added file.
|
||||
|
||||
https://github.com/restic/restic/pull/1623
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Enhancement: Allow keeping a time range of snaphots
|
||||
Enhancement: Allow keeping a time range of snapshots
|
||||
|
||||
We've added the `--keep-within` option to the `forget` command. It instructs
|
||||
restic to keep all snapshots within the given duration since the newest
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Enhancement: Display reason why forget keeps snapshots
|
||||
|
||||
We've added a column to the list of snapshots `forget` keeps which details the
|
||||
reasons to keep a particuliar snapshot. This makes debugging policies for
|
||||
reasons to keep a particular snapshot. This makes debugging policies for
|
||||
forget much easier. Please remember to always try things out with `--dry-run`!
|
||||
|
||||
https://github.com/restic/restic/pull/1876
|
||||
|
|
|
@ -9,7 +9,7 @@ file should be noticed, and the modified file will be backed up. The ctime check
|
|||
will be disabled if the --ignore-inode flag was given.
|
||||
|
||||
If this change causes problems for you, please open an issue, and we can look in
|
||||
to adding a seperate flag to disable just the ctime check.
|
||||
to adding a separate flag to disable just the ctime check.
|
||||
|
||||
https://github.com/restic/restic/issues/2179
|
||||
https://github.com/restic/restic/pull/2212
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
{{- range $changes := . }}{{ with $changes -}}
|
||||
Changelog for restic {{ .Version }} ({{ .Date }})
|
||||
=======================================
|
||||
# Table of Contents
|
||||
|
||||
{{ range . -}}
|
||||
* [Changelog for {{ .Version }}](#changelog-for-restic-{{ .Version | replace "." ""}}-{{ .Date | lower -}})
|
||||
{{ end -}}
|
||||
|
||||
{{- range $changes := . }}{{ with $changes }}
|
||||
|
||||
# Changelog for restic {{ .Version }} ({{ .Date }})
|
||||
The following sections list the changes in restic {{ .Version }} relevant to
|
||||
restic users. The changes are ordered by importance.
|
||||
|
||||
Summary
|
||||
-------
|
||||
## Summary
|
||||
{{ range $entry := .Entries }}{{ with $entry }}
|
||||
* {{ .TypeShort }} #{{ .PrimaryID }}: {{ .Title }}
|
||||
{{- end }}{{ end }}
|
||||
|
||||
Details
|
||||
-------
|
||||
## Details
|
||||
{{ range $entry := .Entries }}{{ with $entry }}
|
||||
* {{ .Type }} #{{ .PrimaryID }}: {{ .Title }}
|
||||
{{ range $par := .Paragraphs }}
|
||||
|
@ -27,6 +30,5 @@ Details
|
|||
{{ range $url := .OtherURLs }}
|
||||
{{ $url -}}
|
||||
{{ end }}
|
||||
{{ end }}{{ end }}
|
||||
|
||||
{{ end }}{{ end -}}
|
||||
{{ end }}{{ end -}}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
# The first line must start with Bugfix:, Enhancement: or Change:,
|
||||
# including the colon. Use present tense. Remove lines starting with '#'
|
||||
# from this template.
|
||||
# including the colon. Use present tense and the imperative mood. Remove
|
||||
# lines starting with '#' from this template.
|
||||
Enhancement: Allow custom bar in the foo command
|
||||
|
||||
# Describe the problem in the past tense, the new behavior in the present
|
||||
# tense. Mention the affected commands, backends, operating systems, etc.
|
||||
# Focus on user-facing behavior, not the implementation.
|
||||
# Use "Restic now ..." instead of "We have changed ...".
|
||||
|
||||
Restic foo always used the system-wide bar when deciding how to frob an
|
||||
item in the baz backend. It now permits selecting the bar with --bar or
|
||||
the environment variable RESTIC_BAR. The system-wide bar is still the
|
||||
default.
|
||||
item in the `baz` backend. It now permits selecting the bar with `--bar`
|
||||
or the environment variable `RESTIC_BAR`. The system-wide bar is still
|
||||
the default.
|
||||
|
||||
# The last section is a list of issue, PR and forum URLs.
|
||||
# The first issue ID determines the filename for the changelog entry:
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
Enhancement: Certificates can be passed through environment variables
|
||||
|
||||
Restic will now read the paths to the certificates from the environment
|
||||
variables `RESTIC_CACERT` or `RESTIC_TLS_CLIENT_CERT` if `--cacert` or
|
||||
`--tls-client-cert` are not specified.
|
||||
|
||||
https://github.com/restic/restic/issues/1926
|
||||
https://github.com/restic/restic/pull/4384
|
|
@ -1,8 +0,0 @@
|
|||
Bugfix: Restic forget --keep-* options now interpret "-1" as "forever"
|
||||
|
||||
Restic would forget snapshots that should have been kept when "-1" was
|
||||
used as a value for --keep-* options. It now interprets "-1" as forever,
|
||||
e.g. an option like --keep-monthly -1 will keep all monthly snapshots.
|
||||
|
||||
https://github.com/restic/restic/issues/2565
|
||||
https://github.com/restic/restic/pull/4234
|
|
@ -1,11 +0,0 @@
|
|||
Enhancement: Improve the ETA displayed during backup
|
||||
|
||||
Restic's `backup` command displayed an ETA that did not adapt when the rate
|
||||
of progress made during the backup changed during the course of the
|
||||
backup. Restic now uses recent progress when computing the ETA. It is
|
||||
important to realize that the estimate may still be wrong, because restic
|
||||
cannot predict the future, but the hope is that the ETA will be more
|
||||
accurate in most cases.
|
||||
|
||||
https://github.com/restic/restic/issues/3397
|
||||
https://github.com/restic/restic/pull/3563
|
|
@ -1,14 +0,0 @@
|
|||
Enhancement: Support `--group-by` for backup parent selection
|
||||
|
||||
The backup command by default selected the parent snapshot based on the hostname
|
||||
and the backup targets. When the backup path list changed, the backup command
|
||||
was unable to determine a suitable parent snapshot and had to read all
|
||||
files again.
|
||||
|
||||
The new `--group-by` option for the backup command allows filtering snapshots
|
||||
for the parent selection by `host`, `paths` and `tags`. It defaults to
|
||||
`host,paths` which selects the latest snapshot with hostname and paths matching
|
||||
those of the backup run. It should be used consistently with `forget --group-by`.
|
||||
|
||||
https://github.com/restic/restic/issues/3941
|
||||
https://github.com/restic/restic/pull/4081
|
|
@ -1,8 +0,0 @@
|
|||
Enhancement: `backup` includes restic version in snapshot metadata
|
||||
|
||||
The restic version used backup the snapshot is now included in its metadata.
|
||||
The program version is shown when inspecting a snapshot using `restic cat
|
||||
snapshot <snapshotID>` or `restic snapshots --json`.
|
||||
|
||||
https://github.com/restic/restic/issues/4188
|
||||
https://github.com/restic/restic/pull/4378
|
14
changelog/unreleased/issue-4251
Normal file
14
changelog/unreleased/issue-4251
Normal file
|
@ -0,0 +1,14 @@
|
|||
Enhancement: Support reading backup from a program's standard output
|
||||
|
||||
When reading data from stdin, the `backup` command could not verify whether the
|
||||
corresponding command completed successfully.
|
||||
|
||||
The `backup` command now supports starting an arbitrary command and sourcing
|
||||
the backup content from its standard output. This enables restic to verify that
|
||||
the command completes with exit code zero. A non-zero exit code causes the
|
||||
backup to fail.
|
||||
|
||||
Example: `restic backup --stdin-from-command mysqldump [...]`
|
||||
|
||||
https://github.com/restic/restic/issues/4251
|
||||
https://github.com/restic/restic/pull/4410
|
8
changelog/unreleased/issue-4515
Normal file
8
changelog/unreleased/issue-4515
Normal file
|
@ -0,0 +1,8 @@
|
|||
Change: Don't retry to load files that don't exist
|
||||
|
||||
Restic used to always retry to load files. It now only retries to load
|
||||
files if they exist.
|
||||
|
||||
https://github.com/restic/restic/issues/4515
|
||||
https://github.com/restic/restic/issues/1523
|
||||
https://github.com/restic/restic/pull/4520
|
6
changelog/unreleased/issue-4540
Normal file
6
changelog/unreleased/issue-4540
Normal file
|
@ -0,0 +1,6 @@
|
|||
Change: Require at least ARMv6 for ARM binaries
|
||||
|
||||
The official release binaries of restic now require at least ARMv6 support for ARM platforms.
|
||||
|
||||
https://github.com/restic/restic/issues/4540
|
||||
https://github.com/restic/restic/pull/4542
|
7
changelog/unreleased/issue-4547
Normal file
7
changelog/unreleased/issue-4547
Normal file
|
@ -0,0 +1,7 @@
|
|||
Enhancement: Add support for `--json` option to `version` command
|
||||
|
||||
Restic now supports outputting restic version and used go version, OS and
|
||||
architecture via JSON when using the version command.
|
||||
|
||||
https://github.com/restic/restic/issues/4547
|
||||
https://github.com/restic/restic/pull/4553
|
|
@ -1,8 +0,0 @@
|
|||
Enhancement: Add --retry-lock option
|
||||
|
||||
This option allows to specify a duration for which restic will wait if there
|
||||
already exists a conflicting lock within the repository.
|
||||
|
||||
https://github.com/restic/restic/issues/719
|
||||
https://github.com/restic/restic/pull/2214
|
||||
https://github.com/restic/restic/pull/4107
|
|
@ -1,7 +0,0 @@
|
|||
Enhancement: Cancel current command if cache becomes unusable
|
||||
|
||||
If the cache directory was removed or ran out of space while restic was
|
||||
running, this caused further caching attempts to fail and drastically slow down
|
||||
the command execution. Now, the currently running command is canceled instead.
|
||||
|
||||
https://github.com/restic/restic/pull/4166
|
|
@ -1,5 +0,0 @@
|
|||
Enhancement: Add jq to container image
|
||||
|
||||
The Docker container image now contains jq which can be useful when restic outputs json data.
|
||||
|
||||
https://github.com/restic/restic/pull/4220
|
|
@ -1,7 +0,0 @@
|
|||
Enhancement: Allow specifying the region of new buckets in the gcs backend
|
||||
|
||||
Buckets used by the Google Cloud Storage backend would always get created in
|
||||
the "us" region. It is now possible to specify the region, where a bucket
|
||||
should get created.
|
||||
|
||||
https://github.com/restic/restic/pull/4226
|
|
@ -1,5 +0,0 @@
|
|||
Bugfix: Avoid lock refresh issues with slow network connections
|
||||
|
||||
On network connections with a low upload speed, restic could often fail backups and other operations with `Fatal: failed to refresh lock in time`. We've reworked the lock refresh to avoid this error.
|
||||
|
||||
https://github.com/restic/restic/pull/4304
|
|
@ -1,6 +0,0 @@
|
|||
Change: Building restic on AIX is temporarily unsupported
|
||||
|
||||
As the current version of the library used for the Azure backend does not
|
||||
compile on AIX, there are currently no restic builds available for AIX.
|
||||
|
||||
https://github.com/restic/restic/pull/4365
|
7
changelog/unreleased/pull-4503
Normal file
7
changelog/unreleased/pull-4503
Normal file
|
@ -0,0 +1,7 @@
|
|||
Bugfix: Correct hardlink handling in `stats` command
|
||||
|
||||
If files on different devices had the same inode id, then the `stats` command
|
||||
did not correctly calculate the snapshot size. This has been fixed.
|
||||
|
||||
https://github.com/restic/restic/pull/4503
|
||||
https://forum.restic.net/t/possible-bug-in-stats/6461/8
|
5
changelog/unreleased/pull-4573
Normal file
5
changelog/unreleased/pull-4573
Normal file
|
@ -0,0 +1,5 @@
|
|||
Enhancement: Add `--new-host` and `--new-time` options to `rewrite` command
|
||||
|
||||
`restic rewrite` now allows rewriting the host and / or time metadata of a snapshot.
|
||||
|
||||
https://github.com/restic/restic/pull/4573
|
7
changelog/unreleased/pull-4590
Normal file
7
changelog/unreleased/pull-4590
Normal file
|
@ -0,0 +1,7 @@
|
|||
Enhancement: `mount` tests mountpoint existence before opening the repository
|
||||
|
||||
The restic `mount` command now checks for the existence of the
|
||||
mountpoint before opening the repository, leading to quicker error
|
||||
detection.
|
||||
|
||||
https://github.com/restic/restic/pull/4590
|
|
@ -97,6 +97,7 @@ type BackupOptions struct {
|
|||
ExcludeLargerThan string
|
||||
Stdin bool
|
||||
StdinFilename string
|
||||
StdinCommand bool
|
||||
Tags restic.TagLists
|
||||
Host string
|
||||
FilesFrom []string
|
||||
|
@ -134,6 +135,7 @@ func init() {
|
|||
f.StringVar(&backupOptions.ExcludeLargerThan, "exclude-larger-than", "", "max `size` of the files to be backed up (allowed suffixes: k/K, m/M, g/G, t/T)")
|
||||
f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin")
|
||||
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "`filename` to use when reading from stdin")
|
||||
f.BoolVar(&backupOptions.StdinCommand, "stdin-from-command", false, "execute command and store its stdout")
|
||||
f.Var(&backupOptions.Tags, "tag", "add `tags` for the new snapshot in the format `tag[,tag,...]` (can be specified multiple times)")
|
||||
f.UintVar(&backupOptions.ReadConcurrency, "read-concurrency", 0, "read `n` files concurrently (default: $RESTIC_READ_CONCURRENCY or 2)")
|
||||
f.StringVarP(&backupOptions.Host, "host", "H", "", "set the `hostname` for the snapshot manually. To prevent an expensive rescan use the \"parent\" flag")
|
||||
|
@ -287,7 +289,7 @@ func (opts BackupOptions) Check(gopts GlobalOptions, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if opts.Stdin {
|
||||
if opts.Stdin || opts.StdinCommand {
|
||||
if len(opts.FilesFrom) > 0 {
|
||||
return errors.Fatal("--stdin and --files-from cannot be used together")
|
||||
}
|
||||
|
@ -298,7 +300,7 @@ func (opts BackupOptions) Check(gopts GlobalOptions, args []string) error {
|
|||
return errors.Fatal("--stdin and --files-from-raw cannot be used together")
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
if len(args) > 0 && !opts.StdinCommand {
|
||||
return errors.Fatal("--stdin was specified and files/dirs were listed as arguments")
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +368,7 @@ func collectRejectFuncs(opts BackupOptions, targets []string) (fs []RejectFunc,
|
|||
|
||||
// collectTargets returns a list of target files/dirs from several sources.
|
||||
func collectTargets(opts BackupOptions, args []string) (targets []string, err error) {
|
||||
if opts.Stdin {
|
||||
if opts.Stdin || opts.StdinCommand {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -453,7 +455,7 @@ func findParentSnapshot(ctx context.Context, repo restic.Repository, opts Backup
|
|||
f.Tags = []restic.TagList{opts.Tags.Flatten()}
|
||||
}
|
||||
|
||||
sn, err := f.FindLatest(ctx, repo.Backend(), repo, snName)
|
||||
sn, _, err := f.FindLatest(ctx, repo, repo, snName)
|
||||
// Snapshot not found is ok if no explicit parent was set
|
||||
if opts.Parent == "" && errors.Is(err, restic.ErrNoSnapshotFound) {
|
||||
err = nil
|
||||
|
@ -506,10 +508,13 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
if !gopts.JSON {
|
||||
progressPrinter.V("lock repository")
|
||||
}
|
||||
lock, ctx, err := lockRepo(ctx, repo, gopts.RetryLock, gopts.JSON)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
if !opts.DryRun {
|
||||
var lock *restic.Lock
|
||||
lock, ctx, err = lockRepo(ctx, repo, gopts.RetryLock, gopts.JSON)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// rejectByNameFuncs collect functions that can reject items from the backup based on path only
|
||||
|
@ -543,7 +548,10 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
if !gopts.JSON {
|
||||
progressPrinter.V("load index files")
|
||||
}
|
||||
err = repo.LoadIndex(ctx)
|
||||
|
||||
bar := newIndexTerminalProgress(gopts.Quiet, gopts.JSON, term)
|
||||
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -586,16 +594,24 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
defer localVss.DeleteSnapshots()
|
||||
targetFS = localVss
|
||||
}
|
||||
if opts.Stdin {
|
||||
|
||||
if opts.Stdin || opts.StdinCommand {
|
||||
if !gopts.JSON {
|
||||
progressPrinter.V("read data from stdin")
|
||||
}
|
||||
filename := path.Join("/", opts.StdinFilename)
|
||||
var source io.ReadCloser = os.Stdin
|
||||
if opts.StdinCommand {
|
||||
source, err = fs.NewCommandReader(ctx, args, globalOptions.stderr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
targetFS = &fs.Reader{
|
||||
ModTime: timeStamp,
|
||||
Name: filename,
|
||||
Mode: 0644,
|
||||
ReadCloser: os.Stdin,
|
||||
ReadCloser: source,
|
||||
}
|
||||
targets = []string{filename}
|
||||
}
|
||||
|
@ -624,7 +640,13 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
success := true
|
||||
arch.Error = func(item string, err error) error {
|
||||
success = false
|
||||
return progressReporter.Error(item, err)
|
||||
reterr := progressReporter.Error(item, err)
|
||||
// If we receive a fatal error during the execution of the snapshot,
|
||||
// we abort the snapshot.
|
||||
if reterr == nil && errors.IsFatal(err) {
|
||||
reterr = err
|
||||
}
|
||||
return reterr
|
||||
}
|
||||
arch.CompleteItem = progressReporter.CompleteItem
|
||||
arch.StartFile = progressReporter.StartFile
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
|
@ -252,7 +253,7 @@ func TestBackupTreeLoadError(t *testing.T) {
|
|||
|
||||
r, err := OpenRepository(context.TODO(), env.gopts)
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, r.LoadIndex(context.TODO()))
|
||||
rtest.OK(t, r.LoadIndex(context.TODO(), nil))
|
||||
treePacks := restic.NewIDSet()
|
||||
r.Index().Each(context.TODO(), func(pb restic.PackedBlob) {
|
||||
if pb.Type == restic.TreeBlob {
|
||||
|
@ -265,7 +266,7 @@ func TestBackupTreeLoadError(t *testing.T) {
|
|||
|
||||
// delete the subdirectory pack first
|
||||
for id := range treePacks {
|
||||
rtest.OK(t, r.Backend().Remove(context.TODO(), restic.Handle{Type: restic.PackFile, Name: id.String()}))
|
||||
rtest.OK(t, r.Backend().Remove(context.TODO(), backend.Handle{Type: restic.PackFile, Name: id.String()}))
|
||||
}
|
||||
testRunRebuildIndex(t, env.gopts)
|
||||
// now the repo is missing the tree blob in the index; check should report this
|
||||
|
@ -567,3 +568,72 @@ func linkEqual(source, dest []string) bool {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestStdinFromCommand(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testSetupBackupData(t, env)
|
||||
opts := BackupOptions{
|
||||
StdinCommand: true,
|
||||
StdinFilename: "stdin",
|
||||
}
|
||||
|
||||
testRunBackup(t, filepath.Dir(env.testdata), []string{"python", "-c", "import sys; print('something'); sys.exit(0)"}, opts, env.gopts)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
||||
|
||||
func TestStdinFromCommandNoOutput(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testSetupBackupData(t, env)
|
||||
opts := BackupOptions{
|
||||
StdinCommand: true,
|
||||
StdinFilename: "stdin",
|
||||
}
|
||||
|
||||
err := testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"python", "-c", "import sys; sys.exit(0)"}, opts, env.gopts)
|
||||
rtest.Assert(t, err != nil && err.Error() == "at least one source file could not be read", "No data error expected")
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
||||
|
||||
func TestStdinFromCommandFailExitCode(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testSetupBackupData(t, env)
|
||||
opts := BackupOptions{
|
||||
StdinCommand: true,
|
||||
StdinFilename: "stdin",
|
||||
}
|
||||
|
||||
err := testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"python", "-c", "import sys; print('test'); sys.exit(1)"}, opts, env.gopts)
|
||||
rtest.Assert(t, err != nil, "Expected error while backing up")
|
||||
|
||||
testListSnapshots(t, env.gopts, 0)
|
||||
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
||||
|
||||
func TestStdinFromCommandFailNoOutputAndExitCode(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testSetupBackupData(t, env)
|
||||
opts := BackupOptions{
|
||||
StdinCommand: true,
|
||||
StdinFilename: "stdin",
|
||||
}
|
||||
|
||||
err := testRunBackupAssumeFailure(t, filepath.Dir(env.testdata), []string{"python", "-c", "import sys; sys.exit(1)"}, opts, env.gopts)
|
||||
rtest.Assert(t, err != nil, "Expected error while backing up")
|
||||
|
||||
testListSnapshots(t, env.gopts, 0)
|
||||
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -13,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
var cmdCat = &cobra.Command{
|
||||
Use: "cat [flags] [pack|blob|snapshot|index|key|masterkey|config|lock] ID",
|
||||
Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]",
|
||||
Short: "Print internal objects to stdout",
|
||||
Long: `
|
||||
The "cat" command is used to print internal objects to stdout.
|
||||
|
@ -33,9 +34,34 @@ func init() {
|
|||
cmdRoot.AddCommand(cmdCat)
|
||||
}
|
||||
|
||||
func validateCatArgs(args []string) error {
|
||||
var allowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.Fatal("type not specified")
|
||||
}
|
||||
|
||||
validType := false
|
||||
for _, v := range allowedCmds {
|
||||
if v == args[0] {
|
||||
validType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validType {
|
||||
return errors.Fatalf("invalid type %q, must be one of [%s]", args[0], strings.Join(allowedCmds, "|"))
|
||||
}
|
||||
|
||||
if args[0] != "masterkey" && args[0] != "config" && len(args) != 2 {
|
||||
return errors.Fatal("ID not specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
||||
if len(args) < 1 || (args[0] != "masterkey" && args[0] != "config" && len(args) != 2) {
|
||||
return errors.Fatal("type or ID not specified")
|
||||
if err := validateCatArgs(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := OpenRepository(ctx, gopts)
|
||||
|
@ -55,7 +81,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
tpe := args[0]
|
||||
|
||||
var id restic.ID
|
||||
if tpe != "masterkey" && tpe != "config" && tpe != "snapshot" {
|
||||
if tpe != "masterkey" && tpe != "config" && tpe != "snapshot" && tpe != "tree" {
|
||||
id, err = restic.ParseID(args[1])
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to parse ID: %v\n", err)
|
||||
|
@ -80,7 +106,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
Println(string(buf))
|
||||
return nil
|
||||
case "snapshot":
|
||||
sn, err := restic.FindSnapshot(ctx, repo.Backend(), repo, args[1])
|
||||
sn, _, err := restic.FindSnapshot(ctx, repo, repo, args[1])
|
||||
if err != nil {
|
||||
return errors.Fatalf("could not find snapshot: %v\n", err)
|
||||
}
|
||||
|
@ -128,7 +154,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
return nil
|
||||
|
||||
case "pack":
|
||||
h := restic.Handle{Type: restic.PackFile, Name: id.String()}
|
||||
h := backend.Handle{Type: restic.PackFile, Name: id.String()}
|
||||
buf, err := backend.LoadAll(ctx, nil, repo.Backend(), h)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -143,7 +169,8 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
return err
|
||||
|
||||
case "blob":
|
||||
err = repo.LoadIndex(ctx)
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -165,6 +192,30 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
|
||||
return errors.Fatal("blob not found")
|
||||
|
||||
case "tree":
|
||||
sn, subfolder, err := restic.FindSnapshot(ctx, repo, repo, args[1])
|
||||
if err != nil {
|
||||
return errors.Fatalf("could not find snapshot: %v\n", err)
|
||||
}
|
||||
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf, err := repo.LoadBlob(ctx, restic.TreeBlob, *sn.Tree, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = globalOptions.stdout.Write(buf)
|
||||
return err
|
||||
|
||||
default:
|
||||
return errors.Fatal("invalid type")
|
||||
}
|
||||
|
|
30
cmd/restic/cmd_cat_test.go
Normal file
30
cmd/restic/cmd_cat_test.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func TestCatArgsValidation(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
args []string
|
||||
err string
|
||||
}{
|
||||
{[]string{}, "Fatal: type not specified"},
|
||||
{[]string{"masterkey"}, ""},
|
||||
{[]string{"invalid"}, `Fatal: invalid type "invalid"`},
|
||||
{[]string{"snapshot"}, "Fatal: ID not specified"},
|
||||
{[]string{"snapshot", "12345678"}, ""},
|
||||
} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
err := validateCatArgs(test.args)
|
||||
if test.err == "" {
|
||||
rtest.Assert(t, err == nil, "unexpected error %q", err)
|
||||
} else {
|
||||
rtest.Assert(t, strings.Contains(err.Error(), test.err), "unexpected error expected %q to contain %q", err, test.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -226,7 +226,8 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args
|
|||
}
|
||||
|
||||
Verbosef("load indexes\n")
|
||||
hints, errs := chkr.LoadIndex(ctx)
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
hints, errs := chkr.LoadIndex(ctx, bar)
|
||||
|
||||
errorsFound := false
|
||||
suggestIndexRebuild := false
|
||||
|
@ -329,11 +330,28 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args
|
|||
|
||||
go chkr.ReadPacks(ctx, packs, p, errChan)
|
||||
|
||||
var salvagePacks restic.IDs
|
||||
|
||||
for err := range errChan {
|
||||
errorsFound = true
|
||||
Warnf("%v\n", err)
|
||||
if err, ok := err.(*checker.ErrPackData); ok {
|
||||
if strings.Contains(err.Error(), "wrong data returned, hash is") {
|
||||
salvagePacks = append(salvagePacks, err.PackID)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Done()
|
||||
|
||||
if len(salvagePacks) > 0 {
|
||||
Warnf("\nThe repository contains pack files with damaged blobs. These blobs must be removed to repair the repository. This can be done using the following commands:\n\n")
|
||||
var strIds []string
|
||||
for _, id := range salvagePacks {
|
||||
strIds = append(strIds, id.String())
|
||||
}
|
||||
Warnf("RESTIC_FEATURES=repair-packs-v1 restic repair packs %v\nrestic repair snapshots --forget\n\n", strings.Join(strIds, " "))
|
||||
Warnf("Corrupted blobs are either caused by hardware problems or bugs in restic. Please open an issue at https://github.com/restic/restic/issues/new/choose for further troubleshooting!\n")
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -399,7 +417,7 @@ func selectPacksByBucket(allPacks map[restic.ID]int64, bucket, totalBuckets uint
|
|||
return packs
|
||||
}
|
||||
|
||||
// selectRandomPacksByPercentage selects the given percentage of packs which are randomly choosen.
|
||||
// selectRandomPacksByPercentage selects the given percentage of packs which are randomly chosen.
|
||||
func selectRandomPacksByPercentage(allPacks map[restic.ID]int64, percentage float64) map[restic.ID]int64 {
|
||||
packCount := len(allPacks)
|
||||
packsToCheck := int(float64(packCount) * (percentage / 100.0))
|
||||
|
|
|
@ -71,7 +71,7 @@ func TestSelectPacksByBucket(t *testing.T) {
|
|||
var testPacks = make(map[restic.ID]int64)
|
||||
for i := 1; i <= 10; i++ {
|
||||
id := restic.NewRandomID()
|
||||
// ensure relevant part of generated id is reproducable
|
||||
// ensure relevant part of generated id is reproducible
|
||||
id[0] = byte(i)
|
||||
testPacks[id] = 0
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ func TestSelectRandomPacksByPercentage(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectNoRandomPacksByPercentage(t *testing.T) {
|
||||
// that the a repository without pack files works
|
||||
// that the repository without pack files works
|
||||
var testPacks = make(map[restic.ID]int64)
|
||||
selectedPacks := selectRandomPacksByPercentage(testPacks, 10.0)
|
||||
rtest.Assert(t, len(selectedPacks) == 0, "Expected 0 selected packs")
|
||||
|
@ -158,7 +158,7 @@ func TestSelectRandomPacksByFileSize(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSelectNoRandomPacksByFileSize(t *testing.T) {
|
||||
// that the a repository without pack files works
|
||||
// that the repository without pack files works
|
||||
var testPacks = make(map[restic.ID]int64)
|
||||
selectedPacks := selectRandomPacksByFileSize(testPacks, 10, 500)
|
||||
rtest.Assert(t, len(selectedPacks) == 0, "Expected 0 selected packs")
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
|
@ -88,23 +87,24 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
|
|||
return err
|
||||
}
|
||||
|
||||
srcSnapshotLister, err := backend.MemorizeList(ctx, srcRepo.Backend(), restic.SnapshotFile)
|
||||
srcSnapshotLister, err := restic.MemorizeList(ctx, srcRepo, restic.SnapshotFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstSnapshotLister, err := backend.MemorizeList(ctx, dstRepo.Backend(), restic.SnapshotFile)
|
||||
dstSnapshotLister, err := restic.MemorizeList(ctx, dstRepo, restic.SnapshotFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
debug.Log("Loading source index")
|
||||
if err := srcRepo.LoadIndex(ctx); err != nil {
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
if err := srcRepo.LoadIndex(ctx, bar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bar = newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
debug.Log("Loading destination index")
|
||||
if err := dstRepo.LoadIndex(ctx); err != nil {
|
||||
if err := dstRepo.LoadIndex(ctx, bar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ func prettyPrintJSON(wr io.Writer, item interface{}) error {
|
|||
}
|
||||
|
||||
func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
|
||||
return restic.ForAllSnapshots(ctx, repo.Backend(), repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
|
||||
return restic.ForAllSnapshots(ctx, repo, repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ type Blob struct {
|
|||
func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
|
||||
|
||||
var m sync.Mutex
|
||||
return restic.ParallelList(ctx, repo.Backend(), restic.PackFile, repo.Connections(), func(ctx context.Context, id restic.ID, size int64) error {
|
||||
return restic.ParallelList(ctx, repo, restic.PackFile, repo.Connections(), func(ctx context.Context, id restic.ID, size int64) error {
|
||||
blobs, _, err := repo.ListPack(ctx, id, size)
|
||||
if err != nil {
|
||||
Warnf("error for pack %v: %v\n", id.Str(), err)
|
||||
|
@ -134,7 +134,7 @@ func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer)
|
|||
}
|
||||
|
||||
func dumpIndexes(ctx context.Context, repo restic.Repository, wr io.Writer) error {
|
||||
return index.ForAllIndexes(ctx, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
|
||||
return index.ForAllIndexes(ctx, repo, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
|
||||
Printf("index_id: %v\n", id)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -290,7 +290,7 @@ func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, by
|
|||
})
|
||||
err := wg.Wait()
|
||||
if err != nil {
|
||||
panic("all go rountines can only return nil")
|
||||
panic("all go routines can only return nil")
|
||||
}
|
||||
|
||||
if !found {
|
||||
|
@ -321,7 +321,7 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li
|
|||
panic(err)
|
||||
}
|
||||
be := repo.Backend()
|
||||
h := restic.Handle{
|
||||
h := backend.Handle{
|
||||
Name: packID.String(),
|
||||
Type: restic.PackFile,
|
||||
}
|
||||
|
@ -447,7 +447,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er
|
|||
for _, name := range args {
|
||||
id, err := restic.ParseID(name)
|
||||
if err != nil {
|
||||
id, err = restic.Find(ctx, repo.Backend(), restic.PackFile, name)
|
||||
id, err = restic.Find(ctx, repo, restic.PackFile, name)
|
||||
if err != nil {
|
||||
Warnf("error: %v\n", err)
|
||||
continue
|
||||
|
@ -469,7 +469,8 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er
|
|||
}
|
||||
}
|
||||
|
||||
err = repo.LoadIndex(ctx)
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -489,7 +490,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er
|
|||
func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) error {
|
||||
Printf("examine %v\n", id)
|
||||
|
||||
h := restic.Handle{
|
||||
h := backend.Handle{
|
||||
Type: restic.PackFile,
|
||||
Name: id.String(),
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
@ -16,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
var cmdDiff = &cobra.Command{
|
||||
Use: "diff [flags] snapshot-ID snapshot-ID",
|
||||
Use: "diff [flags] snapshotID snapshotID",
|
||||
Short: "Show differences between two snapshots",
|
||||
Long: `
|
||||
The "diff" command shows differences from the first to the second snapshot. The
|
||||
|
@ -29,6 +28,10 @@ directory:
|
|||
* M The file's content was modified
|
||||
* T The type was changed, e.g. a file was made a symlink
|
||||
|
||||
To only compare files in specific subfolders, you can use the
|
||||
"<snapshotID>:<subfolder>" syntax, where "subfolder" is a path within the
|
||||
snapshot.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
|
@ -54,12 +57,12 @@ func init() {
|
|||
f.BoolVar(&diffOptions.ShowMetadata, "metadata", false, "print changes in metadata")
|
||||
}
|
||||
|
||||
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.Repository, desc string) (*restic.Snapshot, error) {
|
||||
sn, err := restic.FindSnapshot(ctx, be, repo, desc)
|
||||
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.Repository, desc string) (*restic.Snapshot, string, error) {
|
||||
sn, subfolder, err := restic.FindSnapshot(ctx, be, repo, desc)
|
||||
if err != nil {
|
||||
return nil, errors.Fatal(err.Error())
|
||||
return nil, "", errors.Fatal(err.Error())
|
||||
}
|
||||
return sn, err
|
||||
return sn, subfolder, err
|
||||
}
|
||||
|
||||
// Comparer collects all things needed to compare two snapshots.
|
||||
|
@ -342,16 +345,16 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
|
||||
// cache snapshots listing
|
||||
be, err := backend.MemorizeList(ctx, repo.Backend(), restic.SnapshotFile)
|
||||
be, err := restic.MemorizeList(ctx, repo, restic.SnapshotFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sn1, err := loadSnapshot(ctx, be, repo, args[0])
|
||||
sn1, subfolder1, err := loadSnapshot(ctx, be, repo, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sn2, err := loadSnapshot(ctx, be, repo, args[1])
|
||||
sn2, subfolder2, err := loadSnapshot(ctx, be, repo, args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -359,8 +362,8 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
if !gopts.JSON {
|
||||
Verbosef("comparing snapshot %v to %v:\n\n", sn1.ID().Str(), sn2.ID().Str())
|
||||
}
|
||||
|
||||
if err = repo.LoadIndex(ctx); err != nil {
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -372,6 +375,16 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
return errors.Errorf("snapshot %v has nil tree", sn2.ID().Str())
|
||||
}
|
||||
|
||||
sn1.Tree, err = restic.FindTreeDirectory(ctx, repo, sn1.Tree, subfolder1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sn2.Tree, err = restic.FindTreeDirectory(ctx, repo, sn2.Tree, subfolder2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := &Comparer{
|
||||
repo: repo,
|
||||
opts: diffOptions,
|
||||
|
|
|
@ -24,9 +24,13 @@ single file is selected, it prints its contents to stdout. Folders are output
|
|||
as a tar (default) or zip file containing the contents of the specified folder.
|
||||
Pass "/" as file name to dump the whole snapshot as an archive file.
|
||||
|
||||
The special snapshot "latest" can be used to use the latest snapshot in the
|
||||
The special snapshotID "latest" can be used to use the latest snapshot in the
|
||||
repository.
|
||||
|
||||
To include the folder content at the root of the archive, you can use the
|
||||
"<snapshotID>:<subfolder>" syntax, where "subfolder" is a path within the
|
||||
snapshot.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
|
@ -139,16 +143,22 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
}
|
||||
|
||||
sn, err := (&restic.SnapshotFilter{
|
||||
sn, subfolder, err := (&restic.SnapshotFilter{
|
||||
Hosts: opts.Hosts,
|
||||
Paths: opts.Paths,
|
||||
Tags: opts.Tags,
|
||||
}).FindLatest(ctx, repo.Backend(), repo, snapshotIDString)
|
||||
}).FindLatest(ctx, repo, repo, snapshotIDString)
|
||||
if err != nil {
|
||||
return errors.Fatalf("failed to find snapshot: %v", err)
|
||||
}
|
||||
|
||||
err = repo.LoadIndex(ctx)
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/filter"
|
||||
|
@ -51,6 +50,7 @@ type FindOptions struct {
|
|||
PackID, ShowPackID bool
|
||||
CaseInsensitive bool
|
||||
ListLong bool
|
||||
HumanReadable bool
|
||||
restic.SnapshotFilter
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ func init() {
|
|||
f.BoolVar(&findOptions.ShowPackID, "show-pack-id", false, "display the pack-ID the blobs belong to (with --blob or --tree)")
|
||||
f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
|
||||
f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||
f.BoolVar(&findOptions.HumanReadable, "human-readable", false, "print sizes in human readable format")
|
||||
|
||||
initMultiSnapshotFilter(f, &findOptions.SnapshotFilter, true)
|
||||
}
|
||||
|
@ -104,12 +105,13 @@ func parseTime(str string) (time.Time, error) {
|
|||
}
|
||||
|
||||
type statefulOutput struct {
|
||||
ListLong bool
|
||||
JSON bool
|
||||
inuse bool
|
||||
newsn *restic.Snapshot
|
||||
oldsn *restic.Snapshot
|
||||
hits int
|
||||
ListLong bool
|
||||
HumanReadable bool
|
||||
JSON bool
|
||||
inuse bool
|
||||
newsn *restic.Snapshot
|
||||
oldsn *restic.Snapshot
|
||||
hits int
|
||||
}
|
||||
|
||||
func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
||||
|
@ -123,7 +125,6 @@ func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
|||
|
||||
// Make the following attributes disappear
|
||||
Name byte `json:"name,omitempty"`
|
||||
Inode byte `json:"inode,omitempty"`
|
||||
ExtendedAttributes byte `json:"extended_attributes,omitempty"`
|
||||
Device byte `json:"device,omitempty"`
|
||||
Content byte `json:"content,omitempty"`
|
||||
|
@ -164,7 +165,7 @@ func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
|||
s.oldsn = s.newsn
|
||||
Verbosef("Found matching entries in snapshot %s from %s\n", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
||||
}
|
||||
Println(formatNode(path, node, s.ListLong))
|
||||
Println(formatNode(path, node, s.ListLong, s.HumanReadable))
|
||||
}
|
||||
|
||||
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
||||
|
@ -582,19 +583,19 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
}
|
||||
|
||||
snapshotLister, err := backend.MemorizeList(ctx, repo.Backend(), restic.SnapshotFile)
|
||||
snapshotLister, err := restic.MemorizeList(ctx, repo, restic.SnapshotFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo.LoadIndex(ctx); err != nil {
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := &Finder{
|
||||
repo: repo,
|
||||
pat: pat,
|
||||
out: statefulOutput{ListLong: opts.ListLong, JSON: gopts.JSON},
|
||||
out: statefulOutput{ListLong: opts.ListLong, HumanReadable: opts.HumanReadable, JSON: gopts.JSON},
|
||||
ignoreTrees: restic.NewIDSet(),
|
||||
}
|
||||
|
||||
|
@ -618,7 +619,16 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
}
|
||||
|
||||
var filteredSnapshots []*restic.Snapshot
|
||||
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, opts.Snapshots) {
|
||||
filteredSnapshots = append(filteredSnapshots, sn)
|
||||
}
|
||||
|
||||
sort.Slice(filteredSnapshots, func(i, j int) bool {
|
||||
return filteredSnapshots[i].Time.Before(filteredSnapshots[j].Time)
|
||||
})
|
||||
|
||||
for _, sn := range filteredSnapshots {
|
||||
if f.blobIDs != nil || f.treeIDs != nil {
|
||||
if err = f.findIDs(ctx, sn); err != nil && err.Error() != "OK" {
|
||||
return err
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
@ -36,14 +37,49 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
|
|||
},
|
||||
}
|
||||
|
||||
type ForgetPolicyCount int
|
||||
|
||||
var ErrNegativePolicyCount = errors.New("negative values not allowed, use 'unlimited' instead")
|
||||
|
||||
func (c *ForgetPolicyCount) Set(s string) error {
|
||||
switch s {
|
||||
case "unlimited":
|
||||
*c = -1
|
||||
default:
|
||||
val, err := strconv.ParseInt(s, 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val < 0 {
|
||||
return ErrNegativePolicyCount
|
||||
}
|
||||
*c = ForgetPolicyCount(val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ForgetPolicyCount) String() string {
|
||||
switch *c {
|
||||
case -1:
|
||||
return "unlimited"
|
||||
default:
|
||||
return strconv.FormatInt(int64(*c), 10)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ForgetPolicyCount) Type() string {
|
||||
return "n"
|
||||
}
|
||||
|
||||
// ForgetOptions collects all options for the forget command.
|
||||
type ForgetOptions struct {
|
||||
Last int
|
||||
Hourly int
|
||||
Daily int
|
||||
Weekly int
|
||||
Monthly int
|
||||
Yearly int
|
||||
Last ForgetPolicyCount
|
||||
Hourly ForgetPolicyCount
|
||||
Daily ForgetPolicyCount
|
||||
Weekly ForgetPolicyCount
|
||||
Monthly ForgetPolicyCount
|
||||
Yearly ForgetPolicyCount
|
||||
Within restic.Duration
|
||||
WithinHourly restic.Duration
|
||||
WithinDaily restic.Duration
|
||||
|
@ -67,12 +103,12 @@ func init() {
|
|||
cmdRoot.AddCommand(cmdForget)
|
||||
|
||||
f := cmdForget.Flags()
|
||||
f.IntVarP(&forgetOptions.Last, "keep-last", "l", 0, "keep the last `n` snapshots (use '-1' to keep all snapshots)")
|
||||
f.IntVarP(&forgetOptions.Hourly, "keep-hourly", "H", 0, "keep the last `n` hourly snapshots (use '-1' to keep all hourly snapshots)")
|
||||
f.IntVarP(&forgetOptions.Daily, "keep-daily", "d", 0, "keep the last `n` daily snapshots (use '-1' to keep all daily snapshots)")
|
||||
f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots (use '-1' to keep all weekly snapshots)")
|
||||
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots (use '-1' to keep all monthly snapshots)")
|
||||
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots (use '-1' to keep all yearly snapshots)")
|
||||
f.VarP(&forgetOptions.Last, "keep-last", "l", "keep the last `n` snapshots (use 'unlimited' to keep all snapshots)")
|
||||
f.VarP(&forgetOptions.Hourly, "keep-hourly", "H", "keep the last `n` hourly snapshots (use 'unlimited' to keep all hourly snapshots)")
|
||||
f.VarP(&forgetOptions.Daily, "keep-daily", "d", "keep the last `n` daily snapshots (use 'unlimited' to keep all daily snapshots)")
|
||||
f.VarP(&forgetOptions.Weekly, "keep-weekly", "w", "keep the last `n` weekly snapshots (use 'unlimited' to keep all weekly snapshots)")
|
||||
f.VarP(&forgetOptions.Monthly, "keep-monthly", "m", "keep the last `n` monthly snapshots (use 'unlimited' to keep all monthly snapshots)")
|
||||
f.VarP(&forgetOptions.Yearly, "keep-yearly", "y", "keep the last `n` yearly snapshots (use 'unlimited' to keep all yearly snapshots)")
|
||||
f.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
|
||||
f.VarP(&forgetOptions.WithinHourly, "keep-within-hourly", "", "keep hourly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
|
||||
f.VarP(&forgetOptions.WithinDaily, "keep-within-daily", "", "keep daily snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
|
||||
|
@ -147,7 +183,7 @@ func runForget(ctx context.Context, opts ForgetOptions, gopts GlobalOptions, arg
|
|||
var snapshots restic.Snapshots
|
||||
removeSnIDs := restic.NewIDSet()
|
||||
|
||||
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, &opts.SnapshotFilter, args) {
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, repo, &opts.SnapshotFilter, args) {
|
||||
snapshots = append(snapshots, sn)
|
||||
}
|
||||
|
||||
|
@ -165,12 +201,12 @@ func runForget(ctx context.Context, opts ForgetOptions, gopts GlobalOptions, arg
|
|||
}
|
||||
|
||||
policy := restic.ExpirePolicy{
|
||||
Last: opts.Last,
|
||||
Hourly: opts.Hourly,
|
||||
Daily: opts.Daily,
|
||||
Weekly: opts.Weekly,
|
||||
Monthly: opts.Monthly,
|
||||
Yearly: opts.Yearly,
|
||||
Last: int(opts.Last),
|
||||
Hourly: int(opts.Hourly),
|
||||
Daily: int(opts.Daily),
|
||||
Weekly: int(opts.Weekly),
|
||||
Monthly: int(opts.Monthly),
|
||||
Yearly: int(opts.Yearly),
|
||||
Within: opts.Within,
|
||||
WithinHourly: opts.WithinHourly,
|
||||
WithinDaily: opts.WithinDaily,
|
||||
|
|
|
@ -7,55 +7,84 @@ import (
|
|||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func TestForgetPolicyValues(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
value ForgetPolicyCount
|
||||
err string
|
||||
}{
|
||||
{"0", ForgetPolicyCount(0), ""},
|
||||
{"1", ForgetPolicyCount(1), ""},
|
||||
{"unlimited", ForgetPolicyCount(-1), ""},
|
||||
{"", ForgetPolicyCount(0), "strconv.ParseInt: parsing \"\": invalid syntax"},
|
||||
{"-1", ForgetPolicyCount(0), ErrNegativePolicyCount.Error()},
|
||||
{"abc", ForgetPolicyCount(0), "strconv.ParseInt: parsing \"abc\": invalid syntax"},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
var count ForgetPolicyCount
|
||||
err := count.Set(testCase.input)
|
||||
|
||||
if testCase.err != "" {
|
||||
rtest.Assert(t, err != nil, "should have returned error for input %+v", testCase.input)
|
||||
rtest.Equals(t, testCase.err, err.Error())
|
||||
} else {
|
||||
rtest.Assert(t, err == nil, "expected no error for input %+v, got %v", testCase.input, err)
|
||||
rtest.Equals(t, testCase.value, count)
|
||||
rtest.Equals(t, testCase.input, count.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForgetOptionValues(t *testing.T) {
|
||||
const negValErrorMsg = "Fatal: negative values other than -1 are not allowed for --keep-*"
|
||||
const negDurationValErrorMsg = "Fatal: durations containing negative values are not allowed for --keep-within*"
|
||||
testCases := []struct {
|
||||
input ForgetOptions
|
||||
expectsError bool
|
||||
errorMsg string
|
||||
input ForgetOptions
|
||||
errorMsg string
|
||||
}{
|
||||
{ForgetOptions{Last: 1}, false, ""},
|
||||
{ForgetOptions{Hourly: 1}, false, ""},
|
||||
{ForgetOptions{Daily: 1}, false, ""},
|
||||
{ForgetOptions{Weekly: 1}, false, ""},
|
||||
{ForgetOptions{Monthly: 1}, false, ""},
|
||||
{ForgetOptions{Yearly: 1}, false, ""},
|
||||
{ForgetOptions{Last: 0}, false, ""},
|
||||
{ForgetOptions{Hourly: 0}, false, ""},
|
||||
{ForgetOptions{Daily: 0}, false, ""},
|
||||
{ForgetOptions{Weekly: 0}, false, ""},
|
||||
{ForgetOptions{Monthly: 0}, false, ""},
|
||||
{ForgetOptions{Yearly: 0}, false, ""},
|
||||
{ForgetOptions{Last: -1}, false, ""},
|
||||
{ForgetOptions{Hourly: -1}, false, ""},
|
||||
{ForgetOptions{Daily: -1}, false, ""},
|
||||
{ForgetOptions{Weekly: -1}, false, ""},
|
||||
{ForgetOptions{Monthly: -1}, false, ""},
|
||||
{ForgetOptions{Yearly: -1}, false, ""},
|
||||
{ForgetOptions{Last: -2}, true, negValErrorMsg},
|
||||
{ForgetOptions{Hourly: -2}, true, negValErrorMsg},
|
||||
{ForgetOptions{Daily: -2}, true, negValErrorMsg},
|
||||
{ForgetOptions{Weekly: -2}, true, negValErrorMsg},
|
||||
{ForgetOptions{Monthly: -2}, true, negValErrorMsg},
|
||||
{ForgetOptions{Yearly: -2}, true, negValErrorMsg},
|
||||
{ForgetOptions{Within: restic.ParseDurationOrPanic("1y2m3d3h")}, false, ""},
|
||||
{ForgetOptions{WithinHourly: restic.ParseDurationOrPanic("1y2m3d3h")}, false, ""},
|
||||
{ForgetOptions{WithinDaily: restic.ParseDurationOrPanic("1y2m3d3h")}, false, ""},
|
||||
{ForgetOptions{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d3h")}, false, ""},
|
||||
{ForgetOptions{WithinMonthly: restic.ParseDurationOrPanic("2y4m6d8h")}, false, ""},
|
||||
{ForgetOptions{WithinYearly: restic.ParseDurationOrPanic("2y4m6d8h")}, false, ""},
|
||||
{ForgetOptions{Within: restic.ParseDurationOrPanic("-1y2m3d3h")}, true, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinHourly: restic.ParseDurationOrPanic("1y-2m3d3h")}, true, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinDaily: restic.ParseDurationOrPanic("1y2m-3d3h")}, true, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d-3h")}, true, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinMonthly: restic.ParseDurationOrPanic("-2y4m6d8h")}, true, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinYearly: restic.ParseDurationOrPanic("2y-4m6d8h")}, true, negDurationValErrorMsg},
|
||||
{ForgetOptions{Last: 1}, ""},
|
||||
{ForgetOptions{Hourly: 1}, ""},
|
||||
{ForgetOptions{Daily: 1}, ""},
|
||||
{ForgetOptions{Weekly: 1}, ""},
|
||||
{ForgetOptions{Monthly: 1}, ""},
|
||||
{ForgetOptions{Yearly: 1}, ""},
|
||||
{ForgetOptions{Last: 0}, ""},
|
||||
{ForgetOptions{Hourly: 0}, ""},
|
||||
{ForgetOptions{Daily: 0}, ""},
|
||||
{ForgetOptions{Weekly: 0}, ""},
|
||||
{ForgetOptions{Monthly: 0}, ""},
|
||||
{ForgetOptions{Yearly: 0}, ""},
|
||||
{ForgetOptions{Last: -1}, ""},
|
||||
{ForgetOptions{Hourly: -1}, ""},
|
||||
{ForgetOptions{Daily: -1}, ""},
|
||||
{ForgetOptions{Weekly: -1}, ""},
|
||||
{ForgetOptions{Monthly: -1}, ""},
|
||||
{ForgetOptions{Yearly: -1}, ""},
|
||||
{ForgetOptions{Last: -2}, negValErrorMsg},
|
||||
{ForgetOptions{Hourly: -2}, negValErrorMsg},
|
||||
{ForgetOptions{Daily: -2}, negValErrorMsg},
|
||||
{ForgetOptions{Weekly: -2}, negValErrorMsg},
|
||||
{ForgetOptions{Monthly: -2}, negValErrorMsg},
|
||||
{ForgetOptions{Yearly: -2}, negValErrorMsg},
|
||||
{ForgetOptions{Within: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||
{ForgetOptions{WithinHourly: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||
{ForgetOptions{WithinDaily: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||
{ForgetOptions{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||
{ForgetOptions{WithinMonthly: restic.ParseDurationOrPanic("2y4m6d8h")}, ""},
|
||||
{ForgetOptions{WithinYearly: restic.ParseDurationOrPanic("2y4m6d8h")}, ""},
|
||||
{ForgetOptions{Within: restic.ParseDurationOrPanic("-1y2m3d3h")}, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinHourly: restic.ParseDurationOrPanic("1y-2m3d3h")}, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinDaily: restic.ParseDurationOrPanic("1y2m-3d3h")}, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d-3h")}, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinMonthly: restic.ParseDurationOrPanic("-2y4m6d8h")}, negDurationValErrorMsg},
|
||||
{ForgetOptions{WithinYearly: restic.ParseDurationOrPanic("2y-4m6d8h")}, negDurationValErrorMsg},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
err := verifyForgetOptions(&testCase.input)
|
||||
if testCase.expectsError {
|
||||
if testCase.errorMsg != "" {
|
||||
rtest.Assert(t, err != nil, "should have returned error for input %+v", testCase.input)
|
||||
rtest.Equals(t, testCase.errorMsg, err.Error())
|
||||
} else {
|
||||
|
|
|
@ -75,7 +75,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
|||
return err
|
||||
}
|
||||
|
||||
repo, err := ReadRepo(gopts)
|
||||
gopts.Repo, err = ReadRepo(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
|||
return err
|
||||
}
|
||||
|
||||
be, err := create(ctx, repo, gopts, gopts.extended)
|
||||
be, err := create(ctx, gopts.Repo, gopts, gopts.extended)
|
||||
if err != nil {
|
||||
return errors.Fatalf("create repository at %s failed: %v\n", location.StripPassword(gopts.backends, gopts.Repo), err)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
@ -59,7 +60,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
|||
var m sync.Mutex
|
||||
var keys []keyInfo
|
||||
|
||||
err := restic.ParallelList(ctx, s.Backend(), restic.KeyFile, s.Connections(), func(ctx context.Context, id restic.ID, size int64) error {
|
||||
err := restic.ParallelList(ctx, s, restic.KeyFile, s.Connections(), func(ctx context.Context, id restic.ID, size int64) error {
|
||||
k, err := repository.LoadKey(ctx, s, id)
|
||||
if err != nil {
|
||||
Warnf("LoadKey() failed: %v\n", err)
|
||||
|
@ -149,7 +150,7 @@ func deleteKey(ctx context.Context, repo *repository.Repository, id restic.ID) e
|
|||
return errors.Fatal("refusing to remove key currently used to access repository")
|
||||
}
|
||||
|
||||
h := restic.Handle{Type: restic.KeyFile, Name: id.String()}
|
||||
h := backend.Handle{Type: restic.KeyFile, Name: id.String()}
|
||||
err := repo.Backend().Remove(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -176,7 +177,7 @@ func changePassword(ctx context.Context, repo *repository.Repository, gopts Glob
|
|||
return err
|
||||
}
|
||||
|
||||
h := restic.Handle{Type: restic.KeyFile, Name: oldID.String()}
|
||||
h := backend.Handle{Type: restic.KeyFile, Name: oldID.String()}
|
||||
err = repo.Backend().Remove(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -193,7 +194,7 @@ func switchToNewKeyAndRemoveIfBroken(ctx context.Context, repo *repository.Repos
|
|||
err := repo.SearchKey(ctx, pw, 0, key.ID().String())
|
||||
if err != nil {
|
||||
// the key is invalid, try to remove it
|
||||
h := restic.Handle{Type: restic.KeyFile, Name: key.ID().String()}
|
||||
h := backend.Handle{Type: restic.KeyFile, Name: key.ID().String()}
|
||||
_ = repo.Backend().Remove(ctx, h)
|
||||
return errors.Fatalf("failed to access repository with new key: %v", err)
|
||||
}
|
||||
|
@ -212,10 +213,13 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
|
||||
switch args[0] {
|
||||
case "list":
|
||||
lock, ctx, err := lockRepo(ctx, repo, gopts.RetryLock, gopts.JSON)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
if !gopts.NoLock {
|
||||
var lock *restic.Lock
|
||||
lock, ctx, err = lockRepo(ctx, repo, gopts.RetryLock, gopts.JSON)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return listKeys(ctx, repo, gopts)
|
||||
|
@ -234,7 +238,7 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
id, err := restic.Find(ctx, repo.Backend(), restic.KeyFile, args[1])
|
||||
id, err := restic.Find(ctx, repo, restic.KeyFile, args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
|
@ -110,11 +110,11 @@ func TestKeyAddRemove(t *testing.T) {
|
|||
}
|
||||
|
||||
type emptySaveBackend struct {
|
||||
restic.Backend
|
||||
backend.Backend
|
||||
}
|
||||
|
||||
func (b *emptySaveBackend) Save(ctx context.Context, h restic.Handle, _ restic.RewindReader) error {
|
||||
return b.Backend.Save(ctx, h, restic.NewByteReader([]byte{}, nil))
|
||||
func (b *emptySaveBackend) Save(ctx context.Context, h backend.Handle, _ backend.RewindReader) error {
|
||||
return b.Backend.Save(ctx, h, backend.NewByteReader([]byte{}, nil))
|
||||
}
|
||||
|
||||
func TestKeyProblems(t *testing.T) {
|
||||
|
@ -122,7 +122,7 @@ func TestKeyProblems(t *testing.T) {
|
|||
defer cleanup()
|
||||
|
||||
testRunInit(t, env.gopts)
|
||||
env.gopts.backendTestHook = func(r restic.Backend) (restic.Backend, error) {
|
||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||
return &emptySaveBackend{r}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ func runList(ctx context.Context, cmd *cobra.Command, gopts GlobalOptions, args
|
|||
case "locks":
|
||||
t = restic.LockFile
|
||||
case "blobs":
|
||||
return index.ForAllIndexes(ctx, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
|
||||
return index.ForAllIndexes(ctx, repo, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
@ -50,7 +49,8 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
|
|||
type LsOptions struct {
|
||||
ListLong bool
|
||||
restic.SnapshotFilter
|
||||
Recursive bool
|
||||
Recursive bool
|
||||
HumanReadable bool
|
||||
}
|
||||
|
||||
var lsOptions LsOptions
|
||||
|
@ -62,6 +62,7 @@ func init() {
|
|||
initSingleSnapshotFilter(flags, &lsOptions.SnapshotFilter)
|
||||
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||
flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories")
|
||||
flags.BoolVar(&lsOptions.HumanReadable, "human-readable", false, "print sizes in human readable format")
|
||||
}
|
||||
|
||||
type lsSnapshot struct {
|
||||
|
@ -85,6 +86,7 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
|
|||
ModTime time.Time `json:"mtime,omitempty"`
|
||||
AccessTime time.Time `json:"atime,omitempty"`
|
||||
ChangeTime time.Time `json:"ctime,omitempty"`
|
||||
Inode uint64 `json:"inode,omitempty"`
|
||||
StructType string `json:"struct_type"` // "node"
|
||||
|
||||
size uint64 // Target for Size pointer.
|
||||
|
@ -100,6 +102,7 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
|
|||
ModTime: node.ModTime,
|
||||
AccessTime: node.AccessTime,
|
||||
ChangeTime: node.ChangeTime,
|
||||
Inode: node.Inode,
|
||||
StructType: "node",
|
||||
}
|
||||
// Always print size for regular files, even when empty,
|
||||
|
@ -166,12 +169,13 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||
return err
|
||||
}
|
||||
|
||||
snapshotLister, err := backend.MemorizeList(ctx, repo.Backend(), restic.SnapshotFile)
|
||||
snapshotLister, err := restic.MemorizeList(ctx, repo, restic.SnapshotFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = repo.LoadIndex(ctx); err != nil {
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -206,11 +210,11 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||
Verbosef("snapshot %s of %v filtered by %v at %s):\n", sn.ID().Str(), sn.Paths, dirs, sn.Time)
|
||||
}
|
||||
printNode = func(path string, node *restic.Node) {
|
||||
Printf("%s\n", formatNode(path, node, lsOptions.ListLong))
|
||||
Printf("%s\n", formatNode(path, node, lsOptions.ListLong, lsOptions.HumanReadable))
|
||||
}
|
||||
}
|
||||
|
||||
sn, err := (&restic.SnapshotFilter{
|
||||
sn, subfolder, err := (&restic.SnapshotFilter{
|
||||
Hosts: opts.Hosts,
|
||||
Paths: opts.Paths,
|
||||
Tags: opts.Tags,
|
||||
|
@ -219,6 +223,11 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||
return err
|
||||
}
|
||||
|
||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printSnapshot(sn)
|
||||
|
||||
err = walker.Walk(ctx, repo, *sn.Tree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) {
|
||||
|
|
|
@ -113,6 +113,15 @@ func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args
|
|||
return errors.Fatal("wrong number of parameters")
|
||||
}
|
||||
|
||||
mountpoint := args[0]
|
||||
|
||||
// Check the existence of the mount point at the earliest stage to
|
||||
// prevent unnecessary computations while opening the repository.
|
||||
if _, err := resticfs.Stat(mountpoint); errors.Is(err, os.ErrNotExist) {
|
||||
Verbosef("Mountpoint %s doesn't exist\n", mountpoint)
|
||||
return err
|
||||
}
|
||||
|
||||
debug.Log("start mount")
|
||||
defer debug.Log("finish mount")
|
||||
|
||||
|
@ -130,17 +139,12 @@ func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args
|
|||
}
|
||||
}
|
||||
|
||||
err = repo.LoadIndex(ctx)
|
||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mountpoint := args[0]
|
||||
|
||||
if _, err := resticfs.Stat(mountpoint); errors.Is(err, os.ErrNotExist) {
|
||||
Verbosef("Mountpoint %s doesn't exist\n", mountpoint)
|
||||
return err
|
||||
}
|
||||
mountOptions := []systemFuse.MountOption{
|
||||
systemFuse.ReadOnly(),
|
||||
systemFuse.FSName("restic"),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue