mirror of
https://github.com/restic/restic.git
synced 2026-02-22 16:56:24 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be8be3397c | ||
|
|
db6b4f8912 | ||
|
|
1f3f042f32 | ||
|
|
0aaa4e6cbe | ||
|
|
0bac935dac | ||
|
|
1e6e9f9bd0 | ||
|
|
f342db7666 | ||
|
|
07a44a88f2 | ||
|
|
48e065d971 | ||
|
|
3b24c15c3d | ||
|
|
4304e01ca2 | ||
|
|
593eb710b4 | ||
|
|
97274ecabd | ||
|
|
74f7dd0b38 | ||
|
|
21ad357c10 | ||
|
|
7d4b7ad9cb | ||
|
|
a883bb6596 | ||
|
|
91acef90b2 | ||
|
|
b2b7727b31 | ||
|
|
0e4c9a5421 | ||
|
|
49fa8fe6dd | ||
|
|
12f167ee79 | ||
|
|
bb018fbc3e | ||
|
|
3b24e0ac55 | ||
|
|
04da31af2b | ||
|
|
65923e9c26 | ||
|
|
b903081804 | ||
|
|
beb1e872cc | ||
|
|
db350c0430 | ||
|
|
716a5dd20d | ||
|
|
dbd07ade98 | ||
|
|
7adf1e5d37 | ||
|
|
8f94eb5420 | ||
|
|
8aaba83719 | ||
|
|
e16a6d4c50 | ||
|
|
34e67e3510 | ||
|
|
c527c05590 | ||
|
|
ed23edeb62 | ||
|
|
0f398b82e3 | ||
|
|
99755c634b | ||
|
|
f5f13f6648 | ||
|
|
00216d54a1 | ||
|
|
1f3f68b2c0 | ||
|
|
57acc769b4 | ||
|
|
20ad14e362 | ||
|
|
c995b5be52 | ||
|
|
1adf28a2b5 | ||
|
|
6d9675c323 | ||
|
|
551b31ce3c | ||
|
|
ec99507e4c | ||
|
|
5f97f534b1 | ||
|
|
ed11bbd0e2 | ||
|
|
5bb9cb056d | ||
|
|
cd9bd22563 | ||
|
|
ecc826ef7d | ||
|
|
fb43cbab49 | ||
|
|
41d31b1e27 | ||
|
|
f6ea5c5865 | ||
|
|
4a7a6b06af | ||
|
|
e499bbe3ae | ||
|
|
52682b1c7b | ||
|
|
c15b4bceae | ||
|
|
74348be3fa | ||
|
|
72922a79ed |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -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"
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -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 \
|
||||
|
||||
164
CHANGELOG.md
164
CHANGELOG.md
@@ -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)
|
||||
=======================================
|
||||
|
||||
|
||||
10
changelog/0.15.1_2023-01-30/issue-3750
Normal file
10
changelog/0.15.1_2023-01-30/issue-3750
Normal 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
|
||||
7
changelog/0.15.1_2023-01-30/issue-4147
Normal file
7
changelog/0.15.1_2023-01-30/issue-4147
Normal 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
|
||||
19
changelog/0.15.1_2023-01-30/pull-4152
Normal file
19
changelog/0.15.1_2023-01-30/pull-4152
Normal 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
|
||||
13
changelog/0.15.1_2023-01-30/pull-4163
Normal file
13
changelog/0.15.1_2023-01-30/pull-4163
Normal 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
|
||||
6
changelog/0.15.1_2023-01-30/pull-4167
Normal file
6
changelog/0.15.1_2023-01-30/pull-4167
Normal 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
|
||||
12
changelog/0.15.2_2023-04-24/issue-2260
Normal file
12
changelog/0.15.2_2023-04-24/issue-2260
Normal 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
|
||||
8
changelog/0.15.2_2023-04-24/issue-4211
Normal file
8
changelog/0.15.2_2023-04-24/issue-4211
Normal 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
|
||||
11
changelog/0.15.2_2023-04-24/issue-4239
Normal file
11
changelog/0.15.2_2023-04-24/issue-4239
Normal 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
|
||||
18
changelog/0.15.2_2023-04-24/issue-4253
Normal file
18
changelog/0.15.2_2023-04-24/issue-4253
Normal 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
|
||||
4
changelog/0.15.2_2023-04-24/issue-4275
Normal file
4
changelog/0.15.2_2023-04-24/issue-4275
Normal 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
|
||||
6
changelog/0.15.2_2023-04-24/pull-4180
Normal file
6
changelog/0.15.2_2023-04-24/pull-4180
Normal 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
|
||||
5
changelog/0.15.2_2023-04-24/pull-4219
Normal file
5
changelog/0.15.2_2023-04-24/pull-4219
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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, ©Options.secondaryRepoOptions, "destination", "to copy snapshots from")
|
||||
initMultiSnapshotFilterOptions(f, ©Options.snapshotFilterOptions, true)
|
||||
initMultiSnapshotFilter(f, ©Options.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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
46
go.mod
@@ -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
96
go.sum
@@ -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=
|
||||
|
||||
@@ -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"},
|
||||
|
||||
14
internal/cache/file.go
vendored
14
internal/cache/file.go
vendored
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
44
internal/selfupdate/download_test.go
Normal file
44
internal/selfupdate/download_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
84
internal/ui/progress/updater.go
Normal file
84
internal/ui/progress/updater.go
Normal 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)
|
||||
}
|
||||
}
|
||||
52
internal/ui/progress/updater_test.go
Normal file
52
internal/ui/progress/updater_test.go
Normal 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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user