Compare commits

...

64 Commits

Author SHA1 Message Date
Alexander Neumann
be8be3397c Add version for 0.15.2 2023-04-24 20:28:37 +02:00
Alexander Neumann
db6b4f8912 Update manpages and auto-completion 2023-04-24 20:28:37 +02:00
Alexander Neumann
1f3f042f32 Generate CHANGELOG.md for 0.15.2 2023-04-24 20:28:24 +02:00
Alexander Neumann
0aaa4e6cbe Prepare changelog for 0.15.2 2023-04-24 20:28:24 +02:00
Leo R. Lundgren
0bac935dac doc: Polish changelogs 2023-04-23 22:10:04 +02:00
Michael Eischer
1e6e9f9bd0 tweak changelogs 2023-04-23 12:46:55 +02:00
greatroar
f342db7666 ui/termstatus: Quote funny filenames
Fixes #2260, #4191.
2023-04-23 12:45:40 +02:00
Michael Eischer
07a44a88f2 Fix snapshot filtering for relative paths in the backup command
The snapshot filtering internally converts relative paths to absolute
ones to ensure that the parent snapshots selection works for backups of
relative paths.
2023-04-14 22:58:34 +02:00
Michael Eischer
48e065d971 Sync dependency upgrades from master
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob still uses v0.5.1
as upgrading it would increase the minimum Go version on Solaris to
1.20.
2023-04-14 22:58:34 +02:00
greatroar
3b24c15c3d fuse: Mix inode hashes in a non-symmetric way
Since 0.15 (#4020), inodes are generated as hashes of names, xor'd with
the parent inode. That means that the inode of a/b/b is

	h(a/b/b) = h(a) ^ h(b) ^ h(b) = h(a).

I.e., the grandchild has the same inode as the grandparent. GNU find
trips over this because it thinks it has encountered a loop in the
filesystem, and fails to search a/b/b. This happens more generally when
the same name occurs an even number of times.

Fix this by multiplying the parent by a large prime, so the combining
operation is not longer symmetric in its arguments. This is what the FNV
hash does, which we used prior to 0.15. The hash is now

	h(a/b/b) = h(b) ^ p*(h(b) ^ p*h(a))

Note that we already ensure that h(x) is never zero.

Collisions can still occur, but they should be much less likely to occur
within a single path.

Fixes #4253.
2023-04-14 20:50:39 +02:00
greatroar
4304e01ca2 fuse: Report fuse.Attr.Blocks correctly
Fixes #4239.
2023-04-14 20:50:08 +02:00
Ian Muge
593eb710b4 added changelog 2023-04-13 22:54:36 +02:00
greatroar
97274ecabd cmd, restic: Refactor and fix snapshot filtering
This turns snapshotFilterOptions from cmd into a restic.SnapshotFilter
type and makes restic.FindFilteredSnapshot and FindFilteredSnapshots
methods on that type. This fixes #4211 by ensuring that hosts and paths
are named struct fields instead of unnamed function arguments in long
lists of such.

Timestamp limits are also included in the new type. To avoid too much
pointer handling, the convention is that time zero means no limit.
That's January 1st, year 1, 00:00 UTC, which is so unlikely a date that
we can sacrifice it for simpler code.
2023-04-13 22:51:45 +02:00
Michael Eischer
74f7dd0b38 Make help for --verbose less confusing
The output is now
```
-v, --verbose                    be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)
```
instead of
```
-v, --verbose n                  be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)
```
2023-04-13 22:46:37 +02:00
Michael Eischer
21ad357c10 add linux/riscv64 builds 2023-04-13 22:45:35 +02:00
Alexander Neumann
7d4b7ad9cb Add version for 0.15.1 2023-01-30 20:43:47 +01:00
Alexander Neumann
a883bb6596 Generate CHANGELOG.md for 0.15.1 2023-01-30 20:43:15 +01:00
Alexander Neumann
91acef90b2 Prepare changelog for 0.15.1 2023-01-30 20:43:15 +01:00
Michael Eischer
b2b7727b31 Merge pull request #4178 from rawtaz/changelogs
Polish changelogs
2023-01-27 22:46:46 +01:00
Leo R. Lundgren
0e4c9a5421 Polish changelogs 2023-01-27 22:27:57 +01:00
Michael Eischer
49fa8fe6dd Merge pull request #4175 from fergus-dall/deadlock-fix
Fix deadlock in Lock.Stale
2023-01-26 22:45:13 +01:00
Michael Eischer
12f167ee79 Merge pull request #4167 from aneesh-n/progress-percent-eta-fix
ui/backup: Fix percent and eta in backup progress
2023-01-26 22:42:10 +01:00
Michael Eischer
bb018fbc3e Merge pull request #4163 from MichaelEischer/fix-windows-self-upgrade
self-upgrade: Fix handling of `--output` on windows
2023-01-26 22:40:36 +01:00
Michael Eischer
3b24e0ac55 Merge pull request #4154 from MichaelEischer/cleanup-prune-tests
deduplicate prune tests a bit
2023-01-26 22:39:15 +01:00
Fergus Dall
04da31af2b Fix deadlock in Lock.Stale
With debug logging enabled this method would take a lock and then
format the lock as a string. Since PR #4022 landed the string
formatting method has also taken the lock, so this deadlocks.

Instead just record the lock ID, as is done elsewhere.
2023-01-27 03:21:37 +11:00
Alexander Neumann
65923e9c26 Merge pull request #4152 from MichaelEischer/invalid-locks
Improve handling of invalid locks
2023-01-25 08:19:53 +01:00
Michael Eischer
b903081804 Merge pull request #4169 from restic/dependabot/go_modules/github.com/klauspost/compress-1.15.15
build(deps): bump github.com/klauspost/compress from 1.15.14 to 1.15.15
2023-01-23 22:35:52 +01:00
Michael Eischer
beb1e872cc Merge pull request #4170 from restic/dependabot/go_modules/cloud.google.com/go/storage-1.29.0
build(deps): bump cloud.google.com/go/storage from 1.28.1 to 1.29.0
2023-01-23 22:25:09 +01:00
Michael Eischer
db350c0430 Merge pull request #4171 from restic/dependabot/go_modules/google.golang.org/api-0.108.0
build(deps): bump google.golang.org/api from 0.107.0 to 0.108.0
2023-01-23 22:23:51 +01:00
dependabot[bot]
716a5dd20d build(deps): bump google.golang.org/api from 0.107.0 to 0.108.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.107.0 to 0.108.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.107.0...v0.108.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-23 02:23:51 +00:00
dependabot[bot]
dbd07ade98 build(deps): bump cloud.google.com/go/storage from 1.28.1 to 1.29.0
Bumps [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) from 1.28.1 to 1.29.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/storage/v1.28.1...spanner/v1.29.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/storage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-23 02:23:33 +00:00
dependabot[bot]
7adf1e5d37 build(deps): bump github.com/klauspost/compress from 1.15.14 to 1.15.15
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.15.14 to 1.15.15.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml)
- [Commits](https://github.com/klauspost/compress/compare/v1.15.14...v1.15.15)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-23 02:23:15 +00:00
Michael Eischer
8f94eb5420 add changelog for less strict lock handling 2023-01-22 15:54:07 +01:00
Michael Eischer
8aaba83719 add changelog for self-upgrade bug on windows 2023-01-22 15:39:42 +01:00
Michael Eischer
e16a6d4c50 self-update: add basic test for extractToFile 2023-01-22 15:39:42 +01:00
Michael Eischer
34e67e3510 self-update: Fix handling of --output on windows
The code always assumed that the upgrade happens in place. Thus writing
the upgrade to a separate file fails, when trying to remove the file
stored at that location.
2023-01-22 15:39:42 +01:00
Michael Eischer
c527c05590 add changelog for missing backup ETA 2023-01-22 15:32:55 +01:00
Aneesh Nireshwalia
ed23edeb62 ui/backup: Fix percent and eta in backup progress
Added missing call to scanFinished=true.
This was causing the percent and eta to never get
printed for backup progress even after the scan was finished.
2023-01-21 13:25:08 -07:00
Michael Eischer
0f398b82e3 Merge pull request #4145 from greatroar/index-encode
index: Optimize generatePackList
2023-01-21 01:01:12 +01:00
greatroar
99755c634b index: Optimize generatePackList
name                 old time/op    new time/op    delta
EncodeIndex/100-8      1.56ms ± 2%    1.48ms ± 3%   -5.37%  (p=0.000 n=10+10)
EncodeIndex/1000-8     14.5ms ± 2%    13.1ms ± 2%   -9.49%  (p=0.000 n=9+10)
EncodeIndex/10000-8     120ms ± 2%     116ms ± 2%   -3.58%  (p=0.000 n=10+10)

name                 old alloc/op   new alloc/op   delta
EncodeIndex/100-8       306kB ± 1%     275kB ± 1%  -10.28%  (p=0.000 n=10+10)
EncodeIndex/1000-8     3.69MB ±11%    2.88MB ± 5%  -22.07%  (p=0.000 n=10+9)
EncodeIndex/10000-8    35.9MB ±11%    31.9MB ±10%  -11.13%  (p=0.005 n=10+10)

name                 old allocs/op  new allocs/op  delta
EncodeIndex/100-8       3.39k ± 0%     2.39k ± 0%  -29.61%  (p=0.000 n=10+10)
EncodeIndex/1000-8      32.6k ± 0%     22.9k ± 0%  -29.63%  (p=0.000 n=10+9)
EncodeIndex/10000-8      326k ± 0%      229k ± 0%  -29.71%  (p=0.000 n=10+10)

The bulk of the allocation rate improvement comes from just removing the
debug.Log calls: every one of those copied a restic.ID to the heap.
2023-01-14 20:41:07 +01:00
Michael Eischer
f5f13f6648 Merge pull request #4153 from MichaelEischer/fix-quiet-prune
prune: Don't show "packs processed" for quiet runs
2023-01-14 20:11:34 +01:00
Michael Eischer
00216d54a1 deduplicate prune tests a bit 2023-01-14 19:05:45 +01:00
Michael Eischer
1f3f68b2c0 prune: Don't show "packs processed" for quiet runs 2023-01-14 18:48:32 +01:00
Michael Eischer
57acc769b4 lock: Ignore empty lock files
These may be left behind by backends which do not guarantee atomic
uploads.
2023-01-14 18:15:46 +01:00
Michael Eischer
20ad14e362 lock: add help message how to recover from invalid locks 2023-01-14 18:04:22 +01:00
Michael Eischer
c995b5be52 lock: cleanup error message
The error message is now `Fatal: unable to create lock in backend:
[...]` instead of `unable to create lock in backend: Fatal: [...]`.
2023-01-14 17:57:02 +01:00
Michael Eischer
1adf28a2b5 repository: properly return invalid data error in LoadUnpacked
The retry backend does not return the original error, if its execution
is interrupted by canceling the context. Thus, we have to manually
ensure that the invalid data error gets returned.

Additionally, use the retry backend for some of the repository tests, as
this is the configuration which will be used by restic.
2023-01-14 17:57:02 +01:00
Michael Eischer
6d9675c323 repository: cleanup error message on invalid data
The retry printed the filename twice:
```
Load(<lock/04804cba82>, 0, 0) returned error, retrying after 720.254544ms: load(<lock/04804cba82>): invalid data returned
```
now the warning has changed to
```
Load(<lock/04804cba82>, 0, 0) returned error, retrying after 720.254544ms: invalid data returned
```
2023-01-14 17:57:02 +01:00
Michael Eischer
551b31ce3c Merge pull request #4146 from MichaelEischer/update-blazer
Update blazer to fix `b2_download_file_by_name: 404`
2023-01-14 15:23:32 +01:00
Michael Eischer
ec99507e4c update blazer
This removes a stray warning, that was printed when checking the status
of non-existent files.
2023-01-14 14:56:25 +01:00
Michael Eischer
5f97f534b1 Merge pull request #4150 from restic/dependabot/go_modules/google.golang.org/api-0.107.0
build(deps): bump google.golang.org/api from 0.106.0 to 0.107.0
2023-01-14 14:55:15 +01:00
Michael Eischer
ed11bbd0e2 Merge pull request #4151 from restic/dependabot/go_modules/github.com/minio/minio-go/v7-7.0.47
build(deps): bump github.com/minio/minio-go/v7 from 7.0.46 to 7.0.47
2023-01-14 14:50:01 +01:00
dependabot[bot]
5bb9cb056d build(deps): bump github.com/minio/minio-go/v7 from 7.0.46 to 7.0.47
Bumps [github.com/minio/minio-go/v7](https://github.com/minio/minio-go) from 7.0.46 to 7.0.47.
- [Release notes](https://github.com/minio/minio-go/releases)
- [Commits](https://github.com/minio/minio-go/compare/v7.0.46...v7.0.47)

---
updated-dependencies:
- dependency-name: github.com/minio/minio-go/v7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-14 13:38:58 +00:00
dependabot[bot]
cd9bd22563 build(deps): bump google.golang.org/api from 0.106.0 to 0.107.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.106.0 to 0.107.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.106.0...v0.107.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-14 13:38:42 +00:00
Michael Eischer
ecc826ef7d Merge pull request #4149 from MichaelEischer/more-frequent-dependabot
CI: let dependabot check for updates weekly
2023-01-14 14:37:30 +01:00
Michael Eischer
fb43cbab49 Merge pull request #4129 from greatroar/cleanup
cache: Replace readCloser+LimitedReader by backend.LimitedReadCloser
2023-01-14 12:17:25 +01:00
Michael Eischer
41d31b1e27 Merge pull request #4116 from MichaelEischer/extract-progress-updater
ui/progress: Extract progress updater
2023-01-14 12:07:26 +01:00
Michael Eischer
f6ea5c5865 CI: let dependabot check for updates weekly 2023-01-14 12:02:42 +01:00
Michael Eischer
4a7a6b06af ui/backup: Use progress.Updater for progress updates 2023-01-14 01:20:43 +01:00
Michael Eischer
e499bbe3ae progress: extract progress updating into Updater struct
This allows reusing the code to create periodic progress updates.
2023-01-14 01:13:08 +01:00
Michael Eischer
52682b1c7b Merge pull request #4111 from MichaelEischer/extract-stdio-wrapper
backup: extract StdioWrapper from ProgressPrinters
2023-01-14 01:12:23 +01:00
Michael Eischer
c15b4bceae backup: extract StdioWrapper from ProgressPrinters
The StdioWrapper is not used at all by the ProgressPrinters. It is
called a bit earlier than previously. However, as the password prompt
directly accessed stdin/stdout this doesn't cause problems.
2023-01-14 00:58:13 +01:00
Alexander Neumann
74348be3fa Set development version for 0.15.0 2023-01-12 20:51:23 +01:00
greatroar
72922a79ed cache: Replace readCloser+LimitedReader by backend.LimitedReadCloser 2023-01-03 19:03:36 +01:00
95 changed files with 963 additions and 448 deletions

View File

@@ -4,10 +4,10 @@ updates:
- package-ecosystem: "gomod"
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
interval: "weekly"
# Dependencies listed in .github/workflows/*.yml
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"

View File

@@ -197,7 +197,7 @@ jobs:
matrix:
# run cross-compile in three batches parallel so the overall tests run faster
targets:
- "linux/386 linux/amd64 linux/arm linux/arm64 linux/ppc64le linux/mips linux/mipsle linux/mips64 linux/mips64le linux/s390x"
- "linux/386 linux/amd64 linux/arm linux/arm64 linux/ppc64le linux/mips linux/mipsle linux/mips64 linux/mips64le linux/riscv64 linux/s390x"
- "openbsd/386 openbsd/amd64 \
freebsd/386 freebsd/amd64 freebsd/arm \

View File

@@ -1,3 +1,167 @@
Changelog for restic 0.15.2 (2023-04-24)
=======================================
The following sections list the changes in restic 0.15.2 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Sec #4275: Update golang.org/x/net to address CVE-2022-41723
* Fix #2260: Sanitize filenames printed by `backup` during processing
* Fix #4211: Make `dump` interpret `--host` and `--path` correctly
* Fix #4239: Correct number of blocks reported in mount point
* Fix #4253: Minimize risk of spurious filesystem loops with `mount`
* Enh #4180: Add release binaries for riscv64 architecture on Linux
* Enh #4219: Upgrade Minio to version 7.0.49
Details
-------
* Security #4275: Update golang.org/x/net to address CVE-2022-41723
https://github.com/restic/restic/issues/4275
https://github.com/restic/restic/pull/4213
* Bugfix #2260: Sanitize filenames printed by `backup` during processing
The `backup` command would previously not sanitize the filenames it printed during
processing, potentially causing newlines or terminal control characters to mangle the
status output or even change the state of a terminal.
Filenames are now checked and quoted if they contain non-printable or non-Unicode
characters.
https://github.com/restic/restic/issues/2260
https://github.com/restic/restic/issues/4191
https://github.com/restic/restic/pull/4192
* Bugfix #4211: Make `dump` interpret `--host` and `--path` correctly
A regression in restic 0.15.0 caused `dump` to confuse its `--host=<host>` and
`--path=<path>` options: it looked for snapshots with paths called `<host>` from hosts
called `<path>`. It now treats the options as intended.
https://github.com/restic/restic/issues/4211
https://github.com/restic/restic/pull/4212
* Bugfix #4239: Correct number of blocks reported in mount point
Restic mount points reported an incorrect number of 512-byte (POSIX standard) blocks for
files and links due to a rounding bug. In particular, empty files were reported as taking one
block instead of zero.
The rounding is now fixed: the number of blocks reported is the file size (or link target size)
divided by 512 and rounded up to a whole number.
https://github.com/restic/restic/issues/4239
https://github.com/restic/restic/pull/4240
* Bugfix #4253: Minimize risk of spurious filesystem loops with `mount`
When a backup contains a directory that has the same name as its parent, say `a/b/b`, and the GNU
`find` command was run on this backup in a restic mount, `find` would refuse to traverse the
lowest `b` directory, instead printing `File system loop detected`. This was due to the way the
restic mount command generates inode numbers for directories in the mount point.
The rule for generating these inode numbers was changed in 0.15.0. It has now been changed again
to avoid this issue. A perfect rule does not exist, but the probability of this behavior
occurring is now extremely small.
When it does occur, the mount point is not broken, and scripts that traverse the mount point
should work as long as they don't rely on inode numbers for detecting filesystem loops.
https://github.com/restic/restic/issues/4253
https://github.com/restic/restic/pull/4255
* Enhancement #4180: Add release binaries for riscv64 architecture on Linux
Builds for the `riscv64` architecture on Linux are now included in the release binaries.
https://github.com/restic/restic/pull/4180
* Enhancement #4219: Upgrade Minio to version 7.0.49
The upgraded version now allows use of the `ap-southeast-4` region (Melbourne).
https://github.com/restic/restic/pull/4219
Changelog for restic 0.15.1 (2023-01-30)
=======================================
The following sections list the changes in restic 0.15.1 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #3750: Remove `b2_download_file_by_name: 404` warning from B2 backend
* Fix #4147: Make `prune --quiet` not print progress bar
* Fix #4163: Make `self-update --output` work with new filename on Windows
* Fix #4167: Add missing ETA in `backup` progress bar
* Enh #4143: Ignore empty lock files
Details
-------
* Bugfix #3750: Remove `b2_download_file_by_name: 404` warning from B2 backend
In some cases the B2 backend could print `b2_download_file_by_name: 404: : b2.b2err`
warnings. These are only debug messages and can be safely ignored.
Restic now uses an updated library for accessing B2, which removes the warning.
https://github.com/restic/restic/issues/3750
https://github.com/restic/restic/issues/4144
https://github.com/restic/restic/pull/4146
* Bugfix #4147: Make `prune --quiet` not print progress bar
A regression in restic 0.15.0 caused `prune --quiet` to show a progress bar while deciding how
to process each pack files. This has now been fixed.
https://github.com/restic/restic/issues/4147
https://github.com/restic/restic/pull/4153
* Bugfix #4163: Make `self-update --output` work with new filename on Windows
Since restic 0.14.0 the `self-update` command did not work when a custom output filename was
specified via the `--output` option. This has now been fixed.
As a workaround, either use an older restic version to run the self-update or create an empty
file with the output filename before updating e.g. using CMD:
`type nul > new-file.exe` `restic self-update --output new-file.exe`
https://github.com/restic/restic/pull/4163
https://forum.restic.net/t/self-update-windows-started-failing-after-release-of-0-15/5836
* Bugfix #4167: Add missing ETA in `backup` progress bar
A regression in restic 0.15.0 caused the ETA to be missing from the progress bar displayed by the
`backup` command. This has now been fixed.
https://github.com/restic/restic/pull/4167
* Enhancement #4143: Ignore empty lock files
With restic 0.15.0 the checks for stale locks became much stricter than before. In particular,
empty or unreadable locks were no longer silently ignored. This made restic to complain with
`Load(<lock/1234567812>, 0, 0) returned error, retrying after 552.330144ms:
load(<lock/1234567812>): invalid data returned` and fail in the end.
The error message is now clarified and the implementation changed to ignore empty lock files
which are sometimes created as the result of a failed uploads on some backends.
Please note that unreadable lock files still have to cleaned up manually. To do so, you can run
`restic unlock --remove-all` which removes all existing lock files. But first make sure that
no other restic process is currently using the repository.
https://github.com/restic/restic/issues/4143
https://github.com/restic/restic/pull/4152
Changelog for restic 0.15.0 (2023-01-12)
=======================================

View File

@@ -1 +1 @@
0.15.0
0.15.2

View File

@@ -0,0 +1,10 @@
Bugfix: Remove `b2_download_file_by_name: 404` warning from B2 backend
In some cases the B2 backend could print `b2_download_file_by_name: 404: :
b2.b2err` warnings. These are only debug messages and can be safely ignored.
Restic now uses an updated library for accessing B2, which removes the warning.
https://github.com/restic/restic/issues/3750
https://github.com/restic/restic/issues/4144
https://github.com/restic/restic/pull/4146

View File

@@ -0,0 +1,7 @@
Bugfix: Make `prune --quiet` not print progress bar
A regression in restic 0.15.0 caused `prune --quiet` to show a progress bar
while deciding how to process each pack files. This has now been fixed.
https://github.com/restic/restic/issues/4147
https://github.com/restic/restic/pull/4153

View File

@@ -0,0 +1,19 @@
Enhancement: Ignore empty lock files
With restic 0.15.0 the checks for stale locks became much stricter than before.
In particular, empty or unreadable locks were no longer silently ignored. This
made restic to complain with `Load(<lock/1234567812>, 0, 0) returned error,
retrying after 552.330144ms: load(<lock/1234567812>): invalid data returned`
and fail in the end.
The error message is now clarified and the implementation changed to ignore
empty lock files which are sometimes created as the result of a failed uploads
on some backends.
Please note that unreadable lock files still have to cleaned up manually. To do
so, you can run `restic unlock --remove-all` which removes all existing lock
files. But first make sure that no other restic process is currently using the
repository.
https://github.com/restic/restic/issues/4143
https://github.com/restic/restic/pull/4152

View File

@@ -0,0 +1,13 @@
Bugfix: Make `self-update --output` work with new filename on Windows
Since restic 0.14.0 the `self-update` command did not work when a custom output
filename was specified via the `--output` option. This has now been fixed.
As a workaround, either use an older restic version to run the self-update or
create an empty file with the output filename before updating e.g. using CMD:
`type nul > new-file.exe`
`restic self-update --output new-file.exe`
https://github.com/restic/restic/pull/4163
https://forum.restic.net/t/self-update-windows-started-failing-after-release-of-0-15/5836

View File

@@ -0,0 +1,6 @@
Bugfix: Add missing ETA in `backup` progress bar
A regression in restic 0.15.0 caused the ETA to be missing from the progress
bar displayed by the `backup` command. This has now been fixed.
https://github.com/restic/restic/pull/4167

View File

@@ -0,0 +1,12 @@
Bugfix: Sanitize filenames printed by `backup` during processing
The `backup` command would previously not sanitize the filenames it printed
during processing, potentially causing newlines or terminal control characters
to mangle the status output or even change the state of a terminal.
Filenames are now checked and quoted if they contain non-printable or
non-Unicode characters.
https://github.com/restic/restic/issues/2260
https://github.com/restic/restic/issues/4191
https://github.com/restic/restic/pull/4192

View File

@@ -0,0 +1,8 @@
Bugfix: Make `dump` interpret `--host` and `--path` correctly
A regression in restic 0.15.0 caused `dump` to confuse its `--host=<host>` and
`--path=<path>` options: it looked for snapshots with paths called `<host>`
from hosts called `<path>`. It now treats the options as intended.
https://github.com/restic/restic/issues/4211
https://github.com/restic/restic/pull/4212

View File

@@ -0,0 +1,11 @@
Bugfix: Correct number of blocks reported in mount point
Restic mount points reported an incorrect number of 512-byte (POSIX standard)
blocks for files and links due to a rounding bug. In particular, empty files
were reported as taking one block instead of zero.
The rounding is now fixed: the number of blocks reported is the file size
(or link target size) divided by 512 and rounded up to a whole number.
https://github.com/restic/restic/issues/4239
https://github.com/restic/restic/pull/4240

View File

@@ -0,0 +1,18 @@
Bugfix: Minimize risk of spurious filesystem loops with `mount`
When a backup contains a directory that has the same name as its parent, say
`a/b/b`, and the GNU `find` command was run on this backup in a restic mount,
`find` would refuse to traverse the lowest `b` directory, instead printing
`File system loop detected`. This was due to the way the restic mount command
generates inode numbers for directories in the mount point.
The rule for generating these inode numbers was changed in 0.15.0. It has
now been changed again to avoid this issue. A perfect rule does not exist,
but the probability of this behavior occurring is now extremely small.
When it does occur, the mount point is not broken, and scripts that traverse
the mount point should work as long as they don't rely on inode numbers for
detecting filesystem loops.
https://github.com/restic/restic/issues/4253
https://github.com/restic/restic/pull/4255

View File

@@ -0,0 +1,4 @@
Security: Update golang.org/x/net to address CVE-2022-41723
https://github.com/restic/restic/issues/4275
https://github.com/restic/restic/pull/4213

View File

@@ -0,0 +1,6 @@
Enhancement: Add release binaries for riscv64 architecture on Linux
Builds for the `riscv64` architecture on Linux are now included in the
release binaries.
https://github.com/restic/restic/pull/4180

View File

@@ -0,0 +1,5 @@
Enhancement: Upgrade Minio to version 7.0.49
The upgraded version now allows use of the `ap-southeast-4` region (Melbourne).
https://github.com/restic/restic/pull/4219

View File

@@ -25,6 +25,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/textfile"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/backup"
"github.com/restic/restic/internal/ui/termstatus"
)
@@ -71,6 +72,14 @@ Exit status is 3 if some source data could not be read (incomplete snapshot crea
term.Run(cancelCtx)
}()
// use the terminal for stdout/stderr
prevStdout, prevStderr := globalOptions.stdout, globalOptions.stderr
defer func() {
globalOptions.stdout, globalOptions.stderr = prevStdout, prevStderr
}()
stdioWrapper := ui.NewStdioWrapper(term)
globalOptions.stdout, globalOptions.stderr = stdioWrapper.Stdout(), stdioWrapper.Stderr()
return runBackup(ctx, backupOptions, globalOptions, term, args)
},
}
@@ -430,7 +439,13 @@ func findParentSnapshot(ctx context.Context, repo restic.Repository, opts Backup
if snName == "" {
snName = "latest"
}
sn, err := restic.FindFilteredSnapshot(ctx, repo.Backend(), repo, []string{opts.Host}, []restic.TagList{}, targets, &timeStampLimit, snName)
f := restic.SnapshotFilter{
Hosts: []string{opts.Host},
Paths: targets,
TimestampLimit: timeStampLimit,
}
sn, err := f.FindLatest(ctx, repo.Backend(), repo, snName)
// Snapshot not found is ok if no explicit parent was set
if opts.Parent == "" && errors.Is(err, restic.ErrNoSnapshotFound) {
err = nil
@@ -474,23 +489,12 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
}
progressReporter := backup.NewProgress(progressPrinter,
calculateProgressInterval(!gopts.Quiet, gopts.JSON))
defer progressReporter.Done()
if opts.DryRun {
repo.SetDryRun()
}
// use the terminal for stdout/stderr
prevStdout, prevStderr := gopts.stdout, gopts.stderr
defer func() {
gopts.stdout, gopts.stderr = prevStdout, prevStderr
}()
gopts.stdout, gopts.stderr = progressPrinter.Stdout(), progressPrinter.Stderr()
wg, wgCtx := errgroup.WithContext(ctx)
cancelCtx, cancel := context.WithCancel(wgCtx)
defer cancel()
wg.Go(func() error { progressReporter.Run(cancelCtx); return nil })
if !gopts.JSON {
progressPrinter.V("lock repository")
}
@@ -588,6 +592,10 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
targets = []string{filename}
}
wg, wgCtx := errgroup.WithContext(ctx)
cancelCtx, cancel := context.WithCancel(wgCtx)
defer cancel()
if !opts.NoScan {
sc := archiver.NewScanner(targetFS)
sc.SelectByName = selectByNameFilter

View File

@@ -39,7 +39,7 @@ new destination repository using the "init" command.
// CopyOptions bundles all options for the copy command.
type CopyOptions struct {
secondaryRepoOptions
snapshotFilterOptions
restic.SnapshotFilter
}
var copyOptions CopyOptions
@@ -49,7 +49,7 @@ func init() {
f := cmdCopy.Flags()
initSecondaryRepoOptions(f, &copyOptions.secondaryRepoOptions, "destination", "to copy snapshots from")
initMultiSnapshotFilterOptions(f, &copyOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(f, &copyOptions.SnapshotFilter, true)
}
func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string) error {
@@ -108,7 +108,7 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
}
dstSnapshotByOriginal := make(map[restic.ID][]*restic.Snapshot)
for sn := range FindFilteredSnapshots(ctx, dstSnapshotLister, dstRepo, opts.Hosts, opts.Tags, opts.Paths, nil) {
for sn := range FindFilteredSnapshots(ctx, dstSnapshotLister, dstRepo, &opts.SnapshotFilter, nil) {
if sn.Original != nil && !sn.Original.IsNull() {
dstSnapshotByOriginal[*sn.Original] = append(dstSnapshotByOriginal[*sn.Original], sn)
}
@@ -119,8 +119,7 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
// remember already processed trees across all snapshots
visitedTrees := restic.NewIDSet()
for sn := range FindFilteredSnapshots(ctx, srcSnapshotLister, srcRepo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, srcSnapshotLister, srcRepo, &opts.SnapshotFilter, args) {
// check whether the destination has a snapshot with the same persistent ID which has similar snapshot fields
srcOriginal := *sn.ID()
if sn.Original != nil {

View File

@@ -40,7 +40,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
// DumpOptions collects all options for the dump command.
type DumpOptions struct {
snapshotFilterOptions
restic.SnapshotFilter
Archive string
}
@@ -50,7 +50,7 @@ func init() {
cmdRoot.AddCommand(cmdDump)
flags := cmdDump.Flags()
initSingleSnapshotFilterOptions(flags, &dumpOptions.snapshotFilterOptions)
initSingleSnapshotFilter(flags, &dumpOptions.SnapshotFilter)
flags.StringVarP(&dumpOptions.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"")
}
@@ -139,7 +139,11 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
}
}
sn, err := restic.FindFilteredSnapshot(ctx, repo.Backend(), repo, opts.Paths, opts.Tags, opts.Hosts, nil, snapshotIDString)
sn, err := (&restic.SnapshotFilter{
Hosts: opts.Hosts,
Paths: opts.Paths,
Tags: opts.Tags,
}).FindLatest(ctx, repo.Backend(), repo, snapshotIDString)
if err != nil {
return errors.Fatalf("failed to find snapshot: %v", err)
}

View File

@@ -51,7 +51,7 @@ type FindOptions struct {
PackID, ShowPackID bool
CaseInsensitive bool
ListLong bool
snapshotFilterOptions
restic.SnapshotFilter
}
var findOptions FindOptions
@@ -70,7 +70,7 @@ func init() {
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")
initMultiSnapshotFilterOptions(f, &findOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(f, &findOptions.SnapshotFilter, true)
}
type findPattern struct {
@@ -618,7 +618,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
}
}
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, opts.Snapshots) {
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, opts.Snapshots) {
if f.blobIDs != nil || f.treeIDs != nil {
if err = f.findIDs(ctx, sn); err != nil && err.Error() != "OK" {
return err

View File

@@ -52,7 +52,7 @@ type ForgetOptions struct {
WithinYearly restic.Duration
KeepTags restic.TagLists
snapshotFilterOptions
restic.SnapshotFilter
Compact bool
// Grouping
@@ -81,7 +81,7 @@ func init() {
f.VarP(&forgetOptions.WithinYearly, "keep-within-yearly", "", "keep yearly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
initMultiSnapshotFilterOptions(f, &forgetOptions.snapshotFilterOptions, false)
initMultiSnapshotFilter(f, &forgetOptions.SnapshotFilter, false)
f.StringArrayVar(&forgetOptions.Hosts, "hostname", nil, "only consider snapshots with the given `hostname` (can be specified multiple times)")
err := f.MarkDeprecated("hostname", "use --host")
if err != nil {
@@ -126,7 +126,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.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, &opts.SnapshotFilter, args) {
snapshots = append(snapshots, sn)
}

View File

@@ -49,7 +49,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
// LsOptions collects all options for the ls command.
type LsOptions struct {
ListLong bool
snapshotFilterOptions
restic.SnapshotFilter
Recursive bool
}
@@ -59,7 +59,7 @@ func init() {
cmdRoot.AddCommand(cmdLs)
flags := cmdLs.Flags()
initSingleSnapshotFilterOptions(flags, &lsOptions.snapshotFilterOptions)
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")
}
@@ -210,7 +210,11 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
}
}
sn, err := restic.FindFilteredSnapshot(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, nil, args[0])
sn, err := (&restic.SnapshotFilter{
Hosts: opts.Hosts,
Paths: opts.Paths,
Tags: opts.Tags,
}).FindLatest(ctx, snapshotLister, repo, args[0])
if err != nil {
return err
}

View File

@@ -77,7 +77,7 @@ type MountOptions struct {
OwnerRoot bool
AllowOther bool
NoDefaultPermissions bool
snapshotFilterOptions
restic.SnapshotFilter
TimeTemplate string
PathTemplates []string
}
@@ -92,7 +92,7 @@ func init() {
mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
mountFlags.BoolVar(&mountOptions.NoDefaultPermissions, "no-default-permissions", false, "for 'allow-other', ignore Unix permissions and allow users to read all snapshot files")
initMultiSnapshotFilterOptions(mountFlags, &mountOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(mountFlags, &mountOptions.SnapshotFilter, true)
mountFlags.StringArrayVar(&mountOptions.PathTemplates, "path-template", nil, "set `template` for path names (can be specified multiple times)")
mountFlags.StringVar(&mountOptions.TimeTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
@@ -180,9 +180,7 @@ func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args
cfg := fuse.Config{
OwnerIsRoot: opts.OwnerRoot,
Hosts: opts.Hosts,
Tags: opts.Tags,
Paths: opts.Paths,
Filter: opts.SnapshotFilter,
TimeTemplate: opts.TimeTemplate,
PathTemplates: opts.PathTemplates,
}

View File

@@ -473,7 +473,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, repo restic.Reposi
}
// loop over all packs and decide what to do
bar := newProgressMax(quiet, uint64(len(indexPack)), "packs processed")
bar := newProgressMax(!quiet, uint64(len(indexPack)), "packs processed")
err := repo.List(ctx, restic.PackFile, func(id restic.ID, packSize int64) error {
p, ok := indexPack[id]
if !ok {

View File

@@ -42,7 +42,7 @@ type RestoreOptions struct {
Include []string
InsensitiveInclude []string
Target string
snapshotFilterOptions
restic.SnapshotFilter
Sparse bool
Verify bool
}
@@ -59,7 +59,7 @@ func init() {
flags.StringArrayVar(&restoreOptions.InsensitiveInclude, "iinclude", nil, "same as `--include` but ignores the casing of filenames")
flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
initSingleSnapshotFilterOptions(flags, &restoreOptions.snapshotFilterOptions)
initSingleSnapshotFilter(flags, &restoreOptions.SnapshotFilter)
flags.BoolVar(&restoreOptions.Sparse, "sparse", false, "restore files as sparse")
flags.BoolVar(&restoreOptions.Verify, "verify", false, "verify restored files content")
}
@@ -131,7 +131,11 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, a
}
}
sn, err := restic.FindFilteredSnapshot(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, nil, snapshotIDString)
sn, err := (&restic.SnapshotFilter{
Hosts: opts.Hosts,
Paths: opts.Paths,
Tags: opts.Tags,
}).FindLatest(ctx, repo.Backend(), repo, snapshotIDString)
if err != nil {
return errors.Fatalf("failed to find snapshot: %v", err)
}

View File

@@ -51,7 +51,7 @@ type RewriteOptions struct {
Forget bool
DryRun bool
snapshotFilterOptions
restic.SnapshotFilter
excludePatternOptions
}
@@ -64,7 +64,7 @@ func init() {
f.BoolVarP(&rewriteOptions.Forget, "forget", "", false, "remove original snapshots after creating new ones")
f.BoolVarP(&rewriteOptions.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done")
initMultiSnapshotFilterOptions(f, &rewriteOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(f, &rewriteOptions.SnapshotFilter, true)
initExcludePatternOptions(f, &rewriteOptions.excludePatternOptions)
}
@@ -186,7 +186,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
}
changedCount := 0
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, args) {
Verbosef("\nsnapshot %s of %v at %s)\n", sn.ID().Str(), sn.Paths, sn.Time)
changed, err := rewriteSnapshot(ctx, repo, sn, opts)
if err != nil {

View File

@@ -32,7 +32,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
// SnapshotOptions bundles all options for the snapshots command.
type SnapshotOptions struct {
snapshotFilterOptions
restic.SnapshotFilter
Compact bool
Last bool // This option should be removed in favour of Latest.
Latest int
@@ -45,7 +45,7 @@ func init() {
cmdRoot.AddCommand(cmdSnapshots)
f := cmdSnapshots.Flags()
initMultiSnapshotFilterOptions(f, &snapshotOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(f, &snapshotOptions.SnapshotFilter, true)
f.BoolVarP(&snapshotOptions.Compact, "compact", "c", false, "use compact output format")
f.BoolVar(&snapshotOptions.Last, "last", false, "only show the last snapshot for each host and path")
err := f.MarkDeprecated("last", "use --latest 1")
@@ -73,7 +73,7 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions
}
var snapshots restic.Snapshots
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, &opts.SnapshotFilter, args) {
snapshots = append(snapshots, sn)
}
snapshotGroups, grouped, err := restic.GroupSnapshots(snapshots, opts.GroupBy)

View File

@@ -58,7 +58,7 @@ type StatsOptions struct {
// the mode of counting to perform (see consts for available modes)
countMode string
snapshotFilterOptions
restic.SnapshotFilter
}
var statsOptions StatsOptions
@@ -67,7 +67,7 @@ func init() {
cmdRoot.AddCommand(cmdStats)
f := cmdStats.Flags()
f.StringVar(&statsOptions.countMode, "mode", countModeRestoreSize, "counting mode: restore-size (default), files-by-contents, blobs-per-file or raw-data")
initMultiSnapshotFilterOptions(f, &statsOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(f, &statsOptions.SnapshotFilter, true)
}
func runStats(ctx context.Context, gopts GlobalOptions, args []string) error {
@@ -111,7 +111,7 @@ func runStats(ctx context.Context, gopts GlobalOptions, args []string) error {
SnapshotsCount: 0,
}
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, statsOptions.Hosts, statsOptions.Tags, statsOptions.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &statsOptions.SnapshotFilter, args) {
err = statsWalkSnapshot(ctx, sn, repo, stats)
if err != nil {
return fmt.Errorf("error walking snapshot: %v", err)

View File

@@ -35,7 +35,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
// TagOptions bundles all options for the 'tag' command.
type TagOptions struct {
snapshotFilterOptions
restic.SnapshotFilter
SetTags restic.TagLists
AddTags restic.TagLists
RemoveTags restic.TagLists
@@ -50,7 +50,7 @@ func init() {
tagFlags.Var(&tagOptions.SetTags, "set", "`tags` which will replace the existing tags in the format `tag[,tag,...]` (can be given multiple times)")
tagFlags.Var(&tagOptions.AddTags, "add", "`tags` which will be added to the existing tags in the format `tag[,tag,...]` (can be given multiple times)")
tagFlags.Var(&tagOptions.RemoveTags, "remove", "`tags` which will be removed from the existing tags in the format `tag[,tag,...]` (can be given multiple times)")
initMultiSnapshotFilterOptions(tagFlags, &tagOptions.snapshotFilterOptions, true)
initMultiSnapshotFilter(tagFlags, &tagOptions.SnapshotFilter, true)
}
func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Snapshot, setTags, addTags, removeTags []string) (bool, error) {
@@ -119,7 +119,7 @@ func runTag(ctx context.Context, opts TagOptions, gopts GlobalOptions, args []st
}
changeCnt := 0
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, args) {
for sn := range FindFilteredSnapshots(ctx, repo.Backend(), repo, &opts.SnapshotFilter, args) {
changed, err := changeTags(ctx, repo, sn, opts.SetTags.Flatten(), opts.AddTags.Flatten(), opts.RemoveTags.Flatten())
if err != nil {
Warnf("unable to modify the tags for snapshot ID %q, ignoring: %v\n", sn.ID(), err)

View File

@@ -8,34 +8,28 @@ import (
"github.com/spf13/pflag"
)
type snapshotFilterOptions struct {
Hosts []string
Tags restic.TagLists
Paths []string
}
// initMultiSnapshotFilterOptions is used for commands that work on multiple snapshots
// initMultiSnapshotFilter is used for commands that work on multiple snapshots
// MUST be combined with restic.FindFilteredSnapshots or FindFilteredSnapshots
func initMultiSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilterOptions, addHostShorthand bool) {
func initMultiSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter, addHostShorthand bool) {
hostShorthand := "H"
if !addHostShorthand {
hostShorthand = ""
}
flags.StringArrayVarP(&options.Hosts, "host", hostShorthand, nil, "only consider snapshots for this `host` (can be specified multiple times)")
flags.Var(&options.Tags, "tag", "only consider snapshots including `tag[,tag,...]` (can be specified multiple times)")
flags.StringArrayVar(&options.Paths, "path", nil, "only consider snapshots including this (absolute) `path` (can be specified multiple times)")
flags.StringArrayVarP(&filt.Hosts, "host", hostShorthand, nil, "only consider snapshots for this `host` (can be specified multiple times)")
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]` (can be specified multiple times)")
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path` (can be specified multiple times)")
}
// initSingleSnapshotFilterOptions is used for commands that work on a single snapshot
// initSingleSnapshotFilter is used for commands that work on a single snapshot
// MUST be combined with restic.FindFilteredSnapshot
func initSingleSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilterOptions) {
flags.StringArrayVarP(&options.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times)")
flags.Var(&options.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
flags.StringArrayVar(&options.Paths, "path", nil, "only consider snapshots including this (absolute) `path`, when snapshot ID \"latest\" is given (can be specified multiple times)")
func initSingleSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter) {
flags.StringArrayVarP(&filt.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times)")
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path`, when snapshot ID \"latest\" is given (can be specified multiple times)")
}
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, hosts []string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, f *restic.SnapshotFilter, snapshotIDs []string) <-chan *restic.Snapshot {
out := make(chan *restic.Snapshot)
go func() {
defer close(out)
@@ -45,7 +39,7 @@ func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.
return
}
err = restic.FindFilteredSnapshots(ctx, be, loader, hosts, tags, paths, snapshotIDs, func(id string, sn *restic.Snapshot, err error) error {
err = f.FindAll(ctx, be, loader, snapshotIDs, func(id string, sn *restic.Snapshot, err error) error {
if err != nil {
Warnf("Ignoring %q: %v\n", id, err)
} else {

View File

@@ -42,7 +42,7 @@ import (
"golang.org/x/term"
)
var version = "0.15.0"
var version = "0.15.2"
// TimeFormat is the format used for all timestamps printed by restic.
const TimeFormat = "2006-01-02 15:04:05"
@@ -112,7 +112,8 @@ func init() {
f.StringVarP(&globalOptions.KeyHint, "key-hint", "", "", "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)")
f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", "", "shell `command` to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)")
f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report")
f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify multiple times or a level using --verbose=`n`, max level/times is 2)")
// use empty paremeter name as `-v, --verbose n` instead of the correct `--verbose=n` is confusing
f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)")
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repository, this allows some operations on read-only repositories")
f.BoolVarP(&globalOptions.JSON, "json", "", false, "set output mode to JSON for commands that support it")
f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache `directory`. (default: use system default cache directory)")

View File

@@ -106,7 +106,7 @@ func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID res
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
opts := RestoreOptions{
Target: dir,
snapshotFilterOptions: snapshotFilterOptions{
SnapshotFilter: restic.SnapshotFilter{
Hosts: hosts,
Paths: paths,
},
@@ -1623,10 +1623,7 @@ func testPruneVariants(t *testing.T, unsafeNoSpaceRecovery bool) {
})
}
func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
func createPrunableRepo(t *testing.T, env *testEnvironment) {
testSetupBackupData(t, env)
opts := BackupOptions{}
@@ -1644,6 +1641,13 @@ func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
testRunForgetJSON(t, env.gopts)
testRunForget(t, env.gopts, firstSnapshot[0].String())
}
func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
createPrunableRepo(t, env)
testRunPrune(t, env.gopts, pruneOpts)
rtest.OK(t, runCheck(context.TODO(), checkOpts, env.gopts, nil))
}
@@ -1826,27 +1830,10 @@ func TestListOnce(t *testing.T) {
env.gopts.backendTestHook = func(r restic.Backend) (restic.Backend, error) {
return newListOnceBackend(r), nil
}
pruneOpts := PruneOptions{MaxUnused: "0"}
checkOpts := CheckOptions{ReadData: true, CheckUnused: true}
testSetupBackupData(t, env)
opts := BackupOptions{}
testRunBackup(t, "", []string{filepath.Join(env.testdata, "0", "0", "9")}, opts, env.gopts)
firstSnapshot := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(firstSnapshot) == 1,
"expected one snapshot, got %v", firstSnapshot)
testRunBackup(t, "", []string{filepath.Join(env.testdata, "0", "0", "9", "2")}, opts, env.gopts)
testRunBackup(t, "", []string{filepath.Join(env.testdata, "0", "0", "9", "3")}, opts, env.gopts)
snapshotIDs := testRunList(t, "snapshots", env.gopts)
rtest.Assert(t, len(snapshotIDs) == 3,
"expected 3 snapshot, got %v", snapshotIDs)
testRunForgetJSON(t, env.gopts)
testRunForget(t, env.gopts, firstSnapshot[0].String())
createPrunableRepo(t, env)
testRunPrune(t, env.gopts, pruneOpts)
rtest.OK(t, runCheck(context.TODO(), checkOpts, env.gopts, nil))
@@ -2209,7 +2196,7 @@ func TestFindListOnce(t *testing.T) {
snapshotIDs := restic.NewIDSet()
// specify the two oldest snapshots explicitly and use "latest" to reference the newest one
for sn := range FindFilteredSnapshots(context.TODO(), repo.Backend(), repo, nil, nil, nil, []string{
for sn := range FindFilteredSnapshots(context.TODO(), repo.Backend(), repo, &restic.SnapshotFilter{}, []string{
secondSnapshot[0].String(),
secondSnapshot[1].String()[:8],
"latest",

View File

@@ -2,11 +2,11 @@ package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
)
@@ -44,8 +44,11 @@ func lockRepository(ctx context.Context, repo restic.Repository, exclusive bool)
}
lock, err := lockFn(ctx, repo)
if restic.IsInvalidLock(err) {
return nil, ctx, errors.Fatalf("%v\n\nthe `unlock --remove-all` command can be used to remove invalid locks. Make sure that no other restic process is accessing the repository when running the command", err)
}
if err != nil {
return nil, ctx, fmt.Errorf("unable to create lock in backend: %w", err)
return nil, ctx, errors.Fatalf("unable to create lock in backend: %v", err)
}
debug.Log("create lock %p (exclusive %v)", lock, exclusive)

View File

@@ -37,7 +37,7 @@ func newProgressMax(show bool, max uint64, description string) *progress.Counter
interval := calculateProgressInterval(show, false)
canUpdateStatus := stdoutCanUpdateStatus()
return progress.New(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
return progress.NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
var status string
if max == 0 {
status = fmt.Sprintf("[%s] %d %s",

View File

@@ -205,7 +205,7 @@ Exit status is 3 if some source data could not be read (incomplete snapshot crea
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -118,7 +118,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -106,7 +106,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -123,7 +123,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -147,7 +147,7 @@ new destination repository using the "init" command.
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -126,7 +126,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -129,7 +129,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -151,7 +151,7 @@ It can also be used to search for restic blobs or trees for troubleshooting.
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH EXAMPLE

View File

@@ -217,7 +217,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -127,7 +127,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -134,7 +134,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -118,7 +118,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -106,7 +106,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -141,7 +141,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -112,7 +112,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -190,7 +190,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -135,7 +135,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -111,7 +111,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -108,7 +108,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -151,7 +151,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -159,7 +159,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -113,7 +113,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -130,7 +130,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -152,7 +152,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -137,7 +137,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -110,7 +110,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -107,7 +107,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -100,7 +100,7 @@ directories in an encrypted repository stored on different backends.
.PP
\fB-v\fP, \fB--verbose\fP[=0]
be verbose (specify multiple times or a level using --verbose=\fB\fCn\fR, max level/times is 2)
be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)
.SH SEE ALSO

View File

@@ -67,7 +67,7 @@ Usage help is available:
-r, --repo repository repository to backup to or restore from (default: $RESTIC_REPOSITORY)
--repository-file file file to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
--tls-client-cert file path to a file containing PEM encoded TLS client certificate and private key
-v, --verbose n be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)
-v, --verbose be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)
Use "restic [command] --help" for more information about a command.
@@ -142,7 +142,7 @@ command:
-r, --repo repository repository to backup to or restore from (default: $RESTIC_REPOSITORY)
--repository-file file file to read the repository location from (default: $RESTIC_REPOSITORY_FILE)
--tls-client-cert file path to a file containing PEM encoded TLS client certificate and private key
-v, --verbose n be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)
-v, --verbose be verbose (specify multiple times or a level using --verbose=n, max level/times is 2)
Subcommands that support showing progress information such as ``backup``,
``check`` and ``prune`` will do so unless the quiet flag ``-q`` or

46
go.mod
View File

@@ -1,8 +1,8 @@
module github.com/restic/restic
require (
cloud.google.com/go/storage v1.28.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0
cloud.google.com/go/storage v1.30.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.5.1
github.com/anacrolix/fuse v0.2.0
github.com/cenkalti/backoff/v4 v4.2.0
@@ -12,9 +12,9 @@ require (
github.com/google/go-cmp v0.5.9
github.com/hashicorp/golang-lru/v2 v2.0.1
github.com/juju/ratelimit v1.0.2
github.com/klauspost/compress v1.15.14
github.com/kurin/blazer v0.5.4-0.20211030221322-ba894c124ac6
github.com/minio/minio-go/v7 v7.0.46
github.com/klauspost/compress v1.16.0
github.com/kurin/blazer v0.5.4-0.20230113224640-3887e1ec64b5
github.com/minio/minio-go/v7 v7.0.50
github.com/minio/sha256-simd v1.0.0
github.com/ncw/swift/v2 v2.0.1
github.com/pkg/errors v0.9.1
@@ -24,35 +24,35 @@ require (
github.com/restic/chunker v0.4.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.5.0
golang.org/x/net v0.5.0
golang.org/x/oauth2 v0.4.0
golang.org/x/crypto v0.7.0
golang.org/x/net v0.8.0
golang.org/x/oauth2 v0.6.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.4.0
golang.org/x/term v0.4.0
golang.org/x/text v0.6.0
google.golang.org/api v0.106.0
golang.org/x/sys v0.6.0
golang.org/x/term v0.6.0
golang.org/x/text v0.8.0
google.golang.org/api v0.116.0
)
require (
cloud.google.com/go v0.108.0 // indirect
cloud.google.com/go/compute v1.15.1 // indirect
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.10.0 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dnaeon/go-vcr v1.2.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -63,9 +63,9 @@ require (
go.opencensus.io v0.24.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.52.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

96
go.sum
View File

@@ -1,17 +1,17 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.108.0 h1:xntQwnfn8oHGX0crLVinvHM+AhXvi3QHQIEcX/2hiWk=
cloud.google.com/go v0.108.0/go.mod h1:lNUfQqusBJp0bgAg6qrHgYFYbTB+dOiob1itwnlD33Q=
cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE=
cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/iam v0.10.0 h1:fpP/gByFs6US1ma53v7VxhvbJpO2Aapng6wabJ99MuI=
cloud.google.com/go/iam v0.10.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0=
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
@@ -39,8 +39,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/elithrar/simple-scrypt v1.3.0 h1:KIlOlxdoQf9JWKl5lMAJ28SY2URB0XTRDn2TckyzAZg=
@@ -70,8 +70,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -82,17 +82,17 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b h1:8htHrh2bw9c7Idkb7YNac+ZpTqLMjRpI+FWu51ltaQc=
github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
@@ -103,21 +103,21 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc=
github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kurin/blazer v0.5.4-0.20211030221322-ba894c124ac6 h1:nz7i1au+nDzgExfqW5Zl6q85XNTvYoGnM5DHiQC0yYs=
github.com/kurin/blazer v0.5.4-0.20211030221322-ba894c124ac6/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/kurin/blazer v0.5.4-0.20230113224640-3887e1ec64b5 h1:OUlGa6AAolmjyPtILbMJ8vHayz5wd4wBUloheGcMhfA=
github.com/kurin/blazer v0.5.4-0.20230113224640-3887e1ec64b5/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.46 h1:Vo3tNmNXuj7ME5qrvN4iadO7b4mzu/RSFdUkUhaPldk=
github.com/minio/minio-go/v7 v7.0.46/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/minio-go/v7 v7.0.50 h1:4IL4V8m/kI90ZL6GupCARZVrBv8/XrcKcJhaJ3iz68k=
github.com/minio/minio-go/v7 v7.0.50/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -172,8 +172,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -189,11 +189,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -214,17 +214,17 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -237,8 +237,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.106.0 h1:ffmW0faWCwKkpbbtvlY/K/8fUl+JKvNS5CVzRoyfCv8=
google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/api v0.116.0 h1:09tOPVufPwfm5W4aA8EizGHJ7BcoRDsIareM2a15gO4=
google.golang.org/api v0.116.0/go.mod h1:9cD4/t6uvd9naoEJFA+M96d0IuB6BqFuyhpw68+mRGg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -246,15 +246,15 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 h1:0BOZf6qNozI3pkN3fJLwNubheHJYHhMh91GRFOWWK08=
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -266,8 +266,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=

View File

@@ -225,7 +225,7 @@ var defaultBuildTargets = map[string][]string{
"aix": {"ppc64"},
"darwin": {"amd64", "arm64"},
"freebsd": {"386", "amd64", "arm"},
"linux": {"386", "amd64", "arm", "arm64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "s390x"},
"linux": {"386", "amd64", "arm", "arm64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "riscv64", "s390x"},
"netbsd": {"386", "amd64"},
"openbsd": {"386", "amd64"},
"windows": {"386", "amd64"},

View File

@@ -7,6 +7,7 @@ import (
"runtime"
"github.com/pkg/errors"
"github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/crypto"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/fs"
@@ -30,11 +31,6 @@ func (c *Cache) canBeCached(t restic.FileType) bool {
return ok
}
type readCloser struct {
io.Reader
io.Closer
}
// Load returns a reader that yields the contents of the file with the
// given handle. rd must be closed after use. If an error is returned, the
// ReadCloser is nil.
@@ -75,12 +71,10 @@ func (c *Cache) load(h restic.Handle, length int, offset int64) (io.ReadCloser,
}
}
rd := readCloser{Reader: f, Closer: f}
if length > 0 {
rd.Reader = io.LimitReader(f, int64(length))
if length <= 0 {
return f, nil
}
return rd, nil
return backend.LimitReadCloser(f, int64(length)), nil
}
// Save saves a file in the cache.

View File

@@ -50,7 +50,7 @@ func (f *file) Attr(ctx context.Context, a *fuse.Attr) error {
a.Inode = f.inode
a.Mode = f.node.Mode
a.Size = f.node.Size
a.Blocks = (f.node.Size / blockSize) + 1
a.Blocks = (f.node.Size + blockSize - 1) / blockSize
a.BlockSize = blockSize
a.Nlink = uint32(f.node.Links)

View File

@@ -8,6 +8,7 @@ import (
"context"
"math/rand"
"os"
"strings"
"testing"
"time"
@@ -216,6 +217,37 @@ func testTopUIDGID(t *testing.T, cfg Config, repo restic.Repository, uid, gid ui
rtest.Equals(t, uint32(0), attr.Gid)
}
// Test reporting of fuse.Attr.Blocks in multiples of 512.
func TestBlocks(t *testing.T) {
root := &Root{}
for _, c := range []struct {
size, blocks uint64
}{
{0, 0},
{1, 1},
{511, 1},
{512, 1},
{513, 2},
{1024, 2},
{1025, 3},
{41253, 81},
} {
target := strings.Repeat("x", int(c.size))
for _, n := range []fs.Node{
&file{root: root, node: &restic.Node{Size: uint64(c.size)}},
&link{root: root, node: &restic.Node{LinkTarget: target}},
&snapshotLink{root: root, snapshot: &restic.Snapshot{}, target: target},
} {
var a fuse.Attr
err := n.Attr(context.TODO(), &a)
rtest.OK(t, err)
rtest.Equals(t, c.blocks, a.Blocks)
}
}
}
func TestInodeFromNode(t *testing.T) {
node := &restic.Node{Name: "foo.txt", Type: "chardev", Links: 2}
ino1 := inodeFromNode(1, node)
@@ -226,6 +258,17 @@ func TestInodeFromNode(t *testing.T) {
ino1 = inodeFromNode(1, node)
ino2 = inodeFromNode(2, node)
rtest.Assert(t, ino1 != ino2, "same inode %d but different parent", ino1)
// Regression test: in a path a/b/b, the grandchild should not get the
// same inode as the grandparent.
a := &restic.Node{Name: "a", Type: "dir", Links: 2}
ab := &restic.Node{Name: "b", Type: "dir", Links: 2}
abb := &restic.Node{Name: "b", Type: "dir", Links: 2}
inoA := inodeFromNode(1, a)
inoAb := inodeFromNode(inoA, ab)
inoAbb := inodeFromNode(inoAb, abb)
rtest.Assert(t, inoA != inoAb, "inode(a/b) = inode(a)")
rtest.Assert(t, inoA != inoAbb, "inode(a/b/b) = inode(a)")
}
var sink uint64

View File

@@ -10,9 +10,11 @@ import (
"github.com/restic/restic/internal/restic"
)
const prime = 11400714785074694791 // prime1 from xxhash.
// inodeFromName generates an inode number for a file in a meta dir.
func inodeFromName(parent uint64, name string) uint64 {
inode := parent ^ xxhash.Sum64String(cleanupNodeName(name))
inode := prime*parent ^ xxhash.Sum64String(cleanupNodeName(name))
// Inode 0 is invalid and 1 is the root. Remap those.
if inode < 2 {
@@ -33,7 +35,7 @@ func inodeFromNode(parent uint64, node *restic.Node) (inode uint64) {
} else {
// Else, use the name and the parent inode.
// node.{DeviceID,Inode} may not even be reliable.
inode = parent ^ xxhash.Sum64String(cleanupNodeName(node.Name))
inode = prime*parent ^ xxhash.Sum64String(cleanupNodeName(node.Name))
}
// Inode 0 is invalid and 1 is the root. Remap those.

View File

@@ -42,7 +42,7 @@ func (l *link) Attr(ctx context.Context, a *fuse.Attr) error {
a.Nlink = uint32(l.node.Links)
a.Size = uint64(len(l.node.LinkTarget))
a.Blocks = 1 + a.Size/blockSize
a.Blocks = (a.Size + blockSize - 1) / blockSize
return nil
}

View File

@@ -16,9 +16,7 @@ import (
// Config holds settings for the fuse mount.
type Config struct {
OwnerIsRoot bool
Hosts []string
Tags []restic.TagList
Paths []string
Filter restic.SnapshotFilter
TimeTemplate string
PathTemplates []string
}

View File

@@ -142,7 +142,7 @@ func (l *snapshotLink) Attr(ctx context.Context, a *fuse.Attr) error {
a.Inode = l.inode
a.Mode = os.ModeSymlink | 0777
a.Size = uint64(len(l.target))
a.Blocks = 1 + a.Size/blockSize
a.Blocks = (a.Size + blockSize - 1) / blockSize
a.Uid = l.root.uid
a.Gid = l.root.gid
a.Atime = l.snapshot.Time

View File

@@ -295,7 +295,7 @@ func (d *SnapshotsDirStructure) updateSnapshots(ctx context.Context) error {
}
var snapshots restic.Snapshots
err := restic.FindFilteredSnapshots(ctx, d.root.repo.Backend(), d.root.repo, d.root.cfg.Hosts, d.root.cfg.Tags, d.root.cfg.Paths, nil, func(id string, sn *restic.Snapshot, err error) error {
err := d.root.cfg.Filter.FindAll(ctx, d.root.repo.Backend(), d.root.repo, nil, func(id string, sn *restic.Snapshot, err error) error {
if sn != nil {
snapshots = append(snapshots, sn)
}

View File

@@ -317,9 +317,9 @@ type blobJSON struct {
}
// generatePackList returns a list of packs.
func (idx *Index) generatePackList() ([]*packJSON, error) {
list := []*packJSON{}
packs := make(map[restic.ID]*packJSON)
func (idx *Index) generatePackList() ([]packJSON, error) {
list := make([]packJSON, 0, len(idx.packs))
packs := make(map[restic.ID]int, len(list)) // Maps to index in list.
for typ := range idx.byType {
m := &idx.byType[typ]
@@ -329,18 +329,13 @@ func (idx *Index) generatePackList() ([]*packJSON, error) {
panic("null pack id")
}
debug.Log("handle blob %v", e.id)
// see if pack is already in map
p, ok := packs[packID]
i, ok := packs[packID]
if !ok {
// else create new pack
p = &packJSON{ID: packID}
// and append it to the list and map
list = append(list, p)
packs[p.ID] = p
i = len(list)
list = append(list, packJSON{ID: packID})
packs[packID] = i
}
p := &list[i]
// add blob
p.Blobs = append(p.Blobs, blobJSON{
@@ -355,14 +350,12 @@ func (idx *Index) generatePackList() ([]*packJSON, error) {
})
}
debug.Log("done")
return list, nil
}
type jsonIndex struct {
Supersedes restic.IDs `json:"supersedes,omitempty"`
Packs []*packJSON `json:"packs"`
Supersedes restic.IDs `json:"supersedes,omitempty"`
Packs []packJSON `json:"packs"`
}
// Encode writes the JSON serialization of the index to the writer w.

View File

@@ -3,6 +3,7 @@ package index_test
import (
"bytes"
"context"
"fmt"
"math/rand"
"sync"
"testing"
@@ -405,6 +406,26 @@ func BenchmarkDecodeIndexParallel(b *testing.B) {
})
}
func BenchmarkEncodeIndex(b *testing.B) {
for _, n := range []int{100, 1000, 10000} {
idx, _ := createRandomIndex(rand.New(rand.NewSource(0)), n)
b.Run(fmt.Sprint(n), func(b *testing.B) {
buf := new(bytes.Buffer)
err := idx.Encode(buf)
rtest.OK(b, err)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
buf.Reset()
_ = idx.Encode(buf)
}
})
}
}
func TestIndexUnserializeOld(t *testing.T) {
idx, oldFormat, err := index.DecodeIndex(docOldExample, restic.NewRandomID())
rtest.OK(t, err)

View File

@@ -188,6 +188,7 @@ func (r *Repository) LoadUnpacked(ctx context.Context, t restic.FileType, id res
h := restic.Handle{Type: t, Name: id.String()}
retriedInvalidData := false
var dataErr error
err := r.be.Load(ctx, h, 0, 0, func(rd io.Reader) error {
// make sure this call is idempotent, in case an error occurs
wr := bytes.NewBuffer(buf[:0])
@@ -202,13 +203,20 @@ func (r *Repository) LoadUnpacked(ctx context.Context, t restic.FileType, id res
if !retriedInvalidData {
retriedInvalidData = true
} else {
// with a canceled context there is not guarantee which error will
// be returned by `be.Load`.
dataErr = fmt.Errorf("load(%v): %w", h, restic.ErrInvalidData)
cancel()
}
return errors.Errorf("load(%v): invalid data returned", h)
return restic.ErrInvalidData
}
return nil
})
if dataErr != nil {
return nil, dataErr
}
if err != nil {
return nil, err
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/restic/restic/internal/backend/local"
"github.com/restic/restic/internal/backend/mem"
"github.com/restic/restic/internal/backend/retry"
"github.com/restic/restic/internal/crypto"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/test"
@@ -97,11 +98,14 @@ func TestRepositoryWithVersion(t testing.TB, version uint) restic.Repository {
// TestOpenLocal opens a local repository.
func TestOpenLocal(t testing.TB, dir string) (r restic.Repository) {
var be restic.Backend
be, err := local.Open(context.TODO(), local.Config{Path: dir, Connections: 2})
if err != nil {
t.Fatal(err)
}
be = retry.New(be, 3, nil, nil)
repo, err := New(be, Options{})
if err != nil {
t.Fatal(err)

View File

@@ -93,7 +93,7 @@ func TestFindUsedBlobs(t *testing.T) {
snapshots = append(snapshots, sn)
}
p := progress.New(time.Second, findTestSnapshots, func(value uint64, total uint64, runtime time.Duration, final bool) {})
p := progress.NewCounter(time.Second, findTestSnapshots, func(value uint64, total uint64, runtime time.Duration, final bool) {})
defer p.Done()
for i, sn := range snapshots {
@@ -142,7 +142,7 @@ func TestMultiFindUsedBlobs(t *testing.T) {
want.Merge(loadIDSet(t, goldenFilename))
}
p := progress.New(time.Second, findTestSnapshots, func(value uint64, total uint64, runtime time.Duration, final bool) {})
p := progress.NewCounter(time.Second, findTestSnapshots, func(value uint64, total uint64, runtime time.Duration, final bool) {})
defer p.Done()
// run twice to check progress bar handling of duplicate tree roots

View File

@@ -60,6 +60,27 @@ func IsAlreadyLocked(err error) bool {
return errors.As(err, &e)
}
// invalidLockError is returned when NewLock or NewExclusiveLock fail due
// to an invalid lock.
type invalidLockError struct {
err error
}
func (e *invalidLockError) Error() string {
return fmt.Sprintf("invalid lock file: %v", e.err)
}
func (e *invalidLockError) Unwrap() error {
return e.err
}
// IsInvalidLock returns true iff err indicates that locking failed due to
// an invalid lock.
func IsInvalidLock(err error) bool {
var e *invalidLockError
return errors.As(err, &e)
}
// NewLock returns a new, non-exclusive lock for the repository. If an
// exclusive lock is already held by another process, it returns an error
// that satisfies IsAlreadyLocked.
@@ -146,7 +167,7 @@ func (l *Lock) checkForOtherLocks(ctx context.Context) error {
// if we cannot load a lock then it is unclear whether it can be ignored
// it could either be invalid or just unreadable due to network/permission problems
debug.Log("ignore lock %v: %v", id, err)
return errors.Fatal(err.Error())
return err
}
if l.Exclusive {
@@ -168,6 +189,9 @@ func (l *Lock) checkForOtherLocks(ctx context.Context) error {
return err
}
}
if errors.Is(err, ErrInvalidData) {
return &invalidLockError{err}
}
return err
}
@@ -198,7 +222,7 @@ var StaleLockTimeout = 30 * time.Minute
func (l *Lock) Stale() bool {
l.lock.Lock()
defer l.lock.Unlock()
debug.Log("testing if lock %v for process %d is stale", l, l.PID)
debug.Log("testing if lock %v for process %d is stale", l.lockID, l.PID)
if time.Since(l.Time) > StaleLockTimeout {
debug.Log("lock is stale, timestamp is too old: %v\n", l.Time)
return true
@@ -336,6 +360,12 @@ func ForAllLocks(ctx context.Context, repo Repository, excludeID *ID, fn func(ID
if excludeID != nil && id.Equal(*excludeID) {
return nil
}
if size == 0 {
// Ignore empty lock files as some backends do not guarantee atomic uploads.
// These may leave empty files behind if an upload was interrupted between
// creating the file and writing its data.
return nil
}
lock, err := LoadLock(ctx, repo, id)
m.Lock()

View File

@@ -4,10 +4,14 @@ import (
"context"
"github.com/restic/restic/internal/crypto"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/ui/progress"
"golang.org/x/sync/errgroup"
)
// ErrInvalidData is used to report that a file is corrupted
var ErrInvalidData = errors.New("invalid data returned")
// Repository stores data in a backend. It provides high-level functions and
// transparently encrypts/decrypts data.
type Repository interface {

View File

@@ -12,13 +12,32 @@ import (
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
var ErrNoSnapshotFound = errors.New("no snapshot found")
// findLatestSnapshot finds latest snapshot with optional target/directory, tags, hostname, and timestamp filters.
func findLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string,
tags []TagList, paths []string, timeStampLimit *time.Time) (*Snapshot, error) {
// A SnapshotFilter denotes a set of snapshots based on hosts, tags and paths.
type SnapshotFilter struct {
_ struct{} // Force naming fields in literals.
Hosts []string
Tags TagLists
Paths []string
// Match snapshots from before this timestamp. Zero for no limit.
TimestampLimit time.Time
}
func (f *SnapshotFilter) empty() bool {
return len(f.Hosts)+len(f.Tags)+len(f.Paths) == 0
}
func (f *SnapshotFilter) matches(sn *Snapshot) bool {
return sn.HasHostname(f.Hosts) && sn.HasTagList(f.Tags) && sn.HasPaths(f.Paths)
}
// findLatest finds the latest snapshot with optional target/directory,
// tags, hostname, and timestamp filters.
func (f *SnapshotFilter) findLatest(ctx context.Context, be Lister, loader LoaderUnpacked) (*Snapshot, error) {
var err error
absTargets := make([]string, 0, len(paths))
for _, target := range paths {
absTargets := make([]string, 0, len(f.Paths))
for _, target := range f.Paths {
if !filepath.IsAbs(target) {
target, err = filepath.Abs(target)
if err != nil {
@@ -27,6 +46,7 @@ func findLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, h
}
absTargets = append(absTargets, filepath.Clean(target))
}
f.Paths = absTargets
var latest *Snapshot
@@ -35,7 +55,7 @@ func findLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, h
return errors.Errorf("Error loading snapshot %v: %v", id.Str(), err)
}
if timeStampLimit != nil && snapshot.Time.After(*timeStampLimit) {
if !f.TimestampLimit.IsZero() && snapshot.Time.After(f.TimestampLimit) {
return nil
}
@@ -43,15 +63,7 @@ func findLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, h
return nil
}
if !snapshot.HasHostname(hosts) {
return nil
}
if !snapshot.HasTagList(tags) {
return nil
}
if !snapshot.HasPaths(absTargets) {
if !f.matches(snapshot) {
return nil
}
@@ -85,12 +97,14 @@ func FindSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, s strin
return LoadSnapshot(ctx, loader, id)
}
// FindFilteredSnapshot returns either the latests from a filtered list of all snapshots or a snapshot specified by `snapshotID`.
func FindFilteredSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string, timeStampLimit *time.Time, snapshotID string) (*Snapshot, error) {
// FindLatest returns either the latest of a filtered list of all snapshots
// or a snapshot specified by `snapshotID`.
func (f *SnapshotFilter) FindLatest(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotID string) (*Snapshot, error) {
if snapshotID == "latest" {
sn, err := findLatestSnapshot(ctx, be, loader, hosts, tags, paths, timeStampLimit)
sn, err := f.findLatest(ctx, be, loader)
if err == ErrNoSnapshotFound {
err = fmt.Errorf("snapshot filter (Paths:%v Tags:%v Hosts:%v): %w", paths, tags, hosts, err)
err = fmt.Errorf("snapshot filter (Paths:%v Tags:%v Hosts:%v): %w",
f.Paths, f.Tags, f.Hosts, err)
}
return sn, err
}
@@ -99,8 +113,8 @@ func FindFilteredSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked,
type SnapshotFindCb func(string, *Snapshot, error) error
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
func FindFilteredSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string, snapshotIDs []string, fn SnapshotFindCb) error {
// FindAll yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
func (f *SnapshotFilter) FindAll(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotIDs []string, fn SnapshotFindCb) error {
if len(snapshotIDs) != 0 {
var err error
usedFilter := false
@@ -116,9 +130,10 @@ func FindFilteredSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked
usedFilter = true
sn, err = findLatestSnapshot(ctx, be, loader, hosts, tags, paths, nil)
sn, err = f.findLatest(ctx, be, loader)
if err == ErrNoSnapshotFound {
err = errors.Errorf("no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)", paths, tags, hosts)
err = errors.Errorf("no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)",
f.Paths, f.Tags, f.Hosts)
}
if sn != nil {
ids.Insert(*sn.ID())
@@ -141,18 +156,14 @@ func FindFilteredSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked
}
// Give the user some indication their filters are not used.
if !usedFilter && (len(hosts) != 0 || len(tags) != 0 || len(paths) != 0) {
if !usedFilter && !f.empty() {
return fn("filters", nil, errors.Errorf("explicit snapshot ids are given"))
}
return nil
}
return ForAllSnapshots(ctx, be, loader, nil, func(id ID, sn *Snapshot, err error) error {
if err != nil {
return fn(id.String(), sn, err)
}
if !sn.HasHostname(hosts) || !sn.HasTagList(tags) || !sn.HasPaths(paths) {
if err == nil && !f.matches(sn) {
return nil
}

View File

@@ -14,13 +14,14 @@ func TestFindLatestSnapshot(t *testing.T) {
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1, 0)
latestSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1, 0)
sn, err := restic.FindFilteredSnapshot(context.TODO(), repo.Backend(), repo, []string{"foo"}, []restic.TagList{}, []string{}, nil, "latest")
f := restic.SnapshotFilter{Hosts: []string{"foo"}}
sn, err := f.FindLatest(context.TODO(), repo.Backend(), repo, "latest")
if err != nil {
t.Fatalf("FindLatestSnapshot returned error: %v", err)
t.Fatalf("FindLatest returned error: %v", err)
}
if *sn.ID() != *latestSnapshot.ID() {
t.Errorf("FindLatestSnapshot returned wrong snapshot ID: %v", *sn.ID())
t.Errorf("FindLatest returned wrong snapshot ID: %v", *sn.ID())
}
}
@@ -30,14 +31,15 @@ func TestFindLatestSnapshotWithMaxTimestamp(t *testing.T) {
desiredSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1, 0)
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1, 0)
maxTimestamp := parseTimeUTC("2018-08-08 08:08:08")
sn, err := restic.FindFilteredSnapshot(context.TODO(), repo.Backend(), repo, []string{"foo"}, []restic.TagList{}, []string{}, &maxTimestamp, "latest")
sn, err := (&restic.SnapshotFilter{
Hosts: []string{"foo"},
TimestampLimit: parseTimeUTC("2018-08-08 08:08:08"),
}).FindLatest(context.TODO(), repo.Backend(), repo, "latest")
if err != nil {
t.Fatalf("FindLatestSnapshot returned error: %v", err)
t.Fatalf("FindLatest returned error: %v", err)
}
if *sn.ID() != *desiredSnapshot.ID() {
t.Errorf("FindLatestSnapshot returned wrong snapshot ID: %v", *sn.ID())
t.Errorf("FindLatest returned wrong snapshot ID: %v", *sn.ID())
}
}

View File

@@ -0,0 +1,44 @@
package selfupdate
import (
"archive/zip"
"bytes"
"os"
"path/filepath"
"testing"
rtest "github.com/restic/restic/internal/test"
)
func TestExtractToFileZip(t *testing.T) {
printf := func(string, ...interface{}) {}
dir := t.TempDir()
ext := "zip"
data := []byte("Hello World!")
// create dummy archive
var archive bytes.Buffer
zw := zip.NewWriter(&archive)
w, err := zw.CreateHeader(&zip.FileHeader{
Name: "example.exe",
UncompressedSize64: uint64(len(data)),
})
rtest.OK(t, err)
_, err = w.Write(data[:])
rtest.OK(t, err)
rtest.OK(t, zw.Close())
// run twice to test creating a new file and overwriting
for i := 0; i < 2; i++ {
outfn := filepath.Join(dir, ext+"-out")
rtest.OK(t, extractToFile(archive.Bytes(), "src."+ext, outfn, printf))
outdata, err := os.ReadFile(outfn)
rtest.OK(t, err)
rtest.Assert(t, bytes.Equal(data[:], outdata), "%v contains wrong data", outfn)
// overwrite to test the file is properly overwritten
rtest.OK(t, os.WriteFile(outfn, []byte{1, 2, 3}, 0))
}
}

View File

@@ -7,11 +7,18 @@ import (
"fmt"
"os"
"path/filepath"
"github.com/restic/restic/internal/errors"
)
// Rename (rather than remove) the running version. The running binary will be locked
// on Windows and cannot be removed while still executing.
func removeResticBinary(dir, target string) error {
// nothing to do if the target does not exist
if _, err := os.Stat(target); errors.Is(err, os.ErrNotExist) {
return nil
}
backup := filepath.Join(dir, filepath.Base(target)+".bak")
if _, err := os.Stat(backup); err == nil {
_ = os.Remove(backup)

View File

@@ -15,7 +15,6 @@ import (
// JSONProgress reports progress for the `backup` command in JSON.
type JSONProgress struct {
*ui.Message
*ui.StdioWrapper
term *termstatus.Terminal
v uint
@@ -27,10 +26,9 @@ var _ ProgressPrinter = &JSONProgress{}
// NewJSONProgress returns a new backup progress reporter.
func NewJSONProgress(term *termstatus.Terminal, verbosity uint) *JSONProgress {
return &JSONProgress{
Message: ui.NewMessage(term, verbosity),
StdioWrapper: ui.NewStdioWrapper(term),
term: term,
v: verbosity,
Message: ui.NewMessage(term, verbosity),
term: term,
v: verbosity,
}
}

View File

@@ -1,14 +1,12 @@
package backup
import (
"context"
"io"
"sync"
"time"
"github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/signals"
"github.com/restic/restic/internal/ui/progress"
)
// A ProgressPrinter can print various progress messages.
@@ -22,10 +20,6 @@ type ProgressPrinter interface {
Finish(snapshotID restic.ID, start time.Time, summary *Summary, dryRun bool)
Reset()
// ui.StdioWrapper
Stdout() io.WriteCloser
Stderr() io.WriteCloser
P(msg string, args ...interface{})
V(msg string, args ...interface{})
}
@@ -46,10 +40,10 @@ type Summary struct {
// Progress reports progress for the `backup` command.
type Progress struct {
progress.Updater
mu sync.Mutex
interval time.Duration
start time.Time
start time.Time
scanStarted, scanFinished bool
@@ -57,66 +51,37 @@ type Progress struct {
processed, total Counter
errors uint
closed chan struct{}
summary Summary
printer ProgressPrinter
}
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
return &Progress{
interval: interval,
start: time.Now(),
p := &Progress{
start: time.Now(),
currentFiles: make(map[string]struct{}),
closed: make(chan struct{}),
printer: printer,
printer: printer,
}
}
p.Updater = *progress.NewUpdater(interval, func(runtime time.Duration, final bool) {
if final {
p.printer.Reset()
} else {
p.mu.Lock()
defer p.mu.Unlock()
if !p.scanStarted {
return
}
// Run regularly updates the status lines. It should be called in a separate
// goroutine.
func (p *Progress) Run(ctx context.Context) {
defer close(p.closed)
// Reset status when finished
defer p.printer.Reset()
var secondsRemaining uint64
if p.scanFinished {
secs := float64(runtime / time.Second)
todo := float64(p.total.Bytes - p.processed.Bytes)
secondsRemaining = uint64(secs / float64(p.processed.Bytes) * todo)
}
var tick <-chan time.Time
if p.interval != 0 {
t := time.NewTicker(p.interval)
defer t.Stop()
tick = t.C
}
signalsCh := signals.GetProgressChannel()
for {
var now time.Time
select {
case <-ctx.Done():
return
case now = <-tick:
case <-signalsCh:
now = time.Now()
p.printer.Update(p.total, p.processed, p.errors, p.currentFiles, p.start, secondsRemaining)
}
p.mu.Lock()
if !p.scanStarted {
p.mu.Unlock()
continue
}
var secondsRemaining uint64
if p.scanFinished {
secs := float64(now.Sub(p.start) / time.Second)
todo := float64(p.total.Bytes - p.processed.Bytes)
secondsRemaining = uint64(secs / float64(p.processed.Bytes) * todo)
}
p.printer.Update(p.total, p.processed, p.errors, p.currentFiles, p.start, secondsRemaining)
p.mu.Unlock()
}
})
return p
}
// Error is the error callback function for the archiver, it prints the error and returns nil.
@@ -234,6 +199,7 @@ func (p *Progress) ReportTotal(item string, s archiver.ScanStats) {
p.scanStarted = true
if item == "" {
p.scanFinished = true
p.printer.ReportTotal(item, p.start, s)
}
}
@@ -241,6 +207,6 @@ func (p *Progress) ReportTotal(item string, s archiver.ScanStats) {
// Finish prints the finishing messages.
func (p *Progress) Finish(snapshotID restic.ID, dryrun bool) {
// wait for the status update goroutine to shut down
<-p.closed
p.Updater.Done()
p.printer.Finish(snapshotID, p.start, &p.summary, dryrun)
}

View File

@@ -1,8 +1,6 @@
package backup
import (
"context"
"io"
"sync"
"testing"
"time"
@@ -45,9 +43,6 @@ func (p *mockPrinter) Finish(id restic.ID, _ time.Time, summary *Summary, dryRun
func (p *mockPrinter) Reset() {}
func (p *mockPrinter) Stdout() io.WriteCloser { return nil }
func (p *mockPrinter) Stderr() io.WriteCloser { return nil }
func (p *mockPrinter) P(msg string, args ...interface{}) {}
func (p *mockPrinter) V(msg string, args ...interface{}) {}
@@ -57,9 +52,6 @@ func TestProgress(t *testing.T) {
prnt := &mockPrinter{}
prog := NewProgress(prnt, time.Millisecond)
ctx, cancel := context.WithCancel(context.Background())
go prog.Run(ctx)
prog.StartFile("foo")
prog.CompleteBlob(1024)
@@ -71,7 +63,6 @@ func TestProgress(t *testing.T) {
prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0)
time.Sleep(10 * time.Millisecond)
cancel()
id := restic.NewRandomID()
prog.Finish(id, false)

View File

@@ -14,7 +14,6 @@ import (
// TextProgress reports progress for the `backup` command.
type TextProgress struct {
*ui.Message
*ui.StdioWrapper
term *termstatus.Terminal
}
@@ -25,9 +24,8 @@ var _ ProgressPrinter = &TextProgress{}
// NewTextProgress returns a new backup progress reporter.
func NewTextProgress(term *termstatus.Terminal, verbosity uint) *TextProgress {
return &TextProgress{
Message: ui.NewMessage(term, verbosity),
StdioWrapper: ui.NewStdioWrapper(term),
term: term,
Message: ui.NewMessage(term, verbosity),
term: term,
}
}
@@ -88,6 +86,8 @@ func (b *TextProgress) Error(item string, err error) error {
// CompleteItem is the status callback function for the archiver when a
// file/dir has been saved successfully.
func (b *TextProgress) CompleteItem(messageType, item string, previous, current *restic.Node, s archiver.ItemStats, d time.Duration) {
item = termstatus.Quote(item)
switch messageType {
case "dir new":
b.VV("new %v, saved in %.3fs (%v added, %v stored, %v metadata)",

View File

@@ -3,9 +3,6 @@ package progress
import (
"sync"
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/ui/signals"
)
// A Func is a callback for a Counter.
@@ -19,32 +16,22 @@ type Func func(value uint64, total uint64, runtime time.Duration, final bool)
//
// The Func is also called when SIGUSR1 (or SIGINFO, on BSD) is received.
type Counter struct {
report Func
start time.Time
stopped chan struct{} // Closed by run.
stop chan struct{} // Close to stop run.
tick *time.Ticker
Updater
valueMutex sync.Mutex
value uint64
max uint64
}
// New starts a new Counter.
func New(interval time.Duration, total uint64, report Func) *Counter {
// NewCounter starts a new Counter.
func NewCounter(interval time.Duration, total uint64, report Func) *Counter {
c := &Counter{
report: report,
start: time.Now(),
stopped: make(chan struct{}),
stop: make(chan struct{}),
max: total,
max: total,
}
if interval > 0 {
c.tick = time.NewTicker(interval)
}
go c.run()
c.Updater = *NewUpdater(interval, func(runtime time.Duration, final bool) {
v, max := c.Get()
report(v, max, runtime, final)
})
return c
}
@@ -69,18 +56,6 @@ func (c *Counter) SetMax(max uint64) {
c.valueMutex.Unlock()
}
// Done tells a Counter to stop and waits for it to report its final value.
func (c *Counter) Done() {
if c == nil {
return
}
if c.tick != nil {
c.tick.Stop()
}
close(c.stop)
<-c.stopped // Wait for last progress report.
}
// Get returns the current value and the maximum of c.
// This method is concurrency-safe.
func (c *Counter) Get() (v, max uint64) {
@@ -91,32 +66,8 @@ func (c *Counter) Get() (v, max uint64) {
return v, max
}
func (c *Counter) run() {
defer close(c.stopped)
defer func() {
// Must be a func so that time.Since isn't called at defer time.
v, max := c.Get()
c.report(v, max, time.Since(c.start), true)
}()
var tick <-chan time.Time
if c.tick != nil {
tick = c.tick.C
}
signalsCh := signals.GetProgressChannel()
for {
var now time.Time
select {
case now = <-tick:
case sig := <-signalsCh:
debug.Log("Signal received: %v\n", sig)
now = time.Now()
case <-c.stop:
return
}
v, max := c.Get()
c.report(v, max, now.Sub(c.start), false)
func (c *Counter) Done() {
if c != nil {
c.Updater.Done()
}
}

View File

@@ -35,7 +35,7 @@ func TestCounter(t *testing.T) {
lastTotal = total
ncalls++
}
c := progress.New(10*time.Millisecond, startTotal, report)
c := progress.NewCounter(10*time.Millisecond, startTotal, report)
done := make(chan struct{})
go func() {
@@ -63,24 +63,6 @@ func TestCounterNil(t *testing.T) {
// Shouldn't panic.
var c *progress.Counter
c.Add(1)
c.SetMax(42)
c.Done()
}
func TestCounterNoTick(t *testing.T) {
finalSeen := false
otherSeen := false
report := func(value, total uint64, d time.Duration, final bool) {
if final {
finalSeen = true
} else {
otherSeen = true
}
}
c := progress.New(0, 1, report)
time.Sleep(time.Millisecond)
c.Done()
test.Assert(t, finalSeen, "final call did not happen")
test.Assert(t, !otherSeen, "unexpected status update")
}

View File

@@ -0,0 +1,84 @@
package progress
import (
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/ui/signals"
)
// An UpdateFunc is a callback for a (progress) Updater.
//
// The final argument is true if Updater.Done has been called,
// which means that the current call will be the last.
type UpdateFunc func(runtime time.Duration, final bool)
// An Updater controls a goroutine that periodically calls an UpdateFunc.
//
// The UpdateFunc is also called when SIGUSR1 (or SIGINFO, on BSD) is received.
type Updater struct {
report UpdateFunc
start time.Time
stopped chan struct{} // Closed by run.
stop chan struct{} // Close to stop run.
tick *time.Ticker
}
// NewUpdater starts a new Updater.
func NewUpdater(interval time.Duration, report UpdateFunc) *Updater {
c := &Updater{
report: report,
start: time.Now(),
stopped: make(chan struct{}),
stop: make(chan struct{}),
}
if interval > 0 {
c.tick = time.NewTicker(interval)
}
go c.run()
return c
}
// Done tells an Updater to stop and waits for it to report its final value.
// Later calls do nothing.
func (c *Updater) Done() {
if c == nil || c.stop == nil {
return
}
if c.tick != nil {
c.tick.Stop()
}
close(c.stop)
<-c.stopped // Wait for last progress report.
c.stop = nil
}
func (c *Updater) run() {
defer close(c.stopped)
defer func() {
// Must be a func so that time.Since isn't called at defer time.
c.report(time.Since(c.start), true)
}()
var tick <-chan time.Time
if c.tick != nil {
tick = c.tick.C
}
signalsCh := signals.GetProgressChannel()
for {
var now time.Time
select {
case now = <-tick:
case sig := <-signalsCh:
debug.Log("Signal received: %v\n", sig)
now = time.Now()
case <-c.stop:
return
}
c.report(now.Sub(c.start), false)
}
}

View File

@@ -0,0 +1,52 @@
package progress_test
import (
"testing"
"time"
"github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/progress"
)
func TestUpdater(t *testing.T) {
finalSeen := false
var ncalls int
report := func(d time.Duration, final bool) {
if final {
finalSeen = true
}
ncalls++
}
c := progress.NewUpdater(10*time.Millisecond, report)
time.Sleep(100 * time.Millisecond)
c.Done()
test.Assert(t, finalSeen, "final call did not happen")
test.Assert(t, ncalls > 0, "no progress was reported")
}
func TestUpdaterStopTwice(t *testing.T) {
c := progress.NewUpdater(0, func(runtime time.Duration, final bool) {})
c.Done()
c.Done()
}
func TestUpdaterNoTick(t *testing.T) {
finalSeen := false
otherSeen := false
report := func(d time.Duration, final bool) {
if final {
finalSeen = true
} else {
otherSeen = true
}
}
c := progress.NewUpdater(0, report)
time.Sleep(time.Millisecond)
c.Done()
test.Assert(t, finalSeen, "final call did not happen")
test.Assert(t, !otherSeen, "unexpected status update")
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"unicode"
@@ -325,6 +326,7 @@ func wideRune(r rune) bool {
}
// SetStatus updates the status lines.
// The lines should not contain newlines; this method adds them.
func (t *Terminal) SetStatus(lines []string) {
if len(lines) == 0 {
return
@@ -341,21 +343,34 @@ func (t *Terminal) SetStatus(lines []string) {
}
}
// make sure that all lines have a line break and are not too long
// Sanitize lines and truncate them if they're too long.
for i, line := range lines {
line = strings.TrimRight(line, "\n")
line = Quote(line)
if width > 0 {
line = Truncate(line, width-2)
}
lines[i] = line + "\n"
if i < len(lines)-1 { // Last line gets no line break.
lines[i] = line + "\n"
}
}
// make sure the last line does not have a line break
last := len(lines) - 1
lines[last] = strings.TrimRight(lines[last], "\n")
select {
case t.status <- status{lines: lines}:
case <-t.closed:
}
}
// Quote lines with funny characters in them, meaning control chars, newlines,
// tabs, anything else non-printable and invalid UTF-8.
//
// This is intended to produce a string that does not mess up the terminal
// rather than produce an unambiguous quoted string.
func Quote(line string) string {
for _, r := range line {
// The replacement character usually means the input is not UTF-8.
if r == unicode.ReplacementChar || !unicode.IsPrint(r) {
return strconv.Quote(line)
}
}
return line
}

View File

@@ -1,6 +1,36 @@
package termstatus
import "testing"
import (
"strconv"
"testing"
rtest "github.com/restic/restic/internal/test"
)
func TestQuote(t *testing.T) {
for _, c := range []struct {
in string
needQuote bool
}{
{"foo.bar/baz", false},
{"föó_bàŕ-bãẑ", false},
{" foo ", false},
{"foo bar", false},
{"foo\nbar", true},
{"foo\rbar", true},
{"foo\abar", true},
{"\xff", true},
{`c:\foo\bar`, false},
// Issue #2260: terminal control characters.
{"\x1bm_red_is_beautiful", true},
} {
if c.needQuote {
rtest.Equals(t, strconv.Quote(c.in), Quote(c.in))
} else {
rtest.Equals(t, c.in, Quote(c.in))
}
}
}
func TestTruncate(t *testing.T) {
var tests = []struct {