mirror of
https://github.com/restic/restic.git
synced 2026-02-23 01:06:23 +00:00
Compare commits
292 Commits
v0.9.5
...
debug-chun
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e72d638df | ||
|
|
070d43e290 | ||
|
|
d4bd32a37e | ||
|
|
e7d7b85d59 | ||
|
|
be5a0ff59f | ||
|
|
c5100d5632 | ||
|
|
956a1b0f96 | ||
|
|
fab626a3df | ||
|
|
4f00564574 | ||
|
|
f77477129f | ||
|
|
2e31120f89 | ||
|
|
8fb2c0d3c1 | ||
|
|
072cf7b02d | ||
|
|
df66daa5c9 | ||
|
|
9790d8ce1c | ||
|
|
16710454f4 | ||
|
|
08ec6c9f17 | ||
|
|
7910ff4c0e | ||
|
|
c4da9d1e90 | ||
|
|
3ed61987a2 | ||
|
|
9dba7a2577 | ||
|
|
a1352906e2 | ||
|
|
b7c0d4d8bf | ||
|
|
f033850aa0 | ||
|
|
bdf7ba20cb | ||
|
|
3ee6b8ec63 | ||
|
|
4a400f94bb | ||
|
|
1a1c572bac | ||
|
|
5a7c27ddb6 | ||
|
|
fb842759fc | ||
|
|
7aa2f8a61e | ||
|
|
08bf3bae79 | ||
|
|
e7b741b2d7 | ||
|
|
6bee62e346 | ||
|
|
bc74cd3ae5 | ||
|
|
90243ed1c4 | ||
|
|
0ce81d88b6 | ||
|
|
c03bc88b29 | ||
|
|
b8da7b1f4d | ||
|
|
f1b4d97945 | ||
|
|
2b5a6d255a | ||
|
|
f004dbe605 | ||
|
|
9efbe98879 | ||
|
|
7d9300efca | ||
|
|
c38aaaa768 | ||
|
|
8941041355 | ||
|
|
18fee4806f | ||
|
|
47d4d5bf1b | ||
|
|
74a64c47e4 | ||
|
|
a23e9c86ba | ||
|
|
4de12bf593 | ||
|
|
c542a509f0 | ||
|
|
8cf3bb8737 | ||
|
|
b46cc6d57e | ||
|
|
4a2156d3f0 | ||
|
|
41fee11f66 | ||
|
|
b592614061 | ||
|
|
b7c3039eb2 | ||
|
|
a307797c11 | ||
|
|
a03f107144 | ||
|
|
a141ab1bda | ||
|
|
52abec967f | ||
|
|
2828a9c2b0 | ||
|
|
cec7d581f3 | ||
|
|
12aa1e61da | ||
|
|
95da6c1c1d | ||
|
|
0c03a80fc4 | ||
|
|
b1c77172c2 | ||
|
|
c0373cd307 | ||
|
|
28121090c2 | ||
|
|
44a57d66c3 | ||
|
|
266f9dbe16 | ||
|
|
60e4a88f17 | ||
|
|
c50f91b7f9 | ||
|
|
e851d29565 | ||
|
|
b67b7ebfe6 | ||
|
|
751eba0e68 | ||
|
|
7447c44484 | ||
|
|
c8a672fa29 | ||
|
|
863ba76494 | ||
|
|
8526cc6647 | ||
|
|
694b7a17e7 | ||
|
|
5cd0bce452 | ||
|
|
58bd165253 | ||
|
|
65d3fb6b33 | ||
|
|
f165048172 | ||
|
|
de5516a90e | ||
|
|
4f6fd9fb98 | ||
|
|
9a9101d144 | ||
|
|
616f9499ae | ||
|
|
a40ac37550 | ||
|
|
99fd80a585 | ||
|
|
2464f7c4d1 | ||
|
|
b5c7778428 | ||
|
|
c52198d12c | ||
|
|
f17ffa0283 | ||
|
|
5e2afd91e7 | ||
|
|
79b882e901 | ||
|
|
1b502fa9ef | ||
|
|
e1969d1e33 | ||
|
|
6ac6bca7a1 | ||
|
|
3a6feb0596 | ||
|
|
2f8aa2ce30 | ||
|
|
f2bf06a419 | ||
|
|
71900248ab | ||
|
|
23055aaadf | ||
|
|
8bf6a3af97 | ||
|
|
27d6e5e186 | ||
|
|
4214b1746e | ||
|
|
f6f240573a | ||
|
|
ecdf49679e | ||
|
|
e1f722d266 | ||
|
|
2d47381b1d | ||
|
|
294ca55967 | ||
|
|
78c518ccac | ||
|
|
42a3292bcf | ||
|
|
ef70a2fcb3 | ||
|
|
af20015725 | ||
|
|
680a14afa1 | ||
|
|
bf199e5ca7 | ||
|
|
ae127a412d | ||
|
|
5ae7e6f945 | ||
|
|
d8da9c4401 | ||
|
|
daf3bfc882 | ||
|
|
94f4f13388 | ||
|
|
4615bdfd70 | ||
|
|
299f5971f2 | ||
|
|
7a1352ae87 | ||
|
|
72734d59b5 | ||
|
|
3679669aae | ||
|
|
3ed54e762e | ||
|
|
fea835b4e2 | ||
|
|
16b321b140 | ||
|
|
b58dfda212 | ||
|
|
b229aa5cf1 | ||
|
|
81c0b031f9 | ||
|
|
760863e7f9 | ||
|
|
a135699397 | ||
|
|
94a4d45dfb | ||
|
|
4de384087d | ||
|
|
57815d9cd6 | ||
|
|
644673bcf2 | ||
|
|
e7cdf2acbb | ||
|
|
0f22f008ed | ||
|
|
c184da21d0 | ||
|
|
ef5efc6a82 | ||
|
|
688014487b | ||
|
|
38ddfbc4d3 | ||
|
|
da48b925ff | ||
|
|
4bcd56fbae | ||
|
|
6e9778ae0e | ||
|
|
63c67be908 | ||
|
|
d70a4a9350 | ||
|
|
2cd9c7ef16 | ||
|
|
6ec5dc8016 | ||
|
|
fe430a680a | ||
|
|
69a0d0ee90 | ||
|
|
4557881066 | ||
|
|
9b5d069ade | ||
|
|
c56cbfe95b | ||
|
|
97e5ce4344 | ||
|
|
72bd467e23 | ||
|
|
d818b7618b | ||
|
|
1c3812a6f6 | ||
|
|
ec91b80f09 | ||
|
|
18a336c154 | ||
|
|
d21a13f1ee | ||
|
|
e0eac30ee5 | ||
|
|
fccb579471 | ||
|
|
6c700f95b5 | ||
|
|
95d070c147 | ||
|
|
da4473aba6 | ||
|
|
6e85a58045 | ||
|
|
476e2e8762 | ||
|
|
246bb46e82 | ||
|
|
fe0361ffcb | ||
|
|
952469473f | ||
|
|
02108f202e | ||
|
|
b02357f542 | ||
|
|
f2aeaef8f1 | ||
|
|
547702d285 | ||
|
|
599831f874 | ||
|
|
14c90d9e85 | ||
|
|
c41bbb3761 | ||
|
|
8a54a0f5ac | ||
|
|
c0a5530950 | ||
|
|
77ef92b95c | ||
|
|
69f75bbf70 | ||
|
|
30519f01ff | ||
|
|
b723ca3de5 | ||
|
|
f5084d70d7 | ||
|
|
29b7b17491 | ||
|
|
e14c4b1737 | ||
|
|
745d79fe5f | ||
|
|
fb95426f64 | ||
|
|
4cadc89ad3 | ||
|
|
409909a7f5 | ||
|
|
df500a372d | ||
|
|
a444731dc0 | ||
|
|
a6e8af7e0f | ||
|
|
aa5af8af0e | ||
|
|
4e3353109d | ||
|
|
02c8d38095 | ||
|
|
fd6211653c | ||
|
|
3d4f2dd6b4 | ||
|
|
c1ddc0c18b | ||
|
|
c95f032a9c | ||
|
|
3087776135 | ||
|
|
b6f01ffbe6 | ||
|
|
41fe9318b1 | ||
|
|
8387d18d4d | ||
|
|
929d2b8df3 | ||
|
|
4f0682d730 | ||
|
|
967d1bbf0c | ||
|
|
2f80b37b93 | ||
|
|
4d2aa18273 | ||
|
|
6b1e5d4e18 | ||
|
|
26d1f9f4ba | ||
|
|
6a89c0f0ef | ||
|
|
b87230b93d | ||
|
|
6f2b8d622a | ||
|
|
90440212f2 | ||
|
|
3a5c9aadad | ||
|
|
a78142c1bb | ||
|
|
07045c7e23 | ||
|
|
0a5d42db3f | ||
|
|
67d99b8cfb | ||
|
|
1a0c0dc277 | ||
|
|
e86d9307d0 | ||
|
|
923e681af3 | ||
|
|
37770b1d82 | ||
|
|
02fea4f76a | ||
|
|
7cacba0394 | ||
|
|
e6db3596f1 | ||
|
|
3acc7af310 | ||
|
|
5c4653f427 | ||
|
|
f7317a9287 | ||
|
|
30db8057e4 | ||
|
|
0e897ef7b8 | ||
|
|
b3e727f40d | ||
|
|
17feccd998 | ||
|
|
1596d06f8e | ||
|
|
db20c0b8d0 | ||
|
|
604b18aa74 | ||
|
|
01c51b3449 | ||
|
|
de8cf5e345 | ||
|
|
cfa2ac69e0 | ||
|
|
3ca306d69a | ||
|
|
1e9eefa066 | ||
|
|
e9af012229 | ||
|
|
8066e93f47 | ||
|
|
e19622e4b1 | ||
|
|
38ea7ed4f6 | ||
|
|
76d1866444 | ||
|
|
8b22fe29cf | ||
|
|
02014be76c | ||
|
|
16eeed2ad5 | ||
|
|
3f94f63967 | ||
|
|
88716794e3 | ||
|
|
3ca424050f | ||
|
|
fea2464d4d | ||
|
|
5d272e5c08 | ||
|
|
5bd5db4294 | ||
|
|
4429a66b5f | ||
|
|
8066195e6e | ||
|
|
f7f14cf8c9 | ||
|
|
5096f3b491 | ||
|
|
cf3fc2a5b1 | ||
|
|
920d458a4a | ||
|
|
b016dc2ff0 | ||
|
|
355db0bc29 | ||
|
|
6e2fe73189 | ||
|
|
303a5dab6a | ||
|
|
7dcd2968b6 | ||
|
|
298f490195 | ||
|
|
37cb82b28b | ||
|
|
bce6438d22 | ||
|
|
919dd2ac84 | ||
|
|
870bc5108e | ||
|
|
418296c5c9 | ||
|
|
a6481b3707 | ||
|
|
00b527fb09 | ||
|
|
0ebfc55ee3 | ||
|
|
35b7607802 | ||
|
|
fad9f65c65 | ||
|
|
939f3e972c | ||
|
|
ca8c3b4fd5 | ||
|
|
4f45b14f25 | ||
|
|
389067fb8b | ||
|
|
4b0ca9ddab | ||
|
|
b8c2544dcb | ||
|
|
c7762453cf |
27
.github/ISSUE_TEMPLATE.md
vendored
27
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,27 +0,0 @@
|
||||
<!--
|
||||
|
||||
Welcome! If you have a question or are unsure if you should open an issue,
|
||||
please use the forum instead!
|
||||
|
||||
https://forum.restic.net
|
||||
|
||||
The forum is a better place for questions about restic or general suggestions
|
||||
and topics, e.g. usage or documentation questions! This issue tracker is mainly
|
||||
for tracking bugs and feature requests directly relating to the development of
|
||||
the software itself, rather than the project.
|
||||
|
||||
Thanks for understanding, and for contributing to the project!
|
||||
-->
|
||||
|
||||
|
||||
Output of `restic version`
|
||||
--------------------------
|
||||
|
||||
<!--
|
||||
Please add the version of restic you're currently using here, this helps us
|
||||
later to see what has changed in restic when we revisit this issue after some
|
||||
time.
|
||||
-->
|
||||
|
||||
Describe the issue
|
||||
------------------
|
||||
4
.github/ISSUE_TEMPLATE/Bug.md
vendored
4
.github/ISSUE_TEMPLATE/Bug.md
vendored
@@ -83,8 +83,8 @@ Do you have an idea how to solve the issue?
|
||||
|
||||
|
||||
|
||||
Did restic help you or made you happy in any way?
|
||||
-------------------------------------------------
|
||||
Did restic help you today? Did it make you happy in any way?
|
||||
------------------------------------------------------------
|
||||
|
||||
<!--
|
||||
Answering this question is not required, but if you have anything positive to share, please do so here!
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/Feature.md
vendored
4
.github/ISSUE_TEMPLATE/Feature.md
vendored
@@ -47,8 +47,8 @@ This section should contain a brief description what you're trying to do, which
|
||||
would be possible after implementing the new feature.
|
||||
-->
|
||||
|
||||
Did restic help you or made you happy in any way?
|
||||
-------------------------------------------------
|
||||
Did restic help you today? Did it make you happy in any way?
|
||||
------------------------------------------------------------
|
||||
|
||||
<!--
|
||||
Answering this question is not required, but if you have anything positive to share, please do so here!
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: restic forum
|
||||
url: https://forum.restic.net
|
||||
about: Please ask questions about using restic here, do not open an issue for questions.
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -22,12 +22,16 @@ Was the change discussed in an issue or in the forum before?
|
||||
|
||||
<!--
|
||||
Link issues and relevant forum posts here.
|
||||
|
||||
If this PR resolves an issue on GitHub, use "closes #1234" so that the issue is
|
||||
closed automatically when this PR is merged.
|
||||
-->
|
||||
|
||||
Checklist
|
||||
---------
|
||||
|
||||
- [ ] I have read the [Contribution Guidelines](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#providing-patches)
|
||||
- [ ] I have enabled [maintainer edits for this PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)
|
||||
- [ ] I have added tests for all changes in this PR
|
||||
- [ ] I have added documentation for the changes (in the manual)
|
||||
- [ ] There's a new file in `changelog/unreleased/` that describes the changes for our users (template [here](https://github.com/restic/restic/blob/master/changelog/TEMPLATE))
|
||||
|
||||
31
.travis.yml
31
.travis.yml
@@ -3,14 +3,6 @@ sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
go: "1.10.x"
|
||||
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
|
||||
- os: linux
|
||||
go: "1.11.x"
|
||||
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0
|
||||
@@ -19,9 +11,25 @@ matrix:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
|
||||
# only run fuse and cloud backends tests on Travis for the latest Go on Linux
|
||||
- os: linux
|
||||
go: "1.12.x"
|
||||
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
|
||||
- os: linux
|
||||
go: "1.13.x"
|
||||
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
|
||||
# only run fuse and cloud backends tests on Travis for the latest Go on Linux
|
||||
- os: linux
|
||||
go: "1.14.x"
|
||||
sudo: true
|
||||
cache:
|
||||
directories:
|
||||
@@ -29,7 +37,7 @@ matrix:
|
||||
- $HOME/gopath/pkg/mod
|
||||
|
||||
- os: osx
|
||||
go: "1.12.x"
|
||||
go: "1.14.x"
|
||||
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0
|
||||
cache:
|
||||
directories:
|
||||
@@ -56,6 +64,3 @@ install:
|
||||
|
||||
script:
|
||||
- go run run_integration_tests.go
|
||||
|
||||
after_success:
|
||||
- test -r all.cov && bash <(curl -s https://codecov.io/bash) -f all.cov
|
||||
|
||||
103
CHANGELOG.md
103
CHANGELOG.md
@@ -1,3 +1,106 @@
|
||||
Changelog for restic 0.9.6 (2019-11-22)
|
||||
=======================================
|
||||
|
||||
The following sections list the changes in restic 0.9.6 relevant to
|
||||
restic users. The changes are ordered by importance.
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
* Fix #2063: Allow absolute path for filename when backing up from stdin
|
||||
* Fix #2174: Save files with invalid timestamps
|
||||
* Fix #2249: Read fresh metadata for unmodified files
|
||||
* Fix #2301: Add upper bound for t in --read-data-subset=n/t
|
||||
* Fix #2321: Check errors when loading index files
|
||||
* Enh #2179: Use ctime when checking for file changes
|
||||
* Enh #2306: Allow multiple retries for interactive password input
|
||||
* Enh #2330: Make `--group-by` accept both singular and plural
|
||||
* Enh #2350: Add option to configure S3 region
|
||||
|
||||
Details
|
||||
-------
|
||||
|
||||
* Bugfix #2063: Allow absolute path for filename when backing up from stdin
|
||||
|
||||
When backing up from stdin, handle directory path for `--stdin-filename`. This can be used to
|
||||
specify the full path for the backed-up file.
|
||||
|
||||
https://github.com/restic/restic/issues/2063
|
||||
|
||||
* Bugfix #2174: Save files with invalid timestamps
|
||||
|
||||
When restic reads invalid timestamps (year is before 0000 or after 9999) it refused to read and
|
||||
archive the file. We've changed the behavior and will now save modified timestamps with the
|
||||
year set to either 0000 or 9999, the rest of the timestamp stays the same, so the file will be saved
|
||||
(albeit with a bogus timestamp).
|
||||
|
||||
https://github.com/restic/restic/issues/2174
|
||||
https://github.com/restic/restic/issues/1173
|
||||
|
||||
* Bugfix #2249: Read fresh metadata for unmodified files
|
||||
|
||||
Restic took all metadata for files which were detected as unmodified, not taking into account
|
||||
changed metadata (ownership, mode). This is now corrected.
|
||||
|
||||
https://github.com/restic/restic/issues/2249
|
||||
https://github.com/restic/restic/pull/2252
|
||||
|
||||
* Bugfix #2301: Add upper bound for t in --read-data-subset=n/t
|
||||
|
||||
256 is the effective maximum for t, but restic would allow larger values, leading to strange
|
||||
behavior.
|
||||
|
||||
https://github.com/restic/restic/issues/2301
|
||||
https://github.com/restic/restic/pull/2304
|
||||
|
||||
* Bugfix #2321: Check errors when loading index files
|
||||
|
||||
Restic now checks and handles errors which occur when loading index files, the missing check
|
||||
leads to odd errors (and a stack trace printed to users) later. This was reported in the forum.
|
||||
|
||||
https://github.com/restic/restic/pull/2321
|
||||
https://forum.restic.net/t/check-rebuild-index-prune/1848/13
|
||||
|
||||
* Enhancement #2179: Use ctime when checking for file changes
|
||||
|
||||
Previously, restic only checked a file's mtime (along with other non-timestamp metadata) to
|
||||
decide if a file has changed. This could cause restic to not notice that a file has changed (and
|
||||
therefore continue to store the old version, as opposed to the modified version) if something
|
||||
edits the file and then resets the timestamp. Restic now also checks the ctime of files, so any
|
||||
modifications to a file should be noticed, and the modified file will be backed up. The ctime
|
||||
check will be disabled if the --ignore-inode flag was given.
|
||||
|
||||
If this change causes problems for you, please open an issue, and we can look in to adding a
|
||||
seperate flag to disable just the ctime check.
|
||||
|
||||
https://github.com/restic/restic/issues/2179
|
||||
https://github.com/restic/restic/pull/2212
|
||||
|
||||
* Enhancement #2306: Allow multiple retries for interactive password input
|
||||
|
||||
Restic used to quit if the repository password was typed incorrectly once. Restic will now ask
|
||||
the user again for the repository password if typed incorrectly. The user will now get three
|
||||
tries to input the correct password before restic quits.
|
||||
|
||||
https://github.com/restic/restic/issues/2306
|
||||
|
||||
* Enhancement #2330: Make `--group-by` accept both singular and plural
|
||||
|
||||
One can now use the values `host`/`hosts`, `path`/`paths` and `tag` / `tags` interchangeably
|
||||
in the `--group-by` argument.
|
||||
|
||||
https://github.com/restic/restic/issues/2330
|
||||
|
||||
* Enhancement #2350: Add option to configure S3 region
|
||||
|
||||
We've added a new option for setting the region when accessing an S3-compatible service. For
|
||||
some providers, it is required to set this to a valid value. You can do that either by setting the
|
||||
environment variable `AWS_DEFAULT_REGION` or using the option `s3.region`, e.g. like this:
|
||||
`-o s3.region="us-east-1"`.
|
||||
|
||||
https://github.com/restic/restic/pull/2350
|
||||
|
||||
|
||||
Changelog for restic 0.9.5 (2019-04-23)
|
||||
=======================================
|
||||
|
||||
|
||||
@@ -46,15 +46,12 @@ Remember, the easier it is for us to reproduce the bug, the earlier it will be
|
||||
corrected!
|
||||
|
||||
In addition, you can compile restic with debug support by running
|
||||
`go run -mod=vendor build.go -tags debug` and instructing it to create a debug
|
||||
`go run build.go -tags debug` and instructing it to create a debug
|
||||
log by setting the environment variable `DEBUG_LOG` to a file, e.g. like this:
|
||||
|
||||
$ export DEBUG_LOG=/tmp/restic-debug.log
|
||||
$ restic backup ~/work
|
||||
|
||||
For Go < 1.11, you need to remove the `-mod=vendor` option from the build
|
||||
command.
|
||||
|
||||
Please be aware that the debug log file will contain potentially sensitive
|
||||
things like file and directory names, so please either redact it before
|
||||
uploading it somewhere or post only the parts that are really relevant.
|
||||
@@ -133,8 +130,7 @@ down to the following steps:
|
||||
|
||||
2. Clone the repository locally and create a new branch. If you are working on
|
||||
the code itself, please set up the development environment as described in
|
||||
the previous section. Especially take care to place your forked repository
|
||||
at the correct path (`src/github.com/restic/restic`) within your `GOPATH`.
|
||||
the previous section.
|
||||
|
||||
3. Then commit your changes as fine grained as possible, as smaller patches,
|
||||
that handle one and only one issue are easier to discuss and merge.
|
||||
@@ -142,7 +138,7 @@ down to the following steps:
|
||||
4. Push the new branch with your changes to your fork of the repository.
|
||||
|
||||
5. Create a pull request by visiting the GitHub website, it will guide you
|
||||
through the process.
|
||||
through the process. Please [allow edits from maintainers](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
|
||||
|
||||
6. You will receive comments on your code and the feature or bug that they
|
||||
address. Maybe you need to rework some minor things, in this case push new
|
||||
@@ -150,11 +146,14 @@ down to the following steps:
|
||||
existing commit, use common sense to decide which is better), they will be
|
||||
automatically added to the pull request.
|
||||
|
||||
7. If your pull request changes anything that users should be aware of (a
|
||||
bugfix, a new feature, ...) please add an entry to the file
|
||||
['CHANGELOG.md'](CHANGELOG.md). It will be used in the announcement of the
|
||||
next stable release. While writing, ask yourself: If I were the user, what
|
||||
would I need to be aware of with this change.
|
||||
7. If your pull request changes anything that users should be aware
|
||||
of (a bugfix, a new feature, ...) please add an entry as a new
|
||||
file in `changelog/unreleased` including the issue number in the
|
||||
filename (e.g. `issue-8756`). Use the template in
|
||||
`changelog/TEMPLATE` for the content. It will be used in the
|
||||
announcement of the next stable release. While writing, ask
|
||||
yourself: If I were the user, what would I need to be aware of
|
||||
with this change.
|
||||
|
||||
8. Once your code looks good and passes all the tests, we'll merge it. Thanks
|
||||
a lot for your contribution!
|
||||
|
||||
2
Makefile
2
Makefile
@@ -3,7 +3,7 @@
|
||||
all: restic
|
||||
|
||||
restic:
|
||||
go run -mod=vendor build.go || go run build.go
|
||||
go run build.go
|
||||
|
||||
clean:
|
||||
rm -f restic
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|Documentation| |Build Status| |Build status| |Report Card| |Say Thanks| |TestCoverage| |Reviewed by Hound|
|
||||
|Documentation| |Build Status| |Build status| |Report Card| |Say Thanks| |Reviewed by Hound|
|
||||
|
||||
Introduction
|
||||
------------
|
||||
@@ -129,8 +129,6 @@ Storage are sponsored by `AppsCode <https://appscode.com>`__!
|
||||
:target: https://goreportcard.com/report/github.com/restic/restic
|
||||
.. |Say Thanks| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/restic
|
||||
.. |TestCoverage| image:: https://codecov.io/gh/restic/restic/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/restic/restic
|
||||
.. |AppsCode| image:: https://cdn.appscode.com/images/logo/appscode/ac-logo-color.png
|
||||
:target: https://appscode.com
|
||||
.. |Reviewed by Hound| image:: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
|
||||
|
||||
@@ -20,8 +20,8 @@ init:
|
||||
|
||||
install:
|
||||
- rmdir c:\go /s /q
|
||||
- appveyor DownloadFile https://dl.google.com/go/go1.12.1.windows-amd64.msi
|
||||
- msiexec /i go1.12.1.windows-amd64.msi /q
|
||||
- appveyor DownloadFile https://dl.google.com/go/go1.14.windows-amd64.msi
|
||||
- msiexec /i go1.14.windows-amd64.msi /q
|
||||
- go version
|
||||
- go env
|
||||
- appveyor DownloadFile http://sourceforge.netcologne.de/project/gnuwin32/tar/1.13-1/tar-1.13-1-bin.zip -FileName tar.zip
|
||||
@@ -29,4 +29,4 @@ install:
|
||||
- set PATH=bin/;%PATH%
|
||||
|
||||
build_script:
|
||||
- go run -mod=vendor run_integration_tests.go
|
||||
- go run run_integration_tests.go
|
||||
|
||||
270
build.go
270
build.go
@@ -3,22 +3,15 @@
|
||||
// This program aims to make building Go programs for end users easier by just
|
||||
// calling it with `go run`, without having to setup a GOPATH.
|
||||
//
|
||||
// For Go < 1.11, it'll create a new GOPATH in a temporary directory, then run
|
||||
// `go build` on the package configured as Main in the Config struct.
|
||||
//
|
||||
// For Go >= 1.11 if the file go.mod is present, it'll use Go modules and not
|
||||
// setup a GOPATH. It builds the package configured as Main in the Config
|
||||
// struct with `go build -mod=vendor` to use the vendored dependencies.
|
||||
// The variable GOPROXY is set to `off` so that no network calls are made. All
|
||||
// files are copied to a temporary directory before `go build` is called within
|
||||
// that directory.
|
||||
// This program needs Go >= 1.11. It'll use Go modules for compilation. It
|
||||
// builds the package configured as Main in the Config struct.
|
||||
|
||||
// BSD 2-Clause License
|
||||
//
|
||||
// Copyright (c) 2016-2018, Alexander Neumann <alexander@bumpern.de>
|
||||
// All rights reserved.
|
||||
//
|
||||
// This file has been copied from the repository at:
|
||||
// This file has been derived from the repository at:
|
||||
// https://github.com/fd0/build-go
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -65,7 +58,7 @@ var config = Config{
|
||||
Main: "./cmd/restic", // package name for the main package
|
||||
DefaultBuildTags: []string{"selfupdate"}, // specify build tags which are always used
|
||||
Tests: []string{"./..."}, // tests to run
|
||||
MinVersion: GoVersion{Major: 1, Minor: 10, Patch: 0}, // minimum Go version supported
|
||||
MinVersion: GoVersion{Major: 1, Minor: 11, Patch: 0}, // minimum Go version supported
|
||||
}
|
||||
|
||||
// Config configures the build.
|
||||
@@ -79,125 +72,13 @@ type Config struct {
|
||||
}
|
||||
|
||||
var (
|
||||
verbose bool
|
||||
keepGopath bool
|
||||
runTests bool
|
||||
enableCGO bool
|
||||
enablePIE bool
|
||||
goVersion = ParseGoVersion(runtime.Version())
|
||||
verbose bool
|
||||
runTests bool
|
||||
enableCGO bool
|
||||
enablePIE bool
|
||||
goVersion = ParseGoVersion(runtime.Version())
|
||||
)
|
||||
|
||||
// copy all Go files in src to dst, creating directories on the fly, so calling
|
||||
//
|
||||
// copy("/tmp/gopath/src/github.com/restic/restic", "/home/u/restic")
|
||||
//
|
||||
// with "/home/u/restic" containing the file "foo.go" yields the following tree
|
||||
// at "/tmp/gopath":
|
||||
//
|
||||
// /tmp/gopath
|
||||
// └── src
|
||||
// └── github.com
|
||||
// └── restic
|
||||
// └── restic
|
||||
// └── foo.go
|
||||
func copy(dst, src string) error {
|
||||
verbosePrintf("copy contents of %v to %v\n", src, dst)
|
||||
return filepath.Walk(src, func(name string, fi os.FileInfo, err error) error {
|
||||
if name == src {
|
||||
return err
|
||||
}
|
||||
|
||||
if name == ".git" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
intermediatePath, err := filepath.Rel(src, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileSrc := filepath.Join(src, intermediatePath)
|
||||
fileDst := filepath.Join(dst, intermediatePath)
|
||||
|
||||
return copyFile(fileDst, fileSrc)
|
||||
})
|
||||
}
|
||||
|
||||
func directoryExists(dirname string) bool {
|
||||
stat, err := os.Stat(dirname)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return stat.IsDir()
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
stat, err := os.Stat(filename)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return stat.Mode().IsRegular()
|
||||
}
|
||||
|
||||
// copyFile creates dst from src, preserving file attributes and timestamps.
|
||||
func copyFile(dst, src string) error {
|
||||
fi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fsrc, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
fmt.Printf("MkdirAll(%v)\n", filepath.Dir(dst))
|
||||
return err
|
||||
}
|
||||
|
||||
fdst, err := os.Create(dst)
|
||||
if err != nil {
|
||||
_ = fsrc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fdst, fsrc)
|
||||
if err != nil {
|
||||
_ = fsrc.Close()
|
||||
_ = fdst.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = fdst.Close()
|
||||
if err != nil {
|
||||
_ = fsrc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = fsrc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chmod(dst, fi.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chtimes(dst, fi.ModTime(), fi.ModTime())
|
||||
}
|
||||
|
||||
// die prints the message with fmt.Fprintf() to stderr and exits with an error
|
||||
// code.
|
||||
func die(message string, args ...interface{}) {
|
||||
@@ -211,7 +92,6 @@ func showUsage(output io.Writer) {
|
||||
fmt.Fprintf(output, "OPTIONS:\n")
|
||||
fmt.Fprintf(output, " -v --verbose output more messages\n")
|
||||
fmt.Fprintf(output, " -t --tags specify additional build tags\n")
|
||||
fmt.Fprintf(output, " -k --keep-tempdir do not remove the temporary directory after build\n")
|
||||
fmt.Fprintf(output, " -T --test run tests\n")
|
||||
fmt.Fprintf(output, " -o --output set output file name\n")
|
||||
fmt.Fprintf(output, " --enable-cgo use CGO to link against libc\n")
|
||||
@@ -219,7 +99,6 @@ func showUsage(output io.Writer) {
|
||||
fmt.Fprintf(output, " --goos value set GOOS for cross-compilation\n")
|
||||
fmt.Fprintf(output, " --goarch value set GOARCH for cross-compilation\n")
|
||||
fmt.Fprintf(output, " --goarm value set GOARM for cross-compilation\n")
|
||||
fmt.Fprintf(output, " --tempdir dir use a specific directory for compilation\n")
|
||||
}
|
||||
|
||||
func verbosePrintf(message string, args ...interface{}) {
|
||||
@@ -230,49 +109,39 @@ func verbosePrintf(message string, args ...interface{}) {
|
||||
fmt.Printf("build: "+message, args...)
|
||||
}
|
||||
|
||||
// cleanEnv returns a clean environment with GOPATH, GOBIN and GO111MODULE
|
||||
// removed (if present).
|
||||
func cleanEnv() (env []string) {
|
||||
removeKeys := map[string]struct{}{
|
||||
"GOPATH": struct{}{},
|
||||
"GOBIN": struct{}{},
|
||||
"GO111MODULE": struct{}{},
|
||||
}
|
||||
|
||||
for _, v := range os.Environ() {
|
||||
data := strings.SplitN(v, "=", 2)
|
||||
name := data[0]
|
||||
|
||||
if _, ok := removeKeys[name]; ok {
|
||||
// printEnv prints Go-relevant environment variables in a nice way using verbosePrintf.
|
||||
func printEnv(env []string) {
|
||||
verbosePrintf("environment (GO*):\n")
|
||||
for _, v := range env {
|
||||
// ignore environment variables which do not start with GO*.
|
||||
if !strings.HasPrefix(v, "GO") {
|
||||
continue
|
||||
}
|
||||
|
||||
env = append(env, v)
|
||||
verbosePrintf(" %s\n", v)
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// build runs "go build args..." with GOPATH set to gopath.
|
||||
func build(cwd string, env map[string]string, args ...string) error {
|
||||
a := []string{"build"}
|
||||
|
||||
if goVersion.AtLeast(GoVersion{1, 10, 0}) {
|
||||
verbosePrintf("Go version is at least 1.10, using new syntax for -gcflags\n")
|
||||
// use new prefix
|
||||
// try to remove all absolute paths from resulting binary
|
||||
if goVersion.AtLeast(GoVersion{1, 13, 0}) {
|
||||
// use the new flag introduced by Go 1.13
|
||||
a = append(a, "-trimpath")
|
||||
} else {
|
||||
// otherwise try to trim as many paths as possible
|
||||
a = append(a, "-asmflags", fmt.Sprintf("all=-trimpath=%s", cwd))
|
||||
a = append(a, "-gcflags", fmt.Sprintf("all=-trimpath=%s", cwd))
|
||||
} else {
|
||||
a = append(a, "-asmflags", fmt.Sprintf("-trimpath=%s", cwd))
|
||||
a = append(a, "-gcflags", fmt.Sprintf("-trimpath=%s", cwd))
|
||||
}
|
||||
|
||||
if enablePIE {
|
||||
a = append(a, "-buildmode=pie")
|
||||
}
|
||||
|
||||
a = append(a, args...)
|
||||
cmd := exec.Command("go", a...)
|
||||
cmd.Env = append(cleanEnv(), "GOPROXY=off")
|
||||
cmd.Env = os.Environ()
|
||||
for k, v := range env {
|
||||
cmd.Env = append(cmd.Env, k+"="+v)
|
||||
}
|
||||
@@ -280,6 +149,8 @@ func build(cwd string, env map[string]string, args ...string) error {
|
||||
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
|
||||
}
|
||||
|
||||
printEnv(cmd.Env)
|
||||
|
||||
cmd.Dir = cwd
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
@@ -294,7 +165,7 @@ func build(cwd string, env map[string]string, args ...string) error {
|
||||
func test(cwd string, env map[string]string, args ...string) error {
|
||||
args = append([]string{"test", "-count", "1"}, args...)
|
||||
cmd := exec.Command("go", args...)
|
||||
cmd.Env = append(cleanEnv(), "GOPROXY=off")
|
||||
cmd.Env = os.Environ()
|
||||
for k, v := range env {
|
||||
cmd.Env = append(cmd.Env, k+"="+v)
|
||||
}
|
||||
@@ -305,6 +176,8 @@ func test(cwd string, env map[string]string, args ...string) error {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
printEnv(cmd.Env)
|
||||
|
||||
verbosePrintf("chdir %q\n", cwd)
|
||||
verbosePrintf("go %q\n", args)
|
||||
|
||||
@@ -454,6 +327,10 @@ func (v GoVersion) String() string {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if !goVersion.AtLeast(GoVersion{1, 11, 0}) {
|
||||
die("Go version (%v) is too old, Go <= 1.11 does not support Go Modules\n", goVersion)
|
||||
}
|
||||
|
||||
if !goVersion.AtLeast(config.MinVersion) {
|
||||
fmt.Fprintf(os.Stderr, "%s detected, this program requires at least %s\n", goVersion, config.MinVersion)
|
||||
os.Exit(1)
|
||||
@@ -464,15 +341,13 @@ func main() {
|
||||
skipNext := false
|
||||
params := os.Args[1:]
|
||||
|
||||
goEnv := map[string]string{}
|
||||
buildEnv := map[string]string{
|
||||
"GOOS": runtime.GOOS,
|
||||
"GOARCH": runtime.GOARCH,
|
||||
"GOARM": "",
|
||||
env := map[string]string{
|
||||
"GO111MODULE": "on", // make sure we build in Module mode
|
||||
"GOOS": runtime.GOOS,
|
||||
"GOARCH": runtime.GOARCH,
|
||||
"GOARM": "",
|
||||
}
|
||||
|
||||
tempdir := ""
|
||||
|
||||
var outputFilename string
|
||||
|
||||
for i, arg := range params {
|
||||
@@ -484,8 +359,6 @@ func main() {
|
||||
switch arg {
|
||||
case "-v", "--verbose":
|
||||
verbose = true
|
||||
case "-k", "--keep-gopath":
|
||||
keepGopath = true
|
||||
case "-t", "-tags", "--tags":
|
||||
if i+1 >= len(params) {
|
||||
die("-t given but no tag specified")
|
||||
@@ -495,9 +368,6 @@ func main() {
|
||||
case "-o", "--output":
|
||||
skipNext = true
|
||||
outputFilename = params[i+1]
|
||||
case "--tempdir":
|
||||
skipNext = true
|
||||
tempdir = params[i+1]
|
||||
case "-T", "--test":
|
||||
runTests = true
|
||||
case "--enable-cgo":
|
||||
@@ -506,13 +376,13 @@ func main() {
|
||||
enablePIE = true
|
||||
case "--goos":
|
||||
skipNext = true
|
||||
buildEnv["GOOS"] = params[i+1]
|
||||
env["GOOS"] = params[i+1]
|
||||
case "--goarch":
|
||||
skipNext = true
|
||||
buildEnv["GOARCH"] = params[i+1]
|
||||
env["GOARCH"] = params[i+1]
|
||||
case "--goarm":
|
||||
skipNext = true
|
||||
buildEnv["GOARM"] = params[i+1]
|
||||
env["GOARM"] = params[i+1]
|
||||
case "-h":
|
||||
showUsage(os.Stdout)
|
||||
return
|
||||
@@ -525,8 +395,12 @@ func main() {
|
||||
|
||||
verbosePrintf("detected Go version %v\n", goVersion)
|
||||
|
||||
preserveSymbols := false
|
||||
for i := range buildTags {
|
||||
buildTags[i] = strings.TrimSpace(buildTags[i])
|
||||
if buildTags[i] == "debug" || buildTags[i] == "profile" {
|
||||
preserveSymbols = true
|
||||
}
|
||||
}
|
||||
|
||||
verbosePrintf("build tags: %s\n", buildTags)
|
||||
@@ -538,7 +412,7 @@ func main() {
|
||||
|
||||
if outputFilename == "" {
|
||||
outputFilename = config.Name
|
||||
if buildEnv["GOOS"] == "windows" {
|
||||
if env["GOOS"] == "windows" {
|
||||
outputFilename += ".exe"
|
||||
}
|
||||
}
|
||||
@@ -553,7 +427,11 @@ func main() {
|
||||
if version != "" {
|
||||
constants["main.version"] = version
|
||||
}
|
||||
ldflags := "-s -w " + constants.LDFlags()
|
||||
ldflags := constants.LDFlags()
|
||||
if !preserveSymbols {
|
||||
// Strip debug symbols.
|
||||
ldflags = "-s -w " + ldflags
|
||||
}
|
||||
verbosePrintf("ldflags: %s\n", ldflags)
|
||||
|
||||
var (
|
||||
@@ -567,54 +445,18 @@ func main() {
|
||||
}
|
||||
|
||||
buildTarget := filepath.FromSlash(mainPackage)
|
||||
buildCWD := ""
|
||||
|
||||
if goVersion.AtLeast(GoVersion{1, 11, 0}) && fileExists("go.mod") {
|
||||
verbosePrintf("Go >= 1.11 and 'go.mod' found, building with modules\n")
|
||||
buildCWD = root
|
||||
|
||||
buildArgs = append(buildArgs, "-mod=vendor")
|
||||
testArgs = append(testArgs, "-mod=vendor")
|
||||
} else {
|
||||
if tempdir == "" {
|
||||
tempdir, err = ioutil.TempDir("", fmt.Sprintf("%v-build-", config.Name))
|
||||
if err != nil {
|
||||
die("TempDir(): %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
verbosePrintf("Go < 1.11 or 'go.mod' not found, create GOPATH at %v\n", tempdir)
|
||||
targetdir := filepath.Join(tempdir, "src", filepath.FromSlash(config.Namespace))
|
||||
if err = copy(targetdir, root); err != nil {
|
||||
die("copying files from %v to %v/src failed: %v\n", root, tempdir, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if !keepGopath {
|
||||
verbosePrintf("remove %v\n", tempdir)
|
||||
if err = os.RemoveAll(tempdir); err != nil {
|
||||
die("remove GOPATH at %s failed: %v\n", tempdir, err)
|
||||
}
|
||||
} else {
|
||||
verbosePrintf("leaving temporary GOPATH at %v\n", tempdir)
|
||||
}
|
||||
}()
|
||||
|
||||
buildCWD = targetdir
|
||||
|
||||
goEnv["GOPATH"] = tempdir
|
||||
buildEnv["GOPATH"] = tempdir
|
||||
buildCWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
die("unable to determine current working directory: %v\n", err)
|
||||
}
|
||||
|
||||
verbosePrintf("environment:\n go: %v\n build: %v\n", goEnv, buildEnv)
|
||||
|
||||
buildArgs = append(buildArgs,
|
||||
"-tags", strings.Join(buildTags, " "),
|
||||
"-ldflags", ldflags,
|
||||
"-o", output, buildTarget,
|
||||
)
|
||||
|
||||
err = build(buildCWD, buildEnv, buildArgs...)
|
||||
err = build(buildCWD, env, buildArgs...)
|
||||
if err != nil {
|
||||
die("build failed: %v\n", err)
|
||||
}
|
||||
@@ -624,7 +466,7 @@ func main() {
|
||||
|
||||
testArgs = append(testArgs, config.Tests...)
|
||||
|
||||
err = test(buildCWD, goEnv, testArgs...)
|
||||
err = test(buildCWD, env, testArgs...)
|
||||
if err != nil {
|
||||
die("running tests failed: %v\n", err)
|
||||
}
|
||||
|
||||
6
changelog/0.9.6_2019-11-22/issue-2063
Normal file
6
changelog/0.9.6_2019-11-22/issue-2063
Normal file
@@ -0,0 +1,6 @@
|
||||
Bugfix: Allow absolute path for filename when backing up from stdin
|
||||
|
||||
When backing up from stdin, handle directory path for `--stdin-filename`.
|
||||
This can be used to specify the full path for the backed-up file.
|
||||
|
||||
https://github.com/restic/restic/issues/2063
|
||||
10
changelog/0.9.6_2019-11-22/issue-2174
Normal file
10
changelog/0.9.6_2019-11-22/issue-2174
Normal file
@@ -0,0 +1,10 @@
|
||||
Bugfix: Save files with invalid timestamps
|
||||
|
||||
When restic reads invalid timestamps (year is before 0000 or after 9999) it
|
||||
refused to read and archive the file. We've changed the behavior and will now
|
||||
save modified timestamps with the year set to either 0000 or 9999, the rest of
|
||||
the timestamp stays the same, so the file will be saved (albeit with a bogus
|
||||
timestamp).
|
||||
|
||||
https://github.com/restic/restic/issues/2174
|
||||
https://github.com/restic/restic/issues/1173
|
||||
15
changelog/0.9.6_2019-11-22/issue-2179
Normal file
15
changelog/0.9.6_2019-11-22/issue-2179
Normal file
@@ -0,0 +1,15 @@
|
||||
Enhancement: Use ctime when checking for file changes
|
||||
|
||||
Previously, restic only checked a file's mtime (along with other non-timestamp
|
||||
metadata) to decide if a file has changed. This could cause restic to not notice
|
||||
that a file has changed (and therefore continue to store the old version, as
|
||||
opposed to the modified version) if something edits the file and then resets the
|
||||
timestamp. Restic now also checks the ctime of files, so any modifications to a
|
||||
file should be noticed, and the modified file will be backed up. The ctime check
|
||||
will be disabled if the --ignore-inode flag was given.
|
||||
|
||||
If this change causes problems for you, please open an issue, and we can look in
|
||||
to adding a seperate flag to disable just the ctime check.
|
||||
|
||||
https://github.com/restic/restic/issues/2179
|
||||
https://github.com/restic/restic/pull/2212
|
||||
6
changelog/0.9.6_2019-11-22/issue-2249
Normal file
6
changelog/0.9.6_2019-11-22/issue-2249
Normal file
@@ -0,0 +1,6 @@
|
||||
Bugfix: Read fresh metadata for unmodified files
|
||||
|
||||
Restic took all metadata for files which were detected as unmodified, not taking into account changed metadata (ownership, mode). This is now corrected.
|
||||
|
||||
https://github.com/restic/restic/issues/2249
|
||||
https://github.com/restic/restic/pull/2252
|
||||
7
changelog/0.9.6_2019-11-22/issue-2301
Normal file
7
changelog/0.9.6_2019-11-22/issue-2301
Normal file
@@ -0,0 +1,7 @@
|
||||
Bugfix: Add upper bound for t in --read-data-subset=n/t
|
||||
|
||||
256 is the effective maximum for t, but restic would allow larger
|
||||
values, leading to strange behavior.
|
||||
|
||||
https://github.com/restic/restic/issues/2301
|
||||
https://github.com/restic/restic/pull/2304
|
||||
7
changelog/0.9.6_2019-11-22/issue-2306
Normal file
7
changelog/0.9.6_2019-11-22/issue-2306
Normal file
@@ -0,0 +1,7 @@
|
||||
Enhancement: Allow multiple retries for interactive password input
|
||||
|
||||
Restic used to quit if the repository password was typed incorrectly once.
|
||||
Restic will now ask the user again for the repository password if typed incorrectly.
|
||||
The user will now get three tries to input the correct password before restic quits.
|
||||
|
||||
https://github.com/restic/restic/issues/2306
|
||||
6
changelog/0.9.6_2019-11-22/issue-2330
Normal file
6
changelog/0.9.6_2019-11-22/issue-2330
Normal file
@@ -0,0 +1,6 @@
|
||||
Enhancement: Make `--group-by` accept both singular and plural
|
||||
|
||||
One can now use the values `host`/`hosts`, `path`/`paths` and
|
||||
`tag` / `tags` interchangeably in the `--group-by` argument.
|
||||
|
||||
https://github.com/restic/restic/issues/2330
|
||||
8
changelog/0.9.6_2019-11-22/pull-2321
Normal file
8
changelog/0.9.6_2019-11-22/pull-2321
Normal file
@@ -0,0 +1,8 @@
|
||||
Bugfix: Check errors when loading index files
|
||||
|
||||
Restic now checks and handles errors which occur when loading index files, the
|
||||
missing check leads to odd errors (and a stack trace printed to users) later.
|
||||
This was reported in the forum.
|
||||
|
||||
https://github.com/restic/restic/pull/2321
|
||||
https://forum.restic.net/t/check-rebuild-index-prune/1848/13
|
||||
8
changelog/0.9.6_2019-11-22/pull-2350
Normal file
8
changelog/0.9.6_2019-11-22/pull-2350
Normal file
@@ -0,0 +1,8 @@
|
||||
Enhancement: Add option to configure S3 region
|
||||
|
||||
We've added a new option for setting the region when accessing an S3-compatible
|
||||
service. For some providers, it is required to set this to a valid value. You
|
||||
can do that either by setting the environment variable `AWS_DEFAULT_REGION` or
|
||||
using the option `s3.region`, e.g. like this: `-o s3.region="us-east-1"`.
|
||||
|
||||
https://github.com/restic/restic/pull/2350
|
||||
@@ -16,7 +16,7 @@ Details
|
||||
{{ range $entry := .Entries }}{{ with $entry }}
|
||||
* {{ .Type }} #{{ .PrimaryID }}: {{ .Title }}
|
||||
{{ range $par := .Paragraphs }}
|
||||
{{ wrap $par 80 3 }}
|
||||
{{ wrapIndent $par 80 3 }}
|
||||
{{ end -}}
|
||||
{{ range $url := .IssueURLs }}
|
||||
{{ $url -}}
|
||||
|
||||
@@ -9,4 +9,4 @@ in case there aren't any issue links) is used as the primary ID.
|
||||
|
||||
https://github.com/restic/restic/issues/1234
|
||||
https://github.com/restic/restic/pull/55555
|
||||
https://forum.restic/.net/foo/bar/baz
|
||||
https://forum.restic.net/foo/bar/baz
|
||||
|
||||
0
changelog/unreleased/.gitkeep
Normal file
0
changelog/unreleased/.gitkeep
Normal file
9
changelog/unreleased/issue-1570
Normal file
9
changelog/unreleased/issue-1570
Normal file
@@ -0,0 +1,9 @@
|
||||
Enhancement: Support specifying multiple host flags for various commands
|
||||
|
||||
Previously commands didn't take more than one `--host` or `-H` argument into account, which could be limiting with e.g.
|
||||
the `forget` command.
|
||||
|
||||
The `dump`, `find`, `forget`, `ls`, `mount`, `restore`, `snapshots`, `stats` and `tag` commands will now take into account
|
||||
multiple `--host` and `-H` flags.
|
||||
|
||||
https://github.com/restic/restic/issues/1570
|
||||
5
changelog/unreleased/issue-2072
Normal file
5
changelog/unreleased/issue-2072
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Display snapshot date when using `restic find`
|
||||
|
||||
Added the respective snapshot date to the output of `restic find`.
|
||||
|
||||
https://github.com/restic/restic/issues/2072
|
||||
5
changelog/unreleased/issue-2277
Normal file
5
changelog/unreleased/issue-2277
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Add support for ppc64le
|
||||
|
||||
Adds support for ppc64le, the processor architecture from IBM.
|
||||
|
||||
https://github.com/restic/restic/issues/2277
|
||||
7
changelog/unreleased/issue-2281
Normal file
7
changelog/unreleased/issue-2281
Normal file
@@ -0,0 +1,7 @@
|
||||
Bugfix: Handle format verbs like '%' properly in `find` output
|
||||
|
||||
The JSON or "normal" output of the `find` command can now deal with file names
|
||||
that contain substrings which the Golang `fmt` package considers "format verbs"
|
||||
like `%s`.
|
||||
|
||||
https://github.com/restic/restic/issues/2281
|
||||
8
changelog/unreleased/issue-2298
Normal file
8
changelog/unreleased/issue-2298
Normal file
@@ -0,0 +1,8 @@
|
||||
Bugfix: Do not hang when run as a background job
|
||||
|
||||
Restic did hang on exit while restoring the terminal configuration when it was
|
||||
started as a background job, for example using `restic ... &`. This has been
|
||||
fixed by only restoring the terminal configuration when restic is interrupted
|
||||
while reading a password from the terminal.
|
||||
|
||||
https://github.com/restic/restic/issues/2298
|
||||
8
changelog/unreleased/issue-2389
Normal file
8
changelog/unreleased/issue-2389
Normal file
@@ -0,0 +1,8 @@
|
||||
Bugfix: Fix mangled json output of backup command
|
||||
|
||||
We've fixed a race condition in the json output of the backup command
|
||||
that could cause multiple lines to get mixed up. We've also ensured that
|
||||
the backup summary is printed last.
|
||||
|
||||
https://github.com/restic/restic/issues/2389
|
||||
https://github.com/restic/restic/pull/2545
|
||||
5
changelog/unreleased/issue-2390
Normal file
5
changelog/unreleased/issue-2390
Normal file
@@ -0,0 +1,5 @@
|
||||
Bugfix: Refresh lock timestamp
|
||||
|
||||
Long-running operations did not refresh lock timestamp, resulting in locks becoming stale. This is now fixed.
|
||||
|
||||
https://github.com/restic/restic/issues/2390
|
||||
6
changelog/unreleased/issue-2429
Normal file
6
changelog/unreleased/issue-2429
Normal file
@@ -0,0 +1,6 @@
|
||||
Bugfix: backup --json reports total_bytes_processed as 0
|
||||
|
||||
We've fixed the json output of total_bytes_processed. The non-json output
|
||||
was already fixed with pull request #2138 but left the json output untouched.
|
||||
|
||||
https://github.com/restic/restic/issues/2429
|
||||
5
changelog/unreleased/issue-2469
Normal file
5
changelog/unreleased/issue-2469
Normal file
@@ -0,0 +1,5 @@
|
||||
Bugfix: Fix incorrect bytes stats in `diff` command
|
||||
|
||||
In some cases, the wrong number of bytes (e.g. 16777215.998 TiB) were reported by the `diff` command. This is now fixed.
|
||||
|
||||
https://github.com/restic/restic/issues/2469
|
||||
9
changelog/unreleased/issue-2482
Normal file
9
changelog/unreleased/issue-2482
Normal file
@@ -0,0 +1,9 @@
|
||||
Change: Remove vendored dependencies
|
||||
|
||||
We've removed the vendored dependencies (in the subdir `vendor/`). When
|
||||
building restic, the Go compiler automatically fetches the dependencies. It
|
||||
will also cryptographically verify that the correct code has been fetched by
|
||||
using the hashes in `go.sum` (see the link to the documentation below).
|
||||
|
||||
https://github.com/restic/restic/issues/2482
|
||||
https://golang.org/cmd/go/#hdr-Module_downloading_and_verification
|
||||
16
changelog/unreleased/issue-2518
Normal file
16
changelog/unreleased/issue-2518
Normal file
@@ -0,0 +1,16 @@
|
||||
Bugfix: Do not crash with Synology NAS sftp server
|
||||
|
||||
It was found that when restic is used to store data on an sftp server on a
|
||||
Synology NAS with a relative path (one which does not start with a slash), it
|
||||
may go into an endless loop trying to create directories on the server. We've
|
||||
fixed this bug by using a function in the sftp library instead of our own
|
||||
implementation.
|
||||
|
||||
The bug was discovered because the Synology sftp server behaves erratic with
|
||||
non-absolute path (e.g. `home/restic-repo`). This can be resolved by just using
|
||||
an absolute path instead (`/home/restic-repo`). We've also added a paragraph in
|
||||
the FAQ.
|
||||
|
||||
https://github.com/restic/restic/issues/2518
|
||||
https://github.com/restic/restic/issues/2363
|
||||
https://github.com/restic/restic/pull/2530
|
||||
5
changelog/unreleased/issue-2531
Normal file
5
changelog/unreleased/issue-2531
Normal file
@@ -0,0 +1,5 @@
|
||||
Bugfix: Fix incorrect size calculation in `stats --mode restore-size`
|
||||
|
||||
The restore-size mode of stats was counting hard-linked files as if they were independent.
|
||||
|
||||
https://github.com/restic/restic/issues/2531
|
||||
5
changelog/unreleased/issue-2537
Normal file
5
changelog/unreleased/issue-2537
Normal file
@@ -0,0 +1,5 @@
|
||||
Bugfix: Fix incorrect file counts in `stats --mode restore-size`
|
||||
|
||||
The restore-size mode of stats was failing to count empty directories and some files with hard links.
|
||||
|
||||
https://github.com/restic/restic/issues/2537
|
||||
17
changelog/unreleased/pull-2195
Normal file
17
changelog/unreleased/pull-2195
Normal file
@@ -0,0 +1,17 @@
|
||||
Enhancement: Simplify and improve restore performance
|
||||
|
||||
Significantly improves restore performance of large files (i.e. 50M+):
|
||||
https://github.com/restic/restic/issues/2074
|
||||
https://forum.restic.net/t/restore-using-rclone-gdrive-backend-is-slow/1112/8
|
||||
https://forum.restic.net/t/degraded-restore-performance-s3-backend/1400
|
||||
|
||||
Fixes "not enough cache capacity" error during restore:
|
||||
https://github.com/restic/restic/issues/2244
|
||||
|
||||
NOTE: This new implementation does not guarantee order in which blobs
|
||||
are written to the target files and, for example, the last blob of a
|
||||
file can be written to the file before any of the preceeding file blobs.
|
||||
It is therefore possible to have gaps in the data written to the target
|
||||
files if restore fails or interrupted by the user.
|
||||
|
||||
https://github.com/restic/restic/pull/2195
|
||||
5
changelog/unreleased/pull-2423
Normal file
5
changelog/unreleased/pull-2423
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: support user@domain parsing as user
|
||||
|
||||
Added the ability for user@domain-like users to be authenticated over SFTP servers.
|
||||
|
||||
https://github.com/restic/restic/pull/2423
|
||||
7
changelog/unreleased/pull-2576
Normal file
7
changelog/unreleased/pull-2576
Normal file
@@ -0,0 +1,7 @@
|
||||
Enhancement: Improve the chunking algorithm
|
||||
|
||||
We've updated the chunker library responsible for splitting files into smaller
|
||||
blocks. It should improve the chunking throughput by 5-10% depending on the
|
||||
CPU.
|
||||
|
||||
https://github.com/restic/restic/pull/2576
|
||||
6
changelog/unreleased/pull-2592
Normal file
6
changelog/unreleased/pull-2592
Normal file
@@ -0,0 +1,6 @@
|
||||
Bugfix: SFTP backend supports IPv6 addresses
|
||||
|
||||
The SFTP backend now supports IPv6 addresses natively, without relying on
|
||||
aliases in the external SSH configuration.
|
||||
|
||||
https://github.com/restic/restic/pull/2592
|
||||
6
changelog/unreleased/pull-2600
Normal file
6
changelog/unreleased/pull-2600
Normal file
@@ -0,0 +1,6 @@
|
||||
Change: Require Go >= 1.11
|
||||
|
||||
Restic now requires Go to be at least 1.11. This allows simplifications in the
|
||||
build process and removing workarounds.
|
||||
|
||||
https://github.com/restic/restic/pull/2600
|
||||
7
changelog/unreleased/pull-2607
Normal file
7
changelog/unreleased/pull-2607
Normal file
@@ -0,0 +1,7 @@
|
||||
Bugfix: Honor RESTIC_CACHE_DIR environment variable on Mac and Windows
|
||||
|
||||
On Mac and Windows, the RESTIC_CACHE_DIR environment variable was ignored.
|
||||
This variable can now be used on all platforms to set the directory where
|
||||
restic stores caches.
|
||||
|
||||
https://github.com/restic/restic/pull/2607
|
||||
6
changelog/unreleased/pull-2668
Normal file
6
changelog/unreleased/pull-2668
Normal file
@@ -0,0 +1,6 @@
|
||||
Bugfix: Don't abort the stats command when data blobs are missing
|
||||
|
||||
Runing the stats command in the blobs-per-file mode on a repository with
|
||||
missing data blobs previously resulted in a crash.
|
||||
|
||||
https://github.com/restic/restic/pull/2668
|
||||
@@ -1,18 +1,20 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.6.4
|
||||
- 1.7.4
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
go: "1.9.x"
|
||||
- os: linux
|
||||
go: "1.10.x"
|
||||
- os: linux
|
||||
go: "tip"
|
||||
- os: osx
|
||||
go: "1.10.x"
|
||||
|
||||
install:
|
||||
- go get -t ./...
|
||||
- go get -u github.com/golang/lint/golint
|
||||
- go get -u golang.org/x/lint/golint
|
||||
- go get -u golang.org/x/tools/cmd/goimports
|
||||
|
||||
script:
|
||||
@@ -1,5 +1,5 @@
|
||||
[](http://godoc.org/github.com/restic/chunker)
|
||||
[](https://travis-ci.org/restic/chunker)
|
||||
[](https://travis-ci.com/restic/chunker)
|
||||
|
||||
The package `chunker` implements content-defined-chunking (CDC) based on a
|
||||
rolling Rabin Hash. The library is part of the [restic backup
|
||||
@@ -2,6 +2,7 @@ package chunker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
@@ -47,7 +48,7 @@ type Chunk struct {
|
||||
|
||||
type chunkerState struct {
|
||||
window [windowSize]byte
|
||||
wpos int
|
||||
wpos uint
|
||||
|
||||
buf []byte
|
||||
bpos uint
|
||||
@@ -225,6 +226,12 @@ func (c *Chunker) Next(data []byte) (Chunk, error) {
|
||||
tabout := c.tables.out
|
||||
tabmod := c.tables.mod
|
||||
polShift := c.polShift
|
||||
// go guarantees the expected behavior for bit shifts even for shift counts
|
||||
// larger than the value width. Bounding the value of polShift allows the compiler
|
||||
// to optimize the code for 'digest >> polShift'
|
||||
if polShift > 53-8 {
|
||||
return Chunk{}, errors.New("the polynomial must have a degree less than or equal 53")
|
||||
}
|
||||
minSize := c.MinSize
|
||||
maxSize := c.MaxSize
|
||||
buf := c.buf
|
||||
@@ -259,6 +266,10 @@ func (c *Chunker) Next(data []byte) (Chunk, error) {
|
||||
return Chunk{}, err
|
||||
}
|
||||
|
||||
if n < 0 {
|
||||
return Chunk{}, fmt.Errorf("ReadFull returned negative number of bytes read: %v", n)
|
||||
}
|
||||
|
||||
c.bpos = 0
|
||||
c.bmax = uint(n)
|
||||
}
|
||||
@@ -291,10 +302,12 @@ func (c *Chunker) Next(data []byte) (Chunk, error) {
|
||||
wpos := c.wpos
|
||||
for _, b := range buf[c.bpos:c.bmax] {
|
||||
// slide(b)
|
||||
// limit wpos before to elide array bound checks
|
||||
wpos = wpos % windowSize
|
||||
out := win[wpos]
|
||||
win[wpos] = b
|
||||
digest ^= uint64(tabout[out])
|
||||
wpos = (wpos + 1) % windowSize
|
||||
wpos++
|
||||
|
||||
// updateDigest
|
||||
index := byte(digest >> polShift)
|
||||
@@ -331,7 +344,7 @@ func (c *Chunker) Next(data []byte) (Chunk, error) {
|
||||
}
|
||||
c.digest = digest
|
||||
c.window = win
|
||||
c.wpos = wpos
|
||||
c.wpos = wpos % windowSize
|
||||
|
||||
steps := c.bmax - c.bpos
|
||||
if steps > 0 {
|
||||
347
chunker/chunker_test.go
Normal file
347
chunker/chunker_test.go
Normal file
@@ -0,0 +1,347 @@
|
||||
package chunker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseDigest(s string) []byte {
|
||||
d, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
type chunk struct {
|
||||
Length uint
|
||||
CutFP uint64
|
||||
Digest []byte
|
||||
}
|
||||
|
||||
// polynomial used for all the tests below
|
||||
const testPol = Pol(0x3DA3358B4DC173)
|
||||
|
||||
// created for 32MB of random data out of math/rand's Uint32() seeded by
|
||||
// constant 23
|
||||
//
|
||||
// chunking configuration:
|
||||
// window size 64, avg chunksize 1<<20, min chunksize 1<<19, max chunksize 1<<23
|
||||
// polynom 0x3DA3358B4DC173
|
||||
var chunks1 = []chunk{
|
||||
chunk{2163460, 0x000b98d4cdf00000, parseDigest("4b94cb2cf293855ea43bf766731c74969b91aa6bf3c078719aabdd19860d590d")},
|
||||
chunk{643703, 0x000d4e8364d00000, parseDigest("5727a63c0964f365ab8ed2ccf604912f2ea7be29759a2b53ede4d6841e397407")},
|
||||
chunk{1528956, 0x0015a25c2ef00000, parseDigest("a73759636a1e7a2758767791c69e81b69fb49236c6929e5d1b654e06e37674ba")},
|
||||
chunk{1955808, 0x00102a8242e00000, parseDigest("c955fb059409b25f07e5ae09defbbc2aadf117c97a3724e06ad4abd2787e6824")},
|
||||
chunk{2222372, 0x00045da878000000, parseDigest("6ba5e9f7e1b310722be3627716cf469be941f7f3e39a4c3bcefea492ec31ee56")},
|
||||
chunk{2538687, 0x00198a8179900000, parseDigest("8687937412f654b5cfe4a82b08f28393a0c040f77c6f95e26742c2fc4254bfde")},
|
||||
chunk{609606, 0x001d4e8d17100000, parseDigest("5da820742ff5feb3369112938d3095785487456f65a8efc4b96dac4be7ebb259")},
|
||||
chunk{1205738, 0x000a7204dd600000, parseDigest("cc70d8fad5472beb031b1aca356bcab86c7368f40faa24fe5f8922c6c268c299")},
|
||||
chunk{959742, 0x00183e71e1400000, parseDigest("4065bdd778f95676c92b38ac265d361f81bff17d76e5d9452cf985a2ea5a4e39")},
|
||||
chunk{4036109, 0x001fec043c700000, parseDigest("b9cf166e75200eb4993fc9b6e22300a6790c75e6b0fc8f3f29b68a752d42f275")},
|
||||
chunk{1525894, 0x000b1574b1500000, parseDigest("2f238180e4ca1f7520a05f3d6059233926341090f9236ce677690c1823eccab3")},
|
||||
chunk{1352720, 0x00018965f2e00000, parseDigest("afd12f13286a3901430de816e62b85cc62468c059295ce5888b76b3af9028d84")},
|
||||
chunk{811884, 0x00155628aa100000, parseDigest("42d0cdb1ee7c48e552705d18e061abb70ae7957027db8ae8db37ec756472a70a")},
|
||||
chunk{1282314, 0x001909a0a1400000, parseDigest("819721c2457426eb4f4c7565050c44c32076a56fa9b4515a1c7796441730eb58")},
|
||||
chunk{1318021, 0x001cceb980000000, parseDigest("842eb53543db55bacac5e25cb91e43cc2e310fe5f9acc1aee86bdf5e91389374")},
|
||||
chunk{948640, 0x0011f7a470a00000, parseDigest("b8e36bf7019bb96ac3fb7867659d2167d9d3b3148c09fe0de45850b8fe577185")},
|
||||
chunk{645464, 0x00030ce2d9400000, parseDigest("5584bd27982191c3329f01ed846bfd266e96548dfa87018f745c33cfc240211d")},
|
||||
chunk{533758, 0x0004435c53c00000, parseDigest("4da778a25b72a9a0d53529eccfe2e5865a789116cb1800f470d8df685a8ab05d")},
|
||||
chunk{1128303, 0x0000c48517800000, parseDigest("08c6b0b38095b348d80300f0be4c5184d2744a17147c2cba5cc4315abf4c048f")},
|
||||
chunk{800374, 0x000968473f900000, parseDigest("820284d2c8fd243429674c996d8eb8d3450cbc32421f43113e980f516282c7bf")},
|
||||
chunk{2453512, 0x001e197c92600000, parseDigest("5fa870ed107c67704258e5e50abe67509fb73562caf77caa843b5f243425d853")},
|
||||
chunk{2651975, 0x000ae6c868000000, parseDigest("181347d2bbec32bef77ad5e9001e6af80f6abcf3576549384d334ee00c1988d8")},
|
||||
chunk{237392, 0x0000000000000001, parseDigest("fcd567f5d866357a8e299fd5b2359bb2c8157c30395229c4e9b0a353944a7978")},
|
||||
}
|
||||
|
||||
// test if nullbytes are correctly split, even if length is a multiple of MinSize.
|
||||
var chunks2 = []chunk{
|
||||
chunk{MinSize, 0, parseDigest("07854d2fef297a06ba81685e660c332de36d5d18d546927d30daad6d7fda1541")},
|
||||
chunk{MinSize, 0, parseDigest("07854d2fef297a06ba81685e660c332de36d5d18d546927d30daad6d7fda1541")},
|
||||
chunk{MinSize, 0, parseDigest("07854d2fef297a06ba81685e660c332de36d5d18d546927d30daad6d7fda1541")},
|
||||
chunk{MinSize, 0, parseDigest("07854d2fef297a06ba81685e660c332de36d5d18d546927d30daad6d7fda1541")},
|
||||
}
|
||||
|
||||
// the same as chunks1, but avg chunksize is 1<<19
|
||||
var chunks3 = []chunk{
|
||||
chunk{1491586, 0x00023e586ea80000, parseDigest("4c008237df602048039287427171cef568a6cb965d1b5ca28dc80504a24bb061")},
|
||||
chunk{671874, 0x000b98d4cdf00000, parseDigest("fa8a42321b90c3d4ce9dd850562b2fd0c0fe4bdd26cf01a24f22046a224225d3")},
|
||||
chunk{643703, 0x000d4e8364d00000, parseDigest("5727a63c0964f365ab8ed2ccf604912f2ea7be29759a2b53ede4d6841e397407")},
|
||||
chunk{1284146, 0x0012b527e4780000, parseDigest("16d04cafecbeae9eaedd49da14c7ad7cdc2b1cc8569e5c16c32c9fb045aa899a")},
|
||||
chunk{823366, 0x000d1d6752180000, parseDigest("48662c118514817825ad4761e8e2e5f28f9bd8281b07e95dcafc6d02e0aa45c3")},
|
||||
chunk{810134, 0x0016071b6e180000, parseDigest("f629581aa05562f97f2c359890734c8574c5575da32f9289c5ba70bfd05f3f46")},
|
||||
chunk{567118, 0x00102a8242e00000, parseDigest("d4f0797c56c60d01bac33bfd49957a4816b6c067fc155b026de8a214cab4d70a")},
|
||||
chunk{821315, 0x001b3e42c8180000, parseDigest("8ebd0fd5db0293bd19140da936eb8b1bbd3cd6ffbec487385b956790014751ca")},
|
||||
chunk{1401057, 0x00045da878000000, parseDigest("001360af59adf4871ef138cfa2bb49007e86edaf5ac2d6f0b3d3014510991848")},
|
||||
chunk{2311122, 0x0005cbd885380000, parseDigest("8276d489b566086d9da95dc5c5fe6fc7d72646dd3308ced6b5b6ddb8595f0aa1")},
|
||||
chunk{608723, 0x001cfcd86f280000, parseDigest("518db33ba6a79d4f3720946f3785c05b9611082586d47ea58390fc2f6de9449e")},
|
||||
chunk{980456, 0x0013edb7a7f80000, parseDigest("0121b1690738395e15fecba1410cd0bf13fde02225160cad148829f77e7b6c99")},
|
||||
chunk{1140278, 0x0001f9f017e80000, parseDigest("28ca7c74804b5075d4f5eeb11f0845d99f62e8ea3a42b9a05c7bd5f2fca619dd")},
|
||||
chunk{2015542, 0x00097bf5d8180000, parseDigest("6fe8291f427d48650a5f0f944305d3a2dbc649bd401d2655fc0bdd42e890ca5a")},
|
||||
chunk{904752, 0x000e1863eff80000, parseDigest("62af1f1eb3f588d18aff28473303cc4731fc3cafcc52ce818fee3c4c2820854d")},
|
||||
chunk{713072, 0x001f3bb1b9b80000, parseDigest("4bda9dc2e3031d004d87a5cc93fe5207c4b0843186481b8f31597dc6ffa1496c")},
|
||||
chunk{675937, 0x001fec043c700000, parseDigest("5299c8c5acec1b90bb020cd75718aab5e12abb9bf66291465fd10e6a823a8b4a")},
|
||||
chunk{1525894, 0x000b1574b1500000, parseDigest("2f238180e4ca1f7520a05f3d6059233926341090f9236ce677690c1823eccab3")},
|
||||
chunk{1352720, 0x00018965f2e00000, parseDigest("afd12f13286a3901430de816e62b85cc62468c059295ce5888b76b3af9028d84")},
|
||||
chunk{811884, 0x00155628aa100000, parseDigest("42d0cdb1ee7c48e552705d18e061abb70ae7957027db8ae8db37ec756472a70a")},
|
||||
chunk{1282314, 0x001909a0a1400000, parseDigest("819721c2457426eb4f4c7565050c44c32076a56fa9b4515a1c7796441730eb58")},
|
||||
chunk{1093738, 0x0017f5d048880000, parseDigest("5dddfa7a241b68f65d267744bdb082ee865f3c2f0d8b946ea0ee47868a01bbff")},
|
||||
chunk{962003, 0x000b921f7ef80000, parseDigest("0cb5c9ebba196b441c715c8d805f6e7143a81cd5b0d2c65c6aacf59ca9124af9")},
|
||||
chunk{856384, 0x00030ce2d9400000, parseDigest("7734b206d46f3f387e8661e81edf5b1a91ea681867beb5831c18aaa86632d7fb")},
|
||||
chunk{533758, 0x0004435c53c00000, parseDigest("4da778a25b72a9a0d53529eccfe2e5865a789116cb1800f470d8df685a8ab05d")},
|
||||
chunk{1128303, 0x0000c48517800000, parseDigest("08c6b0b38095b348d80300f0be4c5184d2744a17147c2cba5cc4315abf4c048f")},
|
||||
chunk{800374, 0x000968473f900000, parseDigest("820284d2c8fd243429674c996d8eb8d3450cbc32421f43113e980f516282c7bf")},
|
||||
chunk{2453512, 0x001e197c92600000, parseDigest("5fa870ed107c67704258e5e50abe67509fb73562caf77caa843b5f243425d853")},
|
||||
chunk{665901, 0x00118c842cb80000, parseDigest("deceec26163842fdef6560311c69bf8a9871a56e16d719e2c4b7e4d668ceb61f")},
|
||||
chunk{1986074, 0x000ae6c868000000, parseDigest("64cd64bf3c3bc389eb20df8310f0427d1c36ab2eaaf09e346bfa7f0453fc1a18")},
|
||||
chunk{237392, 0x0000000000000001, parseDigest("fcd567f5d866357a8e299fd5b2359bb2c8157c30395229c4e9b0a353944a7978")},
|
||||
}
|
||||
|
||||
func testWithData(t *testing.T, chnker *Chunker, testChunks []chunk, checkDigest bool) []Chunk {
|
||||
chunks := []Chunk{}
|
||||
|
||||
pos := uint(0)
|
||||
for i, chunk := range testChunks {
|
||||
c, err := chnker.Next(nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error returned with chunk %d: %v", i, err)
|
||||
}
|
||||
|
||||
if c.Start != pos {
|
||||
t.Fatalf("Start for chunk %d does not match: expected %d, got %d",
|
||||
i, pos, c.Start)
|
||||
}
|
||||
|
||||
if c.Length != chunk.Length {
|
||||
t.Fatalf("Length for chunk %d does not match: expected %d, got %d",
|
||||
i, chunk.Length, c.Length)
|
||||
}
|
||||
|
||||
if c.Cut != chunk.CutFP {
|
||||
t.Fatalf("Cut fingerprint for chunk %d/%d does not match: expected %016x, got %016x",
|
||||
i, len(chunks)-1, chunk.CutFP, c.Cut)
|
||||
}
|
||||
|
||||
if checkDigest {
|
||||
digest := hashData(c.Data)
|
||||
if !bytes.Equal(chunk.Digest, digest) {
|
||||
t.Fatalf("Digest fingerprint for chunk %d/%d does not match: expected %02x, got %02x",
|
||||
i, len(chunks)-1, chunk.Digest, digest)
|
||||
}
|
||||
}
|
||||
|
||||
pos += c.Length
|
||||
chunks = append(chunks, c)
|
||||
}
|
||||
|
||||
_, err := chnker.Next(nil)
|
||||
if err != io.EOF {
|
||||
t.Fatal("Wrong error returned after last chunk")
|
||||
}
|
||||
|
||||
if len(chunks) != len(testChunks) {
|
||||
t.Fatal("Amounts of test and resulting chunks do not match")
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
func getRandom(seed int64, count int) []byte {
|
||||
buf := make([]byte, count)
|
||||
|
||||
rnd := rand.New(rand.NewSource(seed))
|
||||
for i := 0; i < count; i += 4 {
|
||||
r := rnd.Uint32()
|
||||
buf[i] = byte(r)
|
||||
buf[i+1] = byte(r >> 8)
|
||||
buf[i+2] = byte(r >> 16)
|
||||
buf[i+3] = byte(r >> 24)
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func hashData(d []byte) []byte {
|
||||
h := sha256.New()
|
||||
h.Write(d)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func TestChunker(t *testing.T) {
|
||||
// setup data source
|
||||
buf := getRandom(23, 32*1024*1024)
|
||||
ch := New(bytes.NewReader(buf), testPol)
|
||||
testWithData(t, ch, chunks1, true)
|
||||
|
||||
// setup nullbyte data source
|
||||
buf = bytes.Repeat([]byte{0}, len(chunks2)*MinSize)
|
||||
ch = New(bytes.NewReader(buf), testPol)
|
||||
|
||||
testWithData(t, ch, chunks2, true)
|
||||
}
|
||||
|
||||
func TestChunkerWithCustomAverageBits(t *testing.T) {
|
||||
buf := getRandom(23, 32*1024*1024)
|
||||
ch := New(bytes.NewReader(buf), testPol)
|
||||
|
||||
// sligthly decrease averageBits to get more chunks
|
||||
ch.SetAverageBits(19)
|
||||
testWithData(t, ch, chunks3, true)
|
||||
}
|
||||
|
||||
func TestChunkerReset(t *testing.T) {
|
||||
buf := getRandom(23, 32*1024*1024)
|
||||
ch := New(bytes.NewReader(buf), testPol)
|
||||
testWithData(t, ch, chunks1, true)
|
||||
|
||||
ch.Reset(bytes.NewReader(buf), testPol)
|
||||
testWithData(t, ch, chunks1, true)
|
||||
}
|
||||
|
||||
func TestChunkerWithRandomPolynomial(t *testing.T) {
|
||||
// setup data source
|
||||
buf := getRandom(23, 32*1024*1024)
|
||||
|
||||
// generate a new random polynomial
|
||||
start := time.Now()
|
||||
p, err := RandomPolynomial()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("generating random polynomial took %v", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
ch := New(bytes.NewReader(buf), p)
|
||||
t.Logf("creating chunker took %v", time.Since(start))
|
||||
|
||||
// make sure that first chunk is different
|
||||
c, err := ch.Next(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if c.Cut == chunks1[0].CutFP {
|
||||
t.Fatal("Cut point is the same")
|
||||
}
|
||||
|
||||
if c.Length == chunks1[0].Length {
|
||||
t.Fatal("Length is the same")
|
||||
}
|
||||
|
||||
if bytes.Equal(hashData(c.Data), chunks1[0].Digest) {
|
||||
t.Fatal("Digest is the same")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChunkerWithoutHash(t *testing.T) {
|
||||
// setup data source
|
||||
buf := getRandom(23, 32*1024*1024)
|
||||
|
||||
ch := New(bytes.NewReader(buf), testPol)
|
||||
chunks := testWithData(t, ch, chunks1, false)
|
||||
|
||||
// test reader
|
||||
for i, c := range chunks {
|
||||
if uint(len(c.Data)) != chunks1[i].Length {
|
||||
t.Fatalf("reader returned wrong number of bytes: expected %d, got %d",
|
||||
chunks1[i].Length, len(c.Data))
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[c.Start:c.Start+c.Length], c.Data) {
|
||||
t.Fatalf("invalid data for chunk returned: expected %02x, got %02x",
|
||||
buf[c.Start:c.Start+c.Length], c.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// setup nullbyte data source
|
||||
buf = bytes.Repeat([]byte{0}, len(chunks2)*MinSize)
|
||||
ch = New(bytes.NewReader(buf), testPol)
|
||||
|
||||
testWithData(t, ch, chunks2, false)
|
||||
}
|
||||
|
||||
func benchmarkChunker(b *testing.B, checkDigest bool) {
|
||||
size := 32 * 1024 * 1024
|
||||
rd := bytes.NewReader(getRandom(23, size))
|
||||
ch := New(rd, testPol)
|
||||
buf := make([]byte, MaxSize)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
||||
var chunks int
|
||||
for i := 0; i < b.N; i++ {
|
||||
chunks = 0
|
||||
|
||||
_, err := rd.Seek(0, 0)
|
||||
if err != nil {
|
||||
b.Fatalf("Seek() return error %v", err)
|
||||
}
|
||||
|
||||
ch.Reset(rd, testPol)
|
||||
|
||||
cur := 0
|
||||
for {
|
||||
chunk, err := ch.Next(buf)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected error occurred: %v", err)
|
||||
}
|
||||
|
||||
if chunk.Length != chunks1[cur].Length {
|
||||
b.Errorf("wrong chunk length, want %d, got %d",
|
||||
chunks1[cur].Length, chunk.Length)
|
||||
}
|
||||
|
||||
if chunk.Cut != chunks1[cur].CutFP {
|
||||
b.Errorf("wrong cut fingerprint, want 0x%x, got 0x%x",
|
||||
chunks1[cur].CutFP, chunk.Cut)
|
||||
}
|
||||
|
||||
if checkDigest {
|
||||
h := hashData(chunk.Data)
|
||||
if !bytes.Equal(h, chunks1[cur].Digest) {
|
||||
b.Errorf("wrong digest, want %x, got %x",
|
||||
chunks1[cur].Digest, h)
|
||||
}
|
||||
}
|
||||
|
||||
chunks++
|
||||
cur++
|
||||
}
|
||||
}
|
||||
|
||||
b.Logf("%d chunks, average chunk size: %d bytes", chunks, size/chunks)
|
||||
}
|
||||
|
||||
func BenchmarkChunkerWithSHA256(b *testing.B) {
|
||||
benchmarkChunker(b, true)
|
||||
}
|
||||
|
||||
func BenchmarkChunker(b *testing.B) {
|
||||
benchmarkChunker(b, false)
|
||||
}
|
||||
|
||||
func BenchmarkNewChunker(b *testing.B) {
|
||||
p, err := RandomPolynomial()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
New(bytes.NewBuffer(nil), p)
|
||||
}
|
||||
}
|
||||
39
chunker/example_test.go
Normal file
39
chunker/example_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package chunker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ExampleChunker() {
|
||||
// generate 32MiB of deterministic pseudo-random data
|
||||
data := getRandom(23, 32*1024*1024)
|
||||
|
||||
// create a chunker
|
||||
chunker := New(bytes.NewReader(data), Pol(0x3DA3358B4DC173))
|
||||
|
||||
// reuse this buffer
|
||||
buf := make([]byte, 8*1024*1024)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
chunk, err := chunker.Next(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%d %02x\n", chunk.Length, sha256.Sum256(chunk.Data))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 2163460 4b94cb2cf293855ea43bf766731c74969b91aa6bf3c078719aabdd19860d590d
|
||||
// 643703 5727a63c0964f365ab8ed2ccf604912f2ea7be29759a2b53ede4d6841e397407
|
||||
// 1528956 a73759636a1e7a2758767791c69e81b69fb49236c6929e5d1b654e06e37674ba
|
||||
// 1955808 c955fb059409b25f07e5ae09defbbc2aadf117c97a3724e06ad4abd2787e6824
|
||||
// 2222372 6ba5e9f7e1b310722be3627716cf469be941f7f3e39a4c3bcefea492ec31ee56
|
||||
}
|
||||
3
chunker/go.mod
Normal file
3
chunker/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/restic/chunker
|
||||
|
||||
go 1.14
|
||||
@@ -94,7 +94,6 @@ func (x Pol) Deg() int {
|
||||
}
|
||||
|
||||
if uint64(x)&0x2 > 0 {
|
||||
x >>= 1
|
||||
r |= 1
|
||||
}
|
||||
|
||||
424
chunker/polynomials_test.go
Normal file
424
chunker/polynomials_test.go
Normal file
@@ -0,0 +1,424 @@
|
||||
package chunker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var polAddTests = []struct {
|
||||
x, y Pol
|
||||
sum Pol
|
||||
}{
|
||||
{23, 16, 23 ^ 16},
|
||||
{0x9a7e30d1e855e0a0, 0x670102a1f4bcd414, 0xfd7f32701ce934b4},
|
||||
{0x9a7e30d1e855e0a0, 0x9a7e30d1e855e0a0, 0},
|
||||
}
|
||||
|
||||
func TestPolAdd(t *testing.T) {
|
||||
for i, test := range polAddTests {
|
||||
if test.sum != test.x.Add(test.y) {
|
||||
t.Errorf("test %d failed: sum != x+y", i)
|
||||
}
|
||||
|
||||
if test.sum != test.y.Add(test.x) {
|
||||
t.Errorf("test %d failed: sum != y+x", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseBin(s string) Pol {
|
||||
i, err := strconv.ParseUint(s, 2, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return Pol(i)
|
||||
}
|
||||
|
||||
var polMulTests = []struct {
|
||||
x, y Pol
|
||||
res Pol
|
||||
}{
|
||||
{1, 2, 2},
|
||||
{
|
||||
parseBin("1101"),
|
||||
parseBin("10"),
|
||||
parseBin("11010"),
|
||||
},
|
||||
{
|
||||
parseBin("1101"),
|
||||
parseBin("11"),
|
||||
parseBin("10111"),
|
||||
},
|
||||
{
|
||||
0x40000000,
|
||||
0x40000000,
|
||||
0x1000000000000000,
|
||||
},
|
||||
{
|
||||
parseBin("1010"),
|
||||
parseBin("100100"),
|
||||
parseBin("101101000"),
|
||||
},
|
||||
{
|
||||
parseBin("100"),
|
||||
parseBin("11"),
|
||||
parseBin("1100"),
|
||||
},
|
||||
{
|
||||
parseBin("11"),
|
||||
parseBin("110101"),
|
||||
parseBin("1011111"),
|
||||
},
|
||||
{
|
||||
parseBin("10011"),
|
||||
parseBin("110101"),
|
||||
parseBin("1100001111"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestPolMul(t *testing.T) {
|
||||
for i, test := range polMulTests {
|
||||
m := test.x.Mul(test.y)
|
||||
if test.res != m {
|
||||
t.Errorf("TestPolMul failed for test %d: %v * %v: want %v, got %v",
|
||||
i, test.x, test.y, test.res, m)
|
||||
}
|
||||
m = test.y.Mul(test.x)
|
||||
if test.res != test.y.Mul(test.x) {
|
||||
t.Errorf("TestPolMul failed for %d: %v * %v: want %v, got %v",
|
||||
i, test.x, test.y, test.res, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolMulOverflow(t *testing.T) {
|
||||
defer func() {
|
||||
// try to recover overflow error
|
||||
err := recover()
|
||||
|
||||
if e, ok := err.(string); ok && e == "multiplication would overflow uint64" {
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("invalid error raised: %v", err)
|
||||
// re-raise error if not overflow
|
||||
panic(err)
|
||||
}()
|
||||
|
||||
x := Pol(1 << 63)
|
||||
x.Mul(2)
|
||||
t.Fatal("overflow test did not panic")
|
||||
}
|
||||
|
||||
var polDivTests = []struct {
|
||||
x, y Pol
|
||||
res Pol
|
||||
}{
|
||||
{10, 50, 0},
|
||||
{0, 1, 0},
|
||||
{
|
||||
parseBin("101101000"), // 0x168
|
||||
parseBin("1010"), // 0xa
|
||||
parseBin("100100"), // 0x24
|
||||
},
|
||||
{2, 2, 1},
|
||||
{
|
||||
0x8000000000000000,
|
||||
0x8000000000000000,
|
||||
1,
|
||||
},
|
||||
{
|
||||
parseBin("1100"),
|
||||
parseBin("100"),
|
||||
parseBin("11"),
|
||||
},
|
||||
{
|
||||
parseBin("1100001111"),
|
||||
parseBin("10011"),
|
||||
parseBin("110101"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestPolDiv(t *testing.T) {
|
||||
for i, test := range polDivTests {
|
||||
m := test.x.Div(test.y)
|
||||
if test.res != m {
|
||||
t.Errorf("TestPolDiv failed for test %d: %v * %v: want %v, got %v",
|
||||
i, test.x, test.y, test.res, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolDeg(t *testing.T) {
|
||||
var x Pol
|
||||
if x.Deg() != -1 {
|
||||
t.Errorf("deg(0) is not -1: %v", x.Deg())
|
||||
}
|
||||
|
||||
x = 1
|
||||
if x.Deg() != 0 {
|
||||
t.Errorf("deg(1) is not 0: %v", x.Deg())
|
||||
}
|
||||
|
||||
for i := 0; i < 64; i++ {
|
||||
x = 1 << uint(i)
|
||||
if x.Deg() != i {
|
||||
t.Errorf("deg(1<<%d) is not %d: %v", i, i, x.Deg())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var polModTests = []struct {
|
||||
x, y Pol
|
||||
res Pol
|
||||
}{
|
||||
{10, 50, 10},
|
||||
{0, 1, 0},
|
||||
{
|
||||
parseBin("101101001"),
|
||||
parseBin("1010"),
|
||||
parseBin("1"),
|
||||
},
|
||||
{2, 2, 0},
|
||||
{
|
||||
0x8000000000000000,
|
||||
0x8000000000000000,
|
||||
0,
|
||||
},
|
||||
{
|
||||
parseBin("1100"),
|
||||
parseBin("100"),
|
||||
parseBin("0"),
|
||||
},
|
||||
{
|
||||
parseBin("1100001111"),
|
||||
parseBin("10011"),
|
||||
parseBin("0"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestPolModt(t *testing.T) {
|
||||
for i, test := range polModTests {
|
||||
res := test.x.Mod(test.y)
|
||||
if test.res != res {
|
||||
t.Errorf("test %d failed: want %v, got %v", i, test.res, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPolDivMod(t *testing.B) {
|
||||
f := Pol(0x2482734cacca49)
|
||||
g := Pol(0x3af4b284899)
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
g.DivMod(f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPolDiv(t *testing.B) {
|
||||
f := Pol(0x2482734cacca49)
|
||||
g := Pol(0x3af4b284899)
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
g.Div(f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPolMod(t *testing.B) {
|
||||
f := Pol(0x2482734cacca49)
|
||||
g := Pol(0x3af4b284899)
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
g.Mod(f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPolDeg(t *testing.B) {
|
||||
f := Pol(0x3af4b284899)
|
||||
d := f.Deg()
|
||||
if d != 41 {
|
||||
t.Fatalf("BenchmalPolDeg: Wrong degree %d returned, expected %d",
|
||||
d, 41)
|
||||
}
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
f.Deg()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomPolynomial(t *testing.T) {
|
||||
_, err := RandomPolynomial()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRandomPolynomial(t *testing.B) {
|
||||
for i := 0; i < t.N; i++ {
|
||||
_, err := RandomPolynomial()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandPolynomial(t *testing.T) {
|
||||
pol := Pol(0x3DA3358B4DC173)
|
||||
s := pol.Expand()
|
||||
if s != "x^53+x^52+x^51+x^50+x^48+x^47+x^45+x^41+x^40+x^37+x^36+x^34+x^32+x^31+x^27+x^25+x^24+x^22+x^19+x^18+x^16+x^15+x^14+x^8+x^6+x^5+x^4+x+1" {
|
||||
t.Fatal("wrong result")
|
||||
}
|
||||
}
|
||||
|
||||
var polIrredTests = []struct {
|
||||
f Pol
|
||||
irred bool
|
||||
}{
|
||||
{0x38f1e565e288df, false},
|
||||
{0x3DA3358B4DC173, true},
|
||||
{0x30a8295b9d5c91, false},
|
||||
{0x255f4350b962cb, false},
|
||||
{0x267f776110a235, false},
|
||||
{0x2f4dae10d41227, false},
|
||||
{0x2482734cacca49, true},
|
||||
{0x312daf4b284899, false},
|
||||
{0x29dfb6553d01d1, false},
|
||||
{0x3548245eb26257, false},
|
||||
{0x3199e7ef4211b3, false},
|
||||
{0x362f39017dae8b, false},
|
||||
{0x200d57aa6fdacb, false},
|
||||
{0x35e0a4efa1d275, false},
|
||||
{0x2ced55b026577f, false},
|
||||
{0x260b012010893d, false},
|
||||
{0x2df29cbcd59e9d, false},
|
||||
{0x3f2ac7488bd429, false},
|
||||
{0x3e5cb1711669fb, false},
|
||||
{0x226d8de57a9959, false},
|
||||
{0x3c8de80aaf5835, false},
|
||||
{0x2026a59efb219b, false},
|
||||
{0x39dfa4d13fb231, false},
|
||||
{0x3143d0464b3299, false},
|
||||
}
|
||||
|
||||
func TestPolIrreducible(t *testing.T) {
|
||||
for _, test := range polIrredTests {
|
||||
if test.f.Irreducible() != test.irred {
|
||||
t.Errorf("Irreducibility test for Polynomial %v failed: got %v, wanted %v",
|
||||
test.f, test.f.Irreducible(), test.irred)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPolIrreducible(b *testing.B) {
|
||||
// find first irreducible polynomial
|
||||
var pol Pol
|
||||
for _, test := range polIrredTests {
|
||||
if test.irred {
|
||||
pol = test.f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if !pol.Irreducible() {
|
||||
b.Errorf("Irreducibility test for Polynomial %v failed", pol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var polGCDTests = []struct {
|
||||
f1 Pol
|
||||
f2 Pol
|
||||
gcd Pol
|
||||
}{
|
||||
{10, 50, 2},
|
||||
{0, 1, 1},
|
||||
{
|
||||
parseBin("101101001"),
|
||||
parseBin("1010"),
|
||||
parseBin("1"),
|
||||
},
|
||||
{2, 2, 2},
|
||||
{
|
||||
parseBin("1010"),
|
||||
parseBin("11"),
|
||||
parseBin("11"),
|
||||
},
|
||||
{
|
||||
0x8000000000000000,
|
||||
0x8000000000000000,
|
||||
0x8000000000000000,
|
||||
},
|
||||
{
|
||||
parseBin("1100"),
|
||||
parseBin("101"),
|
||||
parseBin("11"),
|
||||
},
|
||||
{
|
||||
parseBin("1100001111"),
|
||||
parseBin("10011"),
|
||||
parseBin("10011"),
|
||||
},
|
||||
{
|
||||
0x3DA3358B4DC173,
|
||||
0x3DA3358B4DC173,
|
||||
0x3DA3358B4DC173,
|
||||
},
|
||||
{
|
||||
0x3DA3358B4DC173,
|
||||
0x230d2259defd,
|
||||
1,
|
||||
},
|
||||
{
|
||||
0x230d2259defd,
|
||||
0x51b492b3eff2,
|
||||
parseBin("10011"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestPolGCD(t *testing.T) {
|
||||
for i, test := range polGCDTests {
|
||||
gcd := test.f1.GCD(test.f2)
|
||||
if test.gcd != gcd {
|
||||
t.Errorf("GCD test %d (%+v) failed: got %v, wanted %v",
|
||||
i, test, gcd, test.gcd)
|
||||
}
|
||||
|
||||
gcd = test.f2.GCD(test.f1)
|
||||
if test.gcd != gcd {
|
||||
t.Errorf("GCD test %d (%+v) failed: got %v, wanted %v",
|
||||
i, test, gcd, test.gcd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var polMulModTests = []struct {
|
||||
f1 Pol
|
||||
f2 Pol
|
||||
g Pol
|
||||
mod Pol
|
||||
}{
|
||||
{
|
||||
0x1230,
|
||||
0x230,
|
||||
0x55,
|
||||
0x22,
|
||||
},
|
||||
{
|
||||
0x0eae8c07dbbb3026,
|
||||
0xd5d6db9de04771de,
|
||||
0xdd2bda3b77c9,
|
||||
0x425ae8595b7a,
|
||||
},
|
||||
}
|
||||
|
||||
func TestPolMulMod(t *testing.T) {
|
||||
for i, test := range polMulModTests {
|
||||
mod := test.f1.MulMod(test.f2, test.g)
|
||||
if mod != test.mod {
|
||||
t.Errorf("MulMod test %d (%+v) failed: got %v, wanted %v",
|
||||
i, test, mod, test.mod)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ var cleanupHandlers struct {
|
||||
var stderr = os.Stderr
|
||||
|
||||
func init() {
|
||||
cleanupHandlers.ch = make(chan os.Signal)
|
||||
cleanupHandlers.ch = make(chan os.Signal, 1)
|
||||
go CleanupHandler(cleanupHandlers.ch)
|
||||
signal.Notify(cleanupHandlers.ch, syscall.SIGINT)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/textfile"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/jsonstatus"
|
||||
"github.com/restic/restic/internal/ui/json"
|
||||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
@@ -34,6 +35,14 @@ var cmdBackup = &cobra.Command{
|
||||
Long: `
|
||||
The "backup" command creates a new snapshot and saves the files and directories
|
||||
given as the arguments.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
|
||||
Note that some issues such as unreadable or deleted files during backup
|
||||
currently doesn't result in a non-zero error exit status.
|
||||
`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
if backupOptions.Host == "" {
|
||||
@@ -94,24 +103,24 @@ func init() {
|
||||
cmdRoot.AddCommand(cmdBackup)
|
||||
|
||||
f := cmdBackup.Flags()
|
||||
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)")
|
||||
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent `snapshot` (default: last snapshot in the repo that has the same target files/directories)")
|
||||
f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the target files/directories (overrides the "parent" flag)`)
|
||||
f.StringArrayVarP(&backupOptions.Excludes, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
|
||||
f.StringArrayVar(&backupOptions.InsensitiveExcludes, "iexclude", nil, "same as `--exclude` but ignores the casing of filenames")
|
||||
f.StringArrayVar(&backupOptions.InsensitiveExcludes, "iexclude", nil, "same as --exclude `pattern` but ignores the casing of filenames")
|
||||
f.StringArrayVar(&backupOptions.ExcludeFiles, "exclude-file", nil, "read exclude patterns from a `file` (can be specified multiple times)")
|
||||
f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems")
|
||||
f.StringArrayVar(&backupOptions.ExcludeIfPresent, "exclude-if-present", nil, "takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)")
|
||||
f.StringArrayVar(&backupOptions.ExcludeIfPresent, "exclude-if-present", nil, "takes `filename[:header]`, exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)")
|
||||
f.BoolVar(&backupOptions.ExcludeCaches, "exclude-caches", false, `excludes cache directories that are marked with a CACHEDIR.TAG file. See http://bford.info/cachedir/spec.html for the Cache Directory Tagging Standard`)
|
||||
f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin")
|
||||
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "file name to use when reading from stdin")
|
||||
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "`filename` to use when reading from stdin")
|
||||
f.StringArrayVar(&backupOptions.Tags, "tag", nil, "add a `tag` for the new snapshot (can be specified multiple times)")
|
||||
|
||||
f.StringVarP(&backupOptions.Host, "host", "H", "", "set the `hostname` for the snapshot manually. To prevent an expensive rescan use the \"parent\" flag")
|
||||
f.StringVar(&backupOptions.Host, "hostname", "", "set the `hostname` for the snapshot manually")
|
||||
f.MarkDeprecated("hostname", "use --host")
|
||||
|
||||
f.StringArrayVar(&backupOptions.FilesFrom, "files-from", nil, "read the files to backup from file (can be combined with file args/can be specified multiple times)")
|
||||
f.StringVar(&backupOptions.TimeStamp, "time", "", "time of the backup (ex. '2012-11-01 22:08:41') (default: now)")
|
||||
f.StringArrayVar(&backupOptions.FilesFrom, "files-from", nil, "read the files to backup from `file` (can be combined with file args/can be specified multiple times)")
|
||||
f.StringVar(&backupOptions.TimeStamp, "time", "", "`time` of the backup (ex. '2012-11-01 22:08:41') (default: now)")
|
||||
f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories")
|
||||
f.BoolVar(&backupOptions.IgnoreInode, "ignore-inode", false, "ignore inode number changes when checking for modified files")
|
||||
}
|
||||
@@ -373,7 +382,7 @@ func findParentSnapshot(ctx context.Context, repo restic.Repository, opts Backup
|
||||
|
||||
// Find last snapshot to set it as parent, if not already set
|
||||
if !opts.Force && parentID == nil {
|
||||
id, err := restic.FindLatestSnapshot(ctx, repo, targets, []restic.TagList{}, opts.Host)
|
||||
id, err := restic.FindLatestSnapshot(ctx, repo, targets, []restic.TagList{}, []string{opts.Host})
|
||||
if err == nil {
|
||||
parentID = &id
|
||||
} else if err != restic.ErrNoSnapshotFound {
|
||||
@@ -438,7 +447,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
||||
|
||||
var p ArchiveProgressReporter
|
||||
if gopts.JSON {
|
||||
p = jsonstatus.NewBackup(term, gopts.verbosity)
|
||||
p = json.NewBackup(term, gopts.verbosity)
|
||||
} else {
|
||||
p = ui.NewBackup(term, gopts.verbosity)
|
||||
}
|
||||
@@ -523,13 +532,14 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
||||
if !gopts.JSON {
|
||||
p.V("read data from stdin")
|
||||
}
|
||||
filename := path.Join("/", opts.StdinFilename)
|
||||
targetFS = &fs.Reader{
|
||||
ModTime: timeStamp,
|
||||
Name: opts.StdinFilename,
|
||||
Name: filename,
|
||||
Mode: 0644,
|
||||
ReadCloser: os.Stdin,
|
||||
}
|
||||
targets = []string{opts.StdinFilename}
|
||||
targets = []string{filename}
|
||||
}
|
||||
|
||||
sc := archiver.NewScanner(targetFS)
|
||||
@@ -591,19 +601,18 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||
}
|
||||
|
||||
p.Finish(id)
|
||||
if !gopts.JSON {
|
||||
p.P("snapshot %s saved\n", id.Str())
|
||||
}
|
||||
|
||||
// cleanly shutdown all running goroutines
|
||||
t.Kill(nil)
|
||||
|
||||
// let's see if one returned an error
|
||||
err = t.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
// Report finished execution
|
||||
p.Finish(id)
|
||||
if !gopts.JSON {
|
||||
p.P("snapshot %s saved\n", id.Str())
|
||||
}
|
||||
|
||||
return nil
|
||||
// Return error if any
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ var cmdCache = &cobra.Command{
|
||||
Short: "Operate on local cache directories",
|
||||
Long: `
|
||||
The "cache" command allows listing and cleaning local cache directories.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -18,6 +18,11 @@ var cmdCat = &cobra.Command{
|
||||
Short: "Print internal objects to stdout",
|
||||
Long: `
|
||||
The "cat" command is used to print internal objects to stdout.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -165,18 +170,15 @@ func runCat(gopts GlobalOptions, args []string) error {
|
||||
|
||||
case "blob":
|
||||
for _, t := range []restic.BlobType{restic.DataBlob, restic.TreeBlob} {
|
||||
list, found := repo.Index().Lookup(id, t)
|
||||
_, found := repo.Index().Lookup(id, t)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
blob := list[0]
|
||||
|
||||
buf := make([]byte, blob.Length)
|
||||
n, err := repo.LoadBlob(gopts.ctx, t, id, buf)
|
||||
buf, err := repo.LoadBlob(gopts.ctx, t, id, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
_, err = os.Stdout.Write(buf)
|
||||
return err
|
||||
|
||||
@@ -25,6 +25,11 @@ finds. It can also be used to read all data and therefore simulate a restore.
|
||||
|
||||
By default, the "check" command will always load all data directly from the
|
||||
repository and not use a local cache.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -67,11 +72,17 @@ func checkFlags(opts CheckOptions) error {
|
||||
if dataSubset[0] == 0 || dataSubset[1] == 0 || dataSubset[0] > dataSubset[1] {
|
||||
return errors.Fatalf("check flag --read-data-subset=n/t values must be positive integers, and n <= t, e.g. --read-data-subset=1/2")
|
||||
}
|
||||
if dataSubset[1] > totalBucketsMax {
|
||||
return errors.Fatalf("check flag --read-data-subset=n/t t must be at most %d", totalBucketsMax)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// See doReadData in runCheck below for why this is 256.
|
||||
const totalBucketsMax = 256
|
||||
|
||||
// stringToIntSlice converts string to []uint, using '/' as element separator
|
||||
func stringToIntSlice(param string) (split []uint, err error) {
|
||||
if param == "" {
|
||||
@@ -257,6 +268,8 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error {
|
||||
doReadData := func(bucket, totalBuckets uint) {
|
||||
packs := restic.IDSet{}
|
||||
for pack := range chkr.GetPacks() {
|
||||
// If we ever check more than the first byte
|
||||
// of pack, update totalBucketsMax.
|
||||
if (uint(pack[0]) % totalBuckets) == (bucket - 1) {
|
||||
packs.Insert(pack)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -27,7 +26,13 @@ var cmdDebugDump = &cobra.Command{
|
||||
Short: "Dump data structures",
|
||||
Long: `
|
||||
The "dump" command dumps data structures from the repository as JSON objects. It
|
||||
is used for debugging purposes only.`,
|
||||
is used for debugging purposes only.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDebugDump(globalOptions, args)
|
||||
@@ -84,7 +89,7 @@ func printPacks(repo *repository.Repository, wr io.Writer) error {
|
||||
|
||||
blobs, err := pack.List(repo.Key(), restic.ReaderAt(repo.Backend(), h), size)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error for pack %v: %v\n", id.Str(), err)
|
||||
fmt.Fprintf(globalOptions.stderr, "error for pack %v: %v\n", id.Str(), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -101,13 +106,11 @@ func printPacks(repo *repository.Repository, wr io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
return prettyPrintJSON(os.Stdout, p)
|
||||
return prettyPrintJSON(wr, p)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpIndexes(repo restic.Repository) error {
|
||||
func dumpIndexes(repo restic.Repository, wr io.Writer) error {
|
||||
return repo.List(context.TODO(), restic.IndexFile, func(id restic.ID, size int64) error {
|
||||
fmt.Printf("index_id: %v\n", id)
|
||||
|
||||
@@ -116,7 +119,7 @@ func dumpIndexes(repo restic.Repository) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return idx.Dump(os.Stdout)
|
||||
return idx.Dump(wr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -138,29 +141,24 @@ func runDebugDump(gopts GlobalOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = repo.LoadIndex(gopts.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tpe := args[0]
|
||||
|
||||
switch tpe {
|
||||
case "indexes":
|
||||
return dumpIndexes(repo)
|
||||
return dumpIndexes(repo, gopts.stdout)
|
||||
case "snapshots":
|
||||
return debugPrintSnapshots(repo, os.Stdout)
|
||||
return debugPrintSnapshots(repo, gopts.stdout)
|
||||
case "packs":
|
||||
return printPacks(repo, os.Stdout)
|
||||
return printPacks(repo, gopts.stdout)
|
||||
case "all":
|
||||
fmt.Printf("snapshots:\n")
|
||||
err := debugPrintSnapshots(repo, os.Stdout)
|
||||
err := debugPrintSnapshots(repo, gopts.stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("\nindexes:\n")
|
||||
err = dumpIndexes(repo)
|
||||
err = dumpIndexes(repo, gopts.stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ directory:
|
||||
* U The metadata (access mode, timestamps, ...) for the item was updated
|
||||
* M The file's content was modified
|
||||
* T The type was changed, e.g. a file was made a symlink
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -66,7 +71,7 @@ type Comparer struct {
|
||||
type DiffStat struct {
|
||||
Files, Dirs, Others int
|
||||
DataBlobs, TreeBlobs int
|
||||
Bytes int
|
||||
Bytes uint64
|
||||
}
|
||||
|
||||
// Add adds stats information for node to s.
|
||||
@@ -141,7 +146,7 @@ func updateBlobs(repo restic.Repository, blobs restic.BlobSet, stats *DiffStat)
|
||||
continue
|
||||
}
|
||||
|
||||
stats.Bytes += int(size)
|
||||
stats.Bytes += uint64(size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ prints its contents to stdout.
|
||||
|
||||
The special snapshot "latest" can be used to use the latest snapshot in the
|
||||
repository.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -36,7 +41,7 @@ repository.
|
||||
|
||||
// DumpOptions collects all options for the dump command.
|
||||
type DumpOptions struct {
|
||||
Host string
|
||||
Hosts []string
|
||||
Paths []string
|
||||
Tags restic.TagLists
|
||||
}
|
||||
@@ -47,7 +52,7 @@ func init() {
|
||||
cmdRoot.AddCommand(cmdDump)
|
||||
|
||||
flags := cmdDump.Flags()
|
||||
flags.StringVarP(&dumpOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
|
||||
flags.StringArrayVarP(&dumpOptions.Hosts, "host", "H", nil, `only consider snapshots for this host when the snapshot ID is "latest" (can be specified multiple times)`)
|
||||
flags.Var(&dumpOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
|
||||
flags.StringArrayVar(&dumpOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
|
||||
}
|
||||
@@ -92,7 +97,7 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.Repositor
|
||||
node.Path = pathToPrint
|
||||
return tarTree(ctx, repo, node, pathToPrint)
|
||||
case l > 1:
|
||||
return fmt.Errorf("%q should be a dir, but s a %q", item, node.Type)
|
||||
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
|
||||
case node.Type != "file":
|
||||
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
|
||||
}
|
||||
@@ -136,9 +141,9 @@ func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error {
|
||||
var id restic.ID
|
||||
|
||||
if snapshotIDString == "latest" {
|
||||
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Host)
|
||||
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Hosts)
|
||||
if err != nil {
|
||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host)
|
||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts)
|
||||
}
|
||||
} else {
|
||||
id, err = restic.FindSnapshot(repo, snapshotIDString)
|
||||
@@ -166,24 +171,15 @@ func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error {
|
||||
}
|
||||
|
||||
func getNodeData(ctx context.Context, output io.Writer, repo restic.Repository, node *restic.Node) error {
|
||||
var buf []byte
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
)
|
||||
for _, id := range node.Content {
|
||||
|
||||
size, found := repo.LookupBlobSize(id, restic.DataBlob)
|
||||
if !found {
|
||||
return errors.Errorf("id %v not found in repository", id)
|
||||
}
|
||||
|
||||
buf = buf[:cap(buf)]
|
||||
if len(buf) < restic.CiphertextLength(int(size)) {
|
||||
buf = restic.NewBlobBuffer(int(size))
|
||||
}
|
||||
|
||||
n, err := repo.LoadBlob(ctx, restic.DataBlob, id, buf)
|
||||
buf, err = repo.LoadBlob(ctx, restic.DataBlob, id, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
_, err = output.Write(buf)
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,13 @@ restic find --json "*.yml" "*.json"
|
||||
restic find --json --blob 420f620f b46ebe8a ddd38656
|
||||
restic find --show-pack-id --blob 420f620f
|
||||
restic find --tree 577c2bc9 f81f2e22 a62827a9
|
||||
restic find --pack 025c1d06`,
|
||||
restic find --pack 025c1d06
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runFind(findOptions, globalOptions, args)
|
||||
@@ -45,7 +51,7 @@ type FindOptions struct {
|
||||
PackID, ShowPackID bool
|
||||
CaseInsensitive bool
|
||||
ListLong bool
|
||||
Host string
|
||||
Hosts []string
|
||||
Paths []string
|
||||
Tags restic.TagLists
|
||||
}
|
||||
@@ -66,7 +72,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")
|
||||
|
||||
f.StringVarP(&findOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
||||
f.StringArrayVarP(&findOptions.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when no snapshot ID is given (can be specified multiple times)")
|
||||
f.Var(&findOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given")
|
||||
f.StringArrayVar(&findOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
|
||||
}
|
||||
@@ -150,7 +156,7 @@ func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
||||
if s.hits > 0 {
|
||||
Printf(",")
|
||||
}
|
||||
Printf(string(b))
|
||||
Print(string(b))
|
||||
s.hits++
|
||||
}
|
||||
|
||||
@@ -160,9 +166,9 @@ func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
||||
Verbosef("\n")
|
||||
}
|
||||
s.oldsn = s.newsn
|
||||
Verbosef("Found matching entries in snapshot %s\n", s.oldsn.ID().Str())
|
||||
Verbosef("Found matching entries in snapshot %s from %s\n", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
||||
}
|
||||
Printf(formatNode(path, node, s.ListLong) + "\n")
|
||||
Println(formatNode(path, node, s.ListLong))
|
||||
}
|
||||
|
||||
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
||||
@@ -201,7 +207,7 @@ func (s *statefulOutput) PrintObjectJSON(kind, id, nodepath, treeID string, sn *
|
||||
if s.hits > 0 {
|
||||
Printf(",")
|
||||
}
|
||||
Printf(string(b))
|
||||
Print(string(b))
|
||||
s.hits++
|
||||
}
|
||||
|
||||
@@ -561,7 +567,7 @@ func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
|
||||
f.packsToBlobs(ctx, []string{f.pat.pattern[0]}) // TODO: support multiple packs
|
||||
}
|
||||
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, opts.Snapshots) {
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, opts.Snapshots) {
|
||||
if f.blobIDs != nil || f.treeIDs != nil {
|
||||
if err = f.findIDs(ctx, sn); err != nil && err.Error() != "OK" {
|
||||
return err
|
||||
|
||||
@@ -16,7 +16,13 @@ var cmdForget = &cobra.Command{
|
||||
The "forget" command removes snapshots according to a policy. Please note that
|
||||
this command really only deletes the snapshot object in the repository, which
|
||||
is a reference to data stored there. In order to remove this (now unreferenced)
|
||||
data after 'forget' was run successfully, see the 'prune' command. `,
|
||||
data after 'forget' was run successfully, see the 'prune' command.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runForget(forgetOptions, globalOptions, args)
|
||||
@@ -34,7 +40,7 @@ type ForgetOptions struct {
|
||||
Within restic.Duration
|
||||
KeepTags restic.TagLists
|
||||
|
||||
Host string
|
||||
Hosts []string
|
||||
Tags restic.TagLists
|
||||
Paths []string
|
||||
Compact bool
|
||||
@@ -60,8 +66,8 @@ func init() {
|
||||
f.VarP(&forgetOptions.Within, "keep-within", "", "keep 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)")
|
||||
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
|
||||
f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname`")
|
||||
f.StringArrayVar(&forgetOptions.Hosts, "host", nil, "only consider snapshots with the given `host` (can be specified multiple times)")
|
||||
f.StringArrayVar(&forgetOptions.Hosts, "hostname", nil, "only consider snapshots with the given `hostname` (can be specified multiple times)")
|
||||
f.MarkDeprecated("hostname", "use --host")
|
||||
|
||||
f.Var(&forgetOptions.Tags, "tag", "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)")
|
||||
@@ -95,7 +101,7 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
||||
|
||||
var snapshots restic.Snapshots
|
||||
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
|
||||
snapshots = append(snapshots, sn)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ var cmdGenerate = &cobra.Command{
|
||||
Long: `
|
||||
The "generate" command writes automatically generated files (like the man pages
|
||||
and the auto-completion files for bash and zsh).
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: runGenerate,
|
||||
|
||||
@@ -12,6 +12,11 @@ var cmdInit = &cobra.Command{
|
||||
Short: "Initialize a new repository",
|
||||
Long: `
|
||||
The "init" command initializes a new repository.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -20,6 +20,11 @@ var cmdKey = &cobra.Command{
|
||||
Short: "Manage keys (passwords)",
|
||||
Long: `
|
||||
The "key" command manages keys (passwords) for accessing the repository.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -15,6 +15,11 @@ var cmdList = &cobra.Command{
|
||||
Short: "List objects in the repository",
|
||||
Long: `
|
||||
The "list" command allows listing objects in the repository based on type.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -33,6 +33,11 @@ will be listed. If the --recursive flag is used, then the filter
|
||||
will allow traversing into matching directories' subfolders.
|
||||
Any directory paths specified must be absolute (starting with
|
||||
a path separator); paths use the forward slash '/' as separator.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -43,7 +48,7 @@ a path separator); paths use the forward slash '/' as separator.
|
||||
// LsOptions collects all options for the ls command.
|
||||
type LsOptions struct {
|
||||
ListLong bool
|
||||
Host string
|
||||
Hosts []string
|
||||
Tags restic.TagLists
|
||||
Paths []string
|
||||
Recursive bool
|
||||
@@ -56,7 +61,7 @@ func init() {
|
||||
|
||||
flags := cmdLs.Flags()
|
||||
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||
flags.StringVarP(&lsOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
||||
flags.StringArrayVarP(&lsOptions.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when no snapshot ID is given (can be specified multiple times)")
|
||||
flags.Var(&lsOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot ID is given")
|
||||
flags.StringArrayVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
|
||||
flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories")
|
||||
@@ -84,7 +89,7 @@ type lsNode struct {
|
||||
}
|
||||
|
||||
func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
||||
if len(args) == 0 && opts.Host == "" && len(opts.Tags) == 0 && len(opts.Paths) == 0 {
|
||||
if len(args) == 0 && len(opts.Hosts) == 0 && len(opts.Tags) == 0 && len(opts.Paths) == 0 {
|
||||
return errors.Fatal("Invalid arguments, either give one or more snapshot IDs or set filters.")
|
||||
}
|
||||
|
||||
@@ -186,7 +191,7 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args[:1]) {
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args[:1]) {
|
||||
printSnapshot(sn)
|
||||
|
||||
err := walker.Walk(ctx, repo, *sn.Tree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) {
|
||||
|
||||
@@ -13,6 +13,11 @@ var cmdMigrate = &cobra.Command{
|
||||
Long: `
|
||||
The "migrate" command applies migrations to a repository. When no migration
|
||||
name is explicitly given, a list of migrations that can be applied is printed.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -44,6 +44,11 @@ You need to specify a sample format for exactly the following timestamp:
|
||||
|
||||
For details please see the documentation for time.Format() at:
|
||||
https://godoc.org/time#Time.Format
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -54,10 +59,9 @@ For details please see the documentation for time.Format() at:
|
||||
// MountOptions collects all options for the mount command.
|
||||
type MountOptions struct {
|
||||
OwnerRoot bool
|
||||
AllowRoot bool
|
||||
AllowOther bool
|
||||
NoDefaultPermissions bool
|
||||
Host string
|
||||
Hosts []string
|
||||
Tags restic.TagLists
|
||||
Paths []string
|
||||
SnapshotTemplate string
|
||||
@@ -70,11 +74,10 @@ func init() {
|
||||
|
||||
mountFlags := cmdMount.Flags()
|
||||
mountFlags.BoolVar(&mountOptions.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs")
|
||||
mountFlags.BoolVar(&mountOptions.AllowRoot, "allow-root", false, "allow root user to access the data in the mounted directory")
|
||||
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")
|
||||
|
||||
mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`)
|
||||
mountFlags.StringArrayVarP(&mountOptions.Hosts, "host", "H", nil, `only consider snapshots for this host (can be specified multiple times)`)
|
||||
mountFlags.Var(&mountOptions.Tags, "tag", "only consider snapshots which include this `taglist`")
|
||||
mountFlags.StringArrayVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`")
|
||||
|
||||
@@ -114,10 +117,6 @@ func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
|
||||
systemFuse.FSName("restic"),
|
||||
}
|
||||
|
||||
if opts.AllowRoot {
|
||||
mountOptions = append(mountOptions, systemFuse.AllowRoot())
|
||||
}
|
||||
|
||||
if opts.AllowOther {
|
||||
mountOptions = append(mountOptions, systemFuse.AllowOther())
|
||||
|
||||
@@ -138,7 +137,7 @@ func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
|
||||
|
||||
cfg := fuse.Config{
|
||||
OwnerIsRoot: opts.OwnerRoot,
|
||||
Host: opts.Host,
|
||||
Hosts: opts.Hosts,
|
||||
Tags: opts.Tags,
|
||||
Paths: opts.Paths,
|
||||
SnapshotTemplate: opts.SnapshotTemplate,
|
||||
|
||||
@@ -13,6 +13,11 @@ var optionsCmd = &cobra.Command{
|
||||
Short: "Print list of extended options",
|
||||
Long: `
|
||||
The "options" command prints a list of extended options.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
Hidden: true,
|
||||
DisableAutoGenTag: true,
|
||||
|
||||
@@ -19,6 +19,11 @@ var cmdPrune = &cobra.Command{
|
||||
Long: `
|
||||
The "prune" command checks the repository and removes data that is not
|
||||
referenced and therefore not needed any more.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -16,6 +16,11 @@ var cmdRebuildIndex = &cobra.Command{
|
||||
Long: `
|
||||
The "rebuild-index" command creates a new index based on the pack files in the
|
||||
repository.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -57,11 +62,17 @@ func rebuildIndex(ctx context.Context, repo restic.Repository, ignorePacks resti
|
||||
}
|
||||
|
||||
bar := newProgressMax(!globalOptions.Quiet, packs-uint64(len(ignorePacks)), "packs")
|
||||
idx, _, err := index.New(ctx, repo, ignorePacks, bar)
|
||||
idx, invalidFiles, err := index.New(ctx, repo, ignorePacks, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if globalOptions.verbosity >= 2 {
|
||||
for _, id := range invalidFiles {
|
||||
Printf("skipped incomplete pack file: %v\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
Verbosef("finding old index files\n")
|
||||
|
||||
var supersedes restic.IDs
|
||||
|
||||
@@ -16,6 +16,11 @@ var cmdRecover = &cobra.Command{
|
||||
The "recover" command build a new snapshot from all directories it can find in
|
||||
the raw data of the repository. It can be used if, for example, a snapshot has
|
||||
been removed by accident with "forget".
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/filter"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -20,6 +21,11 @@ a directory.
|
||||
|
||||
The special snapshot "latest" can be used to restore the latest snapshot in the
|
||||
repository.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -34,7 +40,7 @@ type RestoreOptions struct {
|
||||
Include []string
|
||||
InsensitiveInclude []string
|
||||
Target string
|
||||
Host string
|
||||
Hosts []string
|
||||
Paths []string
|
||||
Tags restic.TagLists
|
||||
Verify bool
|
||||
@@ -52,7 +58,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")
|
||||
|
||||
flags.StringVarP(&restoreOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
|
||||
flags.StringArrayVarP(&restoreOptions.Hosts, "host", "H", nil, `only consider snapshots for this host when the snapshot ID is "latest" (can be specified multiple times)`)
|
||||
flags.Var(&restoreOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
|
||||
flags.StringArrayVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
|
||||
flags.BoolVar(&restoreOptions.Verify, "verify", false, "verify restored files content")
|
||||
@@ -111,9 +117,9 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
||||
var id restic.ID
|
||||
|
||||
if snapshotIDString == "latest" {
|
||||
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Host)
|
||||
id, err = restic.FindLatestSnapshot(ctx, repo, opts.Paths, opts.Tags, opts.Hosts)
|
||||
if err != nil {
|
||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Host:%v", err, opts.Paths, opts.Host)
|
||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts)
|
||||
}
|
||||
} else {
|
||||
id, err = restic.FindSnapshot(repo, snapshotIDString)
|
||||
|
||||
@@ -18,6 +18,11 @@ The command "self-update" downloads the latest stable release of restic from
|
||||
GitHub and replaces the currently running binary. After download, the
|
||||
authenticity of the binary is verified using the GPG signature on the release
|
||||
files.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -18,6 +18,11 @@ var cmdSnapshots = &cobra.Command{
|
||||
Short: "List all snapshots",
|
||||
Long: `
|
||||
The "snapshots" command lists all snapshots stored in the repository.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -27,7 +32,7 @@ The "snapshots" command lists all snapshots stored in the repository.
|
||||
|
||||
// SnapshotOptions bundles all options for the snapshots command.
|
||||
type SnapshotOptions struct {
|
||||
Host string
|
||||
Hosts []string
|
||||
Tags restic.TagLists
|
||||
Paths []string
|
||||
Compact bool
|
||||
@@ -41,7 +46,7 @@ func init() {
|
||||
cmdRoot.AddCommand(cmdSnapshots)
|
||||
|
||||
f := cmdSnapshots.Flags()
|
||||
f.StringVarP(&snapshotOptions.Host, "host", "H", "", "only consider snapshots for this `host`")
|
||||
f.StringArrayVarP(&snapshotOptions.Hosts, "host", "H", nil, "only consider snapshots for this `host` (can be specified multiple times)")
|
||||
f.Var(&snapshotOptions.Tags, "tag", "only consider snapshots which include this `taglist` (can be specified multiple times)")
|
||||
f.StringArrayVar(&snapshotOptions.Paths, "path", nil, "only consider snapshots for this `path` (can be specified multiple times)")
|
||||
f.BoolVarP(&snapshotOptions.Compact, "compact", "c", false, "use compact format")
|
||||
@@ -67,7 +72,7 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
|
||||
defer cancel()
|
||||
|
||||
var snapshots restic.Snapshots
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
|
||||
snapshots = append(snapshots, sn)
|
||||
}
|
||||
snapshotGroups, grouped, err := restic.GroupSnapshots(snapshots, opts.GroupBy)
|
||||
|
||||
@@ -38,6 +38,11 @@ The modes are:
|
||||
* blobs-per-file: A combination of files-by-contents and raw-data.
|
||||
|
||||
Refer to the online manual for more details about each mode.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -49,7 +54,7 @@ func init() {
|
||||
cmdRoot.AddCommand(cmdStats)
|
||||
f := cmdStats.Flags()
|
||||
f.StringVar(&countMode, "mode", countModeRestoreSize, "counting mode: restore-size (default), files-by-contents, blobs-per-file, or raw-data")
|
||||
f.StringVarP(&snapshotByHost, "host", "H", "", "filter latest snapshot by this hostname")
|
||||
f.StringArrayVarP(&snapshotByHosts, "host", "H", nil, "filter latest snapshot by this hostname (can be specified multiple times)")
|
||||
}
|
||||
|
||||
func runStats(gopts GlobalOptions, args []string) error {
|
||||
@@ -84,10 +89,11 @@ func runStats(gopts GlobalOptions, args []string) error {
|
||||
|
||||
// create a container for the stats (and other needed state)
|
||||
stats := &statsContainer{
|
||||
uniqueFiles: make(map[fileID]struct{}),
|
||||
fileBlobs: make(map[string]restic.IDSet),
|
||||
blobs: restic.NewBlobSet(),
|
||||
blobsSeen: restic.NewBlobSet(),
|
||||
uniqueFiles: make(map[fileID]struct{}),
|
||||
uniqueInodes: make(map[uint64]struct{}),
|
||||
fileBlobs: make(map[string]restic.IDSet),
|
||||
blobs: restic.NewBlobSet(),
|
||||
blobsSeen: restic.NewBlobSet(),
|
||||
}
|
||||
|
||||
if snapshotIDString != "" {
|
||||
@@ -95,7 +101,7 @@ func runStats(gopts GlobalOptions, args []string) error {
|
||||
|
||||
var sID restic.ID
|
||||
if snapshotIDString == "latest" {
|
||||
sID, err = restic.FindLatestSnapshot(ctx, repo, []string{}, []restic.TagList{}, snapshotByHost)
|
||||
sID, err = restic.FindLatestSnapshot(ctx, repo, []string{}, []restic.TagList{}, snapshotByHosts)
|
||||
if err != nil {
|
||||
return errors.Fatalf("latest snapshot for criteria not found: %v", err)
|
||||
}
|
||||
@@ -112,6 +118,9 @@ func runStats(gopts GlobalOptions, args []string) error {
|
||||
}
|
||||
|
||||
err = statsWalkSnapshot(ctx, snapshot, repo, stats)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking snapshot: %v", err)
|
||||
}
|
||||
} else {
|
||||
// iterate every snapshot in the repo
|
||||
err = repo.List(ctx, restic.SnapshotFile, func(snapshotID restic.ID, size int64) error {
|
||||
@@ -185,7 +194,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo rest
|
||||
}
|
||||
|
||||
func statsWalkTree(repo restic.Repository, stats *statsContainer) walker.WalkFunc {
|
||||
return func(_ restic.ID, npath string, node *restic.Node, nodeErr error) (bool, error) {
|
||||
return func(parentTreeID restic.ID, npath string, node *restic.Node, nodeErr error) (bool, error) {
|
||||
if nodeErr != nil {
|
||||
return true, nodeErr
|
||||
}
|
||||
@@ -220,7 +229,7 @@ func statsWalkTree(repo restic.Repository, stats *statsContainer) walker.WalkFun
|
||||
// is always a data blob since we're accessing it via a file's Content array
|
||||
blobSize, found := repo.LookupBlobSize(blobID, restic.DataBlob)
|
||||
if !found {
|
||||
return true, fmt.Errorf("blob %s not found for tree %s", blobID, *node.Subtree)
|
||||
return true, fmt.Errorf("blob %s not found for tree %s", blobID, parentTreeID)
|
||||
}
|
||||
|
||||
// count the blob's size, then add this blob by this
|
||||
@@ -239,8 +248,16 @@ func statsWalkTree(repo restic.Repository, stats *statsContainer) walker.WalkFun
|
||||
// as this is a file in the snapshot, we can simply count its
|
||||
// size without worrying about uniqueness, since duplicate files
|
||||
// will still be restored
|
||||
stats.TotalSize += node.Size
|
||||
stats.TotalFileCount++
|
||||
|
||||
// if inodes are present, only count each inode once
|
||||
// (hard links do not increase restore size)
|
||||
if _, ok := stats.uniqueInodes[node.Inode]; !ok || node.Inode == 0 {
|
||||
stats.uniqueInodes[node.Inode] = struct{}{}
|
||||
stats.TotalSize += node.Size
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -293,6 +310,10 @@ type statsContainer struct {
|
||||
// contents (hashed sequence of content blob IDs)
|
||||
uniqueFiles map[fileID]struct{}
|
||||
|
||||
// uniqueInodes marks visited files according to their
|
||||
// inode # (hashed sequence of inode numbers)
|
||||
uniqueInodes map[uint64]struct{}
|
||||
|
||||
// fileBlobs maps a file name (path) to the set of
|
||||
// blobs that have been seen as a part of the file
|
||||
fileBlobs map[string]restic.IDSet
|
||||
@@ -314,7 +335,7 @@ var (
|
||||
|
||||
// snapshotByHost is the host to filter latest
|
||||
// snapshot by, if given by user
|
||||
snapshotByHost string
|
||||
snapshotByHosts []string
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -21,6 +21,11 @@ You can either set/replace the entire set of tags on a snapshot, or
|
||||
add tags to/remove tags from the existing set.
|
||||
|
||||
When no snapshot-ID is given, all snapshots matching the host, tag and path filter criteria are modified.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -30,7 +35,7 @@ When no snapshot-ID is given, all snapshots matching the host, tag and path filt
|
||||
|
||||
// TagOptions bundles all options for the 'tag' command.
|
||||
type TagOptions struct {
|
||||
Host string
|
||||
Hosts []string
|
||||
Paths []string
|
||||
Tags restic.TagLists
|
||||
SetTags []string
|
||||
@@ -48,7 +53,7 @@ func init() {
|
||||
tagFlags.StringSliceVar(&tagOptions.AddTags, "add", nil, "`tag` which will be added to the existing tags (can be given multiple times)")
|
||||
tagFlags.StringSliceVar(&tagOptions.RemoveTags, "remove", nil, "`tag` which will be removed from the existing tags (can be given multiple times)")
|
||||
|
||||
tagFlags.StringVarP(&tagOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
||||
tagFlags.StringArrayVarP(&tagOptions.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when no snapshot ID is given (can be specified multiple times)")
|
||||
tagFlags.Var(&tagOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given")
|
||||
tagFlags.StringArrayVar(&tagOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
|
||||
}
|
||||
@@ -124,7 +129,7 @@ func runTag(opts TagOptions, gopts GlobalOptions, args []string) error {
|
||||
changeCnt := 0
|
||||
ctx, cancel := context.WithCancel(gopts.ctx)
|
||||
defer cancel()
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Hosts, opts.Tags, opts.Paths, args) {
|
||||
changed, err := changeTags(ctx, repo, sn, opts.SetTags, opts.AddTags, opts.RemoveTags)
|
||||
if err != nil {
|
||||
Warnf("unable to modify the tags for snapshot ID %q, ignoring: %v\n", sn.ID(), err)
|
||||
|
||||
@@ -10,6 +10,11 @@ var unlockCmd = &cobra.Command{
|
||||
Short: "Remove locks other processes created",
|
||||
Long: `
|
||||
The "unlock" command removes stale locks that have been created by other restic processes.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -13,6 +13,11 @@ var versionCmd = &cobra.Command{
|
||||
Long: `
|
||||
The "version" command prints detailed information about the build environment
|
||||
and the version of this software.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
||||
func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, host string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
|
||||
func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, hosts []string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
|
||||
out := make(chan *restic.Snapshot)
|
||||
go func() {
|
||||
defer close(out)
|
||||
@@ -22,9 +22,9 @@ func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, hos
|
||||
// Process all snapshot IDs given as arguments.
|
||||
for _, s := range snapshotIDs {
|
||||
if s == "latest" {
|
||||
id, err = restic.FindLatestSnapshot(ctx, repo, paths, tags, host)
|
||||
id, err = restic.FindLatestSnapshot(ctx, repo, paths, tags, hosts)
|
||||
if err != nil {
|
||||
Warnf("Ignoring %q, no snapshot matched given filter (Paths:%v Tags:%v Host:%v)\n", s, paths, tags, host)
|
||||
Warnf("Ignoring %q, no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)\n", s, paths, tags, hosts)
|
||||
usedFilter = true
|
||||
continue
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, hos
|
||||
}
|
||||
|
||||
// Give the user some indication their filters are not used.
|
||||
if !usedFilter && (host != "" || len(tags) != 0 || len(paths) != 0) {
|
||||
if !usedFilter && (len(hosts) != 0 || len(tags) != 0 || len(paths) != 0) {
|
||||
Warnf("Ignoring filters as there are explicit snapshot ids given\n")
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, hos
|
||||
return
|
||||
}
|
||||
|
||||
snapshots, err := restic.FindFilteredSnapshots(ctx, repo, host, tags, paths)
|
||||
snapshots, err := restic.FindFilteredSnapshots(ctx, repo, hosts, tags, paths)
|
||||
if err != nil {
|
||||
Warnf("could not load snapshots: %v\n", err)
|
||||
return
|
||||
|
||||
@@ -34,11 +34,12 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var version = "0.9.5"
|
||||
var version = "0.9.6-dev (compiled manually)"
|
||||
|
||||
// TimeFormat is the format used for all timestamps printed by restic.
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
@@ -84,6 +85,8 @@ var globalOptions = GlobalOptions{
|
||||
stderr: os.Stderr,
|
||||
}
|
||||
|
||||
var isReadingPassword bool
|
||||
|
||||
func init() {
|
||||
var cancel context.CancelFunc
|
||||
globalOptions.ctx, cancel = context.WithCancel(context.Background())
|
||||
@@ -93,18 +96,18 @@ func init() {
|
||||
})
|
||||
|
||||
f := cmdRoot.PersistentFlags()
|
||||
f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "repository to backup to or restore from (default: $RESTIC_REPOSITORY)")
|
||||
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "read the repository password from a file (default: $RESTIC_PASSWORD_FILE)")
|
||||
f.StringVarP(&globalOptions.KeyHint, "key-hint", "", os.Getenv("RESTIC_KEY_HINT"), "key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)")
|
||||
f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", os.Getenv("RESTIC_PASSWORD_COMMAND"), "specify a shell command to obtain a password (default: $RESTIC_PASSWORD_COMMAND)")
|
||||
f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)")
|
||||
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "read the repository password from a `file` (default: $RESTIC_PASSWORD_FILE)")
|
||||
f.StringVarP(&globalOptions.KeyHint, "key-hint", "", os.Getenv("RESTIC_KEY_HINT"), "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)")
|
||||
f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", os.Getenv("RESTIC_PASSWORD_COMMAND"), "specify a shell `command` to obtain a password (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 --verbose multiple times or level `n`)")
|
||||
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos")
|
||||
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)")
|
||||
f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache `directory`. (default: use system default cache directory)")
|
||||
f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache")
|
||||
f.StringSliceVar(&globalOptions.CACerts, "cacert", nil, "`file` to load root certificates from (default: use system certificates)")
|
||||
f.StringVar(&globalOptions.TLSClientCert, "tls-client-cert", "", "path to a file containing PEM encoded TLS client certificate and private key")
|
||||
f.StringVar(&globalOptions.TLSClientCert, "tls-client-cert", "", "path to a `file` containing PEM encoded TLS client certificate and private key")
|
||||
f.BoolVar(&globalOptions.CleanupCache, "cleanup-cache", false, "auto remove old cache directories")
|
||||
f.IntVar(&globalOptions.LimitUploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)")
|
||||
f.IntVar(&globalOptions.LimitDownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)")
|
||||
@@ -145,7 +148,10 @@ func stdoutTerminalWidth() int {
|
||||
}
|
||||
|
||||
// restoreTerminal installs a cleanup handler that restores the previous
|
||||
// terminal state on exit.
|
||||
// terminal state on exit. This handler is only intended to restore the
|
||||
// terminal configuration if restic exits after receiving a signal. A regular
|
||||
// program execution must revert changes to the terminal configuration itself.
|
||||
// The terminal configuration is only restored while reading a password.
|
||||
func restoreTerminal() {
|
||||
if !stdoutIsTerminal() {
|
||||
return
|
||||
@@ -159,9 +165,17 @@ func restoreTerminal() {
|
||||
}
|
||||
|
||||
AddCleanupHandler(func() error {
|
||||
// Restoring the terminal configuration while restic runs in the
|
||||
// background, causes restic to get stopped on unix systems with
|
||||
// a SIGTTOU signal. Thus only restore the terminal settings if
|
||||
// they might have been modified, which is the case while reading
|
||||
// a password.
|
||||
if !isReadingPassword {
|
||||
return nil
|
||||
}
|
||||
err := checkErrno(terminal.Restore(fd, state))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to get restore terminal state: %#+v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "unable to restore terminal state: %v\n", err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
@@ -188,6 +202,22 @@ func Printf(format string, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Print writes the message to the configured stdout stream.
|
||||
func Print(args ...interface{}) {
|
||||
_, err := fmt.Fprint(globalOptions.stdout, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Println writes the message to the configured stdout stream.
|
||||
func Println(args ...interface{}) {
|
||||
_, err := fmt.Fprintln(globalOptions.stdout, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verbosef calls Printf to write the message when the verbose flag is set.
|
||||
func Verbosef(format string, args ...interface{}) {
|
||||
if globalOptions.verbosity >= 1 {
|
||||
@@ -231,7 +261,7 @@ func Warnf(format string, args ...interface{}) {
|
||||
// Exitf uses Warnf to write the message and then terminates the process with
|
||||
// the given exit code.
|
||||
func Exitf(exitcode int, format string, args ...interface{}) {
|
||||
if format[len(format)-1] != '\n' {
|
||||
if !(strings.HasSuffix(format, "\n")) {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
@@ -285,7 +315,9 @@ func readPassword(in io.Reader) (password string, err error) {
|
||||
// password.
|
||||
func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password string, err error) {
|
||||
fmt.Fprint(out, prompt)
|
||||
isReadingPassword = true
|
||||
buf, err := terminal.ReadPassword(int(in.Fd()))
|
||||
isReadingPassword = false
|
||||
fmt.Fprintln(out)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "ReadPassword")
|
||||
@@ -319,7 +351,7 @@ func ReadPassword(opts GlobalOptions, prompt string) (string, error) {
|
||||
}
|
||||
|
||||
if len(password) == 0 {
|
||||
return "", errors.Fatal("an empty password is not a password")
|
||||
return "", errors.New("an empty password is not a password")
|
||||
}
|
||||
|
||||
return password, nil
|
||||
@@ -365,14 +397,32 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
|
||||
|
||||
s := repository.New(be)
|
||||
|
||||
opts.password, err = ReadPassword(opts, "enter password for repository: ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
passwordTriesLeft := 1
|
||||
if stdinIsTerminal() && opts.password == "" {
|
||||
passwordTriesLeft = 3
|
||||
}
|
||||
|
||||
err = s.SearchKey(opts.ctx, opts.password, maxKeys, opts.KeyHint)
|
||||
for ; passwordTriesLeft > 0; passwordTriesLeft-- {
|
||||
opts.password, err = ReadPassword(opts, "enter password for repository: ")
|
||||
if err != nil && passwordTriesLeft > 1 {
|
||||
opts.password = ""
|
||||
fmt.Printf("%s. Try again\n", err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.SearchKey(opts.ctx, opts.password, maxKeys, opts.KeyHint)
|
||||
if err != nil && passwordTriesLeft > 1 {
|
||||
opts.password = ""
|
||||
fmt.Printf("%s. Try again\n", err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if errors.IsFatal(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
if stdoutIsTerminal() && !opts.JSON {
|
||||
@@ -425,7 +475,7 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
|
||||
}
|
||||
} else {
|
||||
if stdoutIsTerminal() {
|
||||
Verbosef("found %d old cache directories in %v, pass --cleanup-cache to remove them\n",
|
||||
Verbosef("found %d old cache directories in %v, run `restic cache --cleanup` to remove them\n",
|
||||
len(oldCacheDirs), c.Base)
|
||||
}
|
||||
}
|
||||
@@ -466,6 +516,10 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
|
||||
cfg.Secret = os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
}
|
||||
|
||||
if cfg.Region == "" {
|
||||
cfg.Region = os.Getenv("AWS_DEFAULT_REGION")
|
||||
}
|
||||
|
||||
if err := opts.Apply(loc.Scheme, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
28
cmd/restic/global_test.go
Normal file
28
cmd/restic/global_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func Test_PrintFunctionsRespectsGlobalStdout(t *testing.T) {
|
||||
gopts := globalOptions
|
||||
defer func() {
|
||||
globalOptions = gopts
|
||||
}()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
globalOptions.stdout = buf
|
||||
|
||||
for _, p := range []func(){
|
||||
func() { Println("message") },
|
||||
func() { Print("message\n") },
|
||||
func() { Printf("mes%s\n", "sage") },
|
||||
} {
|
||||
p()
|
||||
rtest.Equals(t, "message\n", buf.String())
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
@@ -94,10 +94,10 @@ func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID res
|
||||
testRunRestoreExcludes(t, opts, dir, snapshotID, nil)
|
||||
}
|
||||
|
||||
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, host string) {
|
||||
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
|
||||
opts := RestoreOptions{
|
||||
Target: dir,
|
||||
Host: host,
|
||||
Hosts: hosts,
|
||||
Paths: paths,
|
||||
}
|
||||
|
||||
@@ -765,7 +765,7 @@ func TestRestore(t *testing.T) {
|
||||
|
||||
// Restore latest without any filters
|
||||
restoredir := filepath.Join(env.base, "restore")
|
||||
testRunRestoreLatest(t, env.gopts, restoredir, nil, "")
|
||||
testRunRestoreLatest(t, env.gopts, restoredir, nil, nil)
|
||||
|
||||
rtest.Assert(t, directoriesEqualContents(env.testdata, filepath.Join(restoredir, filepath.Base(env.testdata))),
|
||||
"directories are not equal")
|
||||
@@ -802,7 +802,7 @@ func TestRestoreLatest(t *testing.T) {
|
||||
testRunCheck(t, env.gopts)
|
||||
|
||||
// Restore latest without any filters
|
||||
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore0"), nil, "")
|
||||
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore0"), nil, nil)
|
||||
rtest.OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", "testfile.c"), int64(101)))
|
||||
|
||||
// Setup test files in different directories backed up in different snapshots
|
||||
@@ -823,14 +823,14 @@ func TestRestoreLatest(t *testing.T) {
|
||||
p1rAbs := filepath.Join(env.base, "restore1", "p1/testfile.c")
|
||||
p2rAbs := filepath.Join(env.base, "restore2", "p2/testfile.c")
|
||||
|
||||
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, "")
|
||||
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, nil)
|
||||
rtest.OK(t, testFileSize(p1rAbs, int64(102)))
|
||||
if _, err := os.Stat(p2rAbs); os.IsNotExist(errors.Cause(err)) {
|
||||
rtest.Assert(t, os.IsNotExist(errors.Cause(err)),
|
||||
"expected %v to not exist in restore, but it exists, err %v", p2rAbs, err)
|
||||
}
|
||||
|
||||
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, "")
|
||||
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, nil)
|
||||
rtest.OK(t, testFileSize(p2rAbs, int64(103)))
|
||||
if _, err := os.Stat(p1rAbs); os.IsNotExist(errors.Cause(err)) {
|
||||
rtest.Assert(t, os.IsNotExist(errors.Cause(err)),
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestRestoreLocalLayout(t *testing.T) {
|
||||
|
||||
// restore latest snapshot
|
||||
target := filepath.Join(env.base, "restore")
|
||||
testRunRestoreLatest(t, env.gopts, target, nil, "")
|
||||
testRunRestoreLatest(t, env.gopts, target, nil, nil)
|
||||
|
||||
rtest.RemoveAll(t, filepath.Join(env.base, "repo"))
|
||||
rtest.RemoveAll(t, target)
|
||||
|
||||
@@ -48,10 +48,6 @@ installed from the official repos, e.g. with ``apt-get``:
|
||||
$ apt-get install restic
|
||||
|
||||
|
||||
.. warning:: Please be aware that, at the time of writing, Debian *stable*
|
||||
has ``restic`` version 0.3.3 which is very old. The *testing* and *unstable*
|
||||
branches have recent versions of ``restic``.
|
||||
|
||||
Fedora
|
||||
======
|
||||
|
||||
@@ -113,6 +109,15 @@ On FreeBSD (11 and probably later versions), you can install restic using ``pkg
|
||||
|
||||
# pkg install restic
|
||||
|
||||
openSUSE
|
||||
========
|
||||
|
||||
On openSUSE (leap 15.0 and greater, and tumbleweed), you can install restic using the ``zypper`` package manager:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# zypper install restic
|
||||
|
||||
RHEL & CentOS
|
||||
=============
|
||||
|
||||
@@ -240,7 +245,7 @@ From Source
|
||||
***********
|
||||
|
||||
restic is written in the Go programming language and you need at least
|
||||
Go version 1.9. Building restic may also work with older versions of Go,
|
||||
Go version 1.11. Building restic may also work with older versions of Go,
|
||||
but that's not supported. See the `Getting
|
||||
started <https://golang.org/doc/install>`__ guide of the Go project for
|
||||
instructions how to install Go.
|
||||
@@ -254,13 +259,6 @@ In order to build restic from source, execute the following steps:
|
||||
|
||||
$ cd restic
|
||||
|
||||
$ go run -mod=vendor build.go
|
||||
|
||||
For Go versions < 1.11, the option ``-mod=vendor`` needs to be removed, like
|
||||
this:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ go run build.go
|
||||
|
||||
You can easily cross-compile restic for all supported platforms, just
|
||||
@@ -269,13 +267,11 @@ supply the target OS and platform via the command-line options like this
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ go run -mod=vendor build.go --goos windows --goarch amd64
|
||||
$ go run build.go --goos windows --goarch amd64
|
||||
|
||||
$ go run -mod=vendor build.go --goos freebsd --goarch 386
|
||||
$ go run build.go --goos freebsd --goarch 386
|
||||
|
||||
$ go run -mod=vendor build.go --goos linux --goarch arm --goarm 6
|
||||
|
||||
Again, for Go < 1.11 ``-mod=vendor`` needs to be removed.
|
||||
$ go run build.go --goos linux --goarch arm --goarm 6
|
||||
|
||||
The resulting binary is statically linked and does not require any
|
||||
libraries.
|
||||
@@ -292,7 +288,7 @@ Restic can write out man pages and bash/zsh compatible autocompletion scripts:
|
||||
|
||||
$ ./restic generate --help
|
||||
|
||||
The "generate" command writes automatically generated files like the man pages
|
||||
The "generate" command writes automatically generated files (like the man pages
|
||||
and the auto-completion files for bash and zsh).
|
||||
|
||||
Usage:
|
||||
|
||||
@@ -54,6 +54,13 @@ command and enter the same password twice:
|
||||
Remembering your password is important! If you lose it, you won't be
|
||||
able to access data stored in the repository.
|
||||
|
||||
.. warning::
|
||||
|
||||
On Linux, storing the backup repository on a CIFS (SMB) share is not
|
||||
recommended due to compatibility issues. Either use another backend
|
||||
or set the environment variable `GODEBUG` to `asyncpreemptoff=1`.
|
||||
Refer to GitHub issue #2659 for further explanations.
|
||||
|
||||
SFTP
|
||||
****
|
||||
|
||||
@@ -78,15 +85,29 @@ You can also specify a relative (read: no slash (``/``) character at the
|
||||
beginning) directory, in this case the dir is relative to the remote
|
||||
user's home directory.
|
||||
|
||||
Also, if the SFTP server is enforcing domain-confined users, you can
|
||||
specify the user this way: ``user@domain@host``.
|
||||
|
||||
.. note:: Please be aware that sftp servers do not expand the tilde character
|
||||
(``~``) normally used as an alias for a user's home directory. If you
|
||||
want to specify a path relative to the user's home directory, pass a
|
||||
relative path to the sftp backend.
|
||||
|
||||
The backend config string does not allow specifying a port. If you need
|
||||
to contact an sftp server on a different port, you can create an entry
|
||||
in the ``ssh`` file, usually located in your user's home directory at
|
||||
``~/.ssh/config`` or in ``/etc/ssh/ssh_config``:
|
||||
If you need to specify a port number or IPv6 address, you'll need to use
|
||||
URL syntax. E.g., the repository ``/srv/restic-repo`` on ``[::1]`` (localhost)
|
||||
at port 2222 with username ``user`` can be specified as
|
||||
|
||||
::
|
||||
|
||||
sftp://user@[::1]:2222//srv/restic-repo
|
||||
|
||||
Note the double slash: the first slash separates the connection settings from
|
||||
the path, while the second is the start of the path. To specify a relative
|
||||
path, use one slash.
|
||||
|
||||
Alternatively, you can create an entry in the ``ssh`` configuration file,
|
||||
usually located in your home directory at ``~/.ssh/config`` or in
|
||||
``/etc/ssh/ssh_config``:
|
||||
|
||||
::
|
||||
|
||||
@@ -197,10 +218,11 @@ default location:
|
||||
Please note that knowledge of your password is required to access the repository.
|
||||
Losing your password means that your data is irrecoverably lost.
|
||||
|
||||
It is not possible at the moment to have restic create a new bucket in a
|
||||
different location, so you need to create it using a different program.
|
||||
Afterwards, the S3 server (``s3.amazonaws.com``) will redirect restic to
|
||||
the correct endpoint.
|
||||
If needed, you can manually specify the region to use by either setting the
|
||||
environment variable ``AWS_DEFAULT_REGION`` or calling restic with an option
|
||||
parameter like ``-o s3.region="us-east-1"``. If the region is not specified,
|
||||
the default region is used. Afterwards, the S3 server (at least for AWS,
|
||||
``s3.amazonaws.com``) will redirect restic to the correct endpoint.
|
||||
|
||||
Until version 0.8.0, restic used a default prefix of ``restic``, so the files
|
||||
in the bucket were placed in a directory named ``restic``. If you want to
|
||||
@@ -234,7 +256,7 @@ credentials of your Minio Server.
|
||||
$ export AWS_ACCESS_KEY_ID=<YOUR-MINIO-ACCESS-KEY-ID>
|
||||
$ export AWS_SECRET_ACCESS_KEY= <YOUR-MINIO-SECRET-ACCESS-KEY>
|
||||
|
||||
Now you can easily initialize restic to use Minio server as backend with
|
||||
Now you can easily initialize restic to use Minio server as a backend with
|
||||
this command.
|
||||
|
||||
.. code-block:: console
|
||||
@@ -246,6 +268,35 @@ this command.
|
||||
Please note that knowledge of your password is required to access
|
||||
the repository. Losing your password means that your data is irrecoverably lost.
|
||||
|
||||
Wasabi
|
||||
************
|
||||
|
||||
`Wasabi <https://wasabi.com>`__ is a low cost AWS S3 conformant object storage provider.
|
||||
Due to it's S3 conformance, Wasabi can be used as a storage provider for a restic repository.
|
||||
|
||||
- Create a Wasabi bucket using the `Wasabi Console <https://console.wasabisys.com>`__.
|
||||
- Determine the correct Wasabi service URL for your bucket `here <https://wasabi-support.zendesk.com/hc/en-us/articles/360015106031-What-are-the-service-URLs-for-Wasabi-s-different-regions->`__.
|
||||
|
||||
You must first setup the following environment variables with the
|
||||
credentials of your Wasabi account.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export AWS_ACCESS_KEY_ID=<YOUR-WASABI-ACCESS-KEY-ID>
|
||||
$ export AWS_SECRET_ACCESS_KEY=<YOUR-WASABI-SECRET-ACCESS-KEY>
|
||||
|
||||
Now you can easily initialize restic to use Wasabi as a backend with
|
||||
this command.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ./restic -r s3:https://<WASABI-SERVICE-URL>/<WASABI-BUCKET-NAME> init
|
||||
enter password for new backend:
|
||||
enter password again:
|
||||
created restic backend xxxxxxxxxx at s3:https://<WASABI-SERVICE-URL>/<WASABI-BUCKET-NAME>
|
||||
Please note that knowledge of your password is required to access
|
||||
the repository. Losing your password means that your data is irrecoverably lost.
|
||||
|
||||
OpenStack Swift
|
||||
***************
|
||||
|
||||
@@ -329,9 +380,9 @@ dashboard on the "Buckets" page when signed into your B2 account:
|
||||
.. code-block:: console
|
||||
|
||||
$ export B2_ACCOUNT_ID=<MY_APPLICATION_KEY_ID>
|
||||
$ export B2_ACCOUNT_KEY=<MY_SECRET_ACCOUNT_KEY>
|
||||
$ export B2_ACCOUNT_KEY=<MY_APPLICATION_KEY>
|
||||
|
||||
.. note:: In case you want to use Backblaze Application Keys replace <MY_APPLICATION_KEY_ID> and <MY_SECRET_ACCOUNT_KEY> with <applicationKeyId> and <applicationKey> respectively.
|
||||
.. note:: As of version 0.9.2, restic supports both master and non-master `application keys <https://www.backblaze.com/b2/docs/application_keys.html>`__. If using a non-master application key, ensure that it is created with at least **read and write** access to the B2 bucket. On earlier versions of restic, a master application key is required.
|
||||
|
||||
You can then initialize a repository stored at Backblaze B2. If the
|
||||
bucket does not exist yet and the credentials you passed to restic have the
|
||||
|
||||
@@ -132,8 +132,8 @@ Now is a good time to run ``restic check`` to verify that all data
|
||||
is properly stored in the repository. You should run this command regularly
|
||||
to make sure the internal structure of the repository is free of errors.
|
||||
|
||||
Including and Excluding Files
|
||||
*****************************
|
||||
Excluding Files
|
||||
***************
|
||||
|
||||
You can exclude folders and files by specifying exclude patterns, currently
|
||||
the exclude options are:
|
||||
@@ -142,9 +142,11 @@ the exclude options are:
|
||||
- ``--iexclude`` Same as ``--exclude`` but ignores the case of paths
|
||||
- ``--exclude-caches`` Specified once to exclude folders containing a special file
|
||||
- ``--exclude-file`` Specified one or more times to exclude items listed in a given file
|
||||
- ``--exclude-if-present foo`` Specified one or more times to exclude a folder's content if it contains a file called ``foo``` (optionally having a given header, no wildcards for the file name supported)
|
||||
- ``--exclude-if-present foo`` Specified one or more times to exclude a folder's content if it contains a file called ``foo`` (optionally having a given header, no wildcards for the file name supported)
|
||||
|
||||
Let's say we have a file called ``excludes.txt`` with the following content:
|
||||
Please see ``restic help backup`` for more specific information about each exclude option.
|
||||
|
||||
Let's say we have a file called ``excludes.txt`` with the following content:
|
||||
|
||||
::
|
||||
|
||||
@@ -159,34 +161,31 @@ It can be used like this:
|
||||
|
||||
$ restic -r /srv/restic-repo backup ~/work --exclude="*.c" --exclude-file=excludes.txt
|
||||
|
||||
This instruct restic to exclude files matching the following criteria:
|
||||
This instructs restic to exclude files matching the following criteria:
|
||||
|
||||
* All files matching ``*.c`` (parameter ``--exclude``)
|
||||
* All files matching ``*.go`` (second line in ``excludes.txt``)
|
||||
* All files and sub-directories named ``bar`` which reside somewhere below a directory called ``foo`` (fourth line in ``excludes.txt``)
|
||||
* All files matching ``*.c`` (parameter ``--exclude``)
|
||||
|
||||
Please see ``restic help backup`` for more specific information about each exclude option.
|
||||
|
||||
Patterns use `filepath.Glob <https://golang.org/pkg/path/filepath/#Glob>`__ internally,
|
||||
see `filepath.Match <https://golang.org/pkg/path/filepath/#Match>`__ for
|
||||
syntax. Patterns are tested against the full path of a file/dir to be saved,
|
||||
even if restic is passed a relative path to save. Environment-variables in
|
||||
exclude-files are expanded with `os.ExpandEnv <https://golang.org/pkg/os/#ExpandEnv>`__,
|
||||
so `/home/$USER/foo` will be expanded to `/home/bob/foo` for the user `bob`. To
|
||||
get a literal dollar sign, write `$$` to the file.
|
||||
even if restic is passed a relative path to save.
|
||||
|
||||
Environment-variables in exclude files are expanded with `os.ExpandEnv <https://golang.org/pkg/os/#ExpandEnv>`__,
|
||||
so ``/home/$USER/foo`` will be expanded to ``/home/bob/foo`` for the user ``bob``.
|
||||
To get a literal dollar sign, write ``$$`` to the file. Note that tilde (``~``) expansion does not work, please use the ``$HOME`` environment variable instead.
|
||||
|
||||
Patterns need to match on complete path components. For example, the pattern ``foo``:
|
||||
|
||||
* matches ``/dir1/foo/dir2/file`` and ``/dir/foo``
|
||||
* does not match ``/dir/foobar`` or ``barfoo``
|
||||
|
||||
A trailing ``/`` is ignored, a leading ``/`` anchors the
|
||||
pattern at the root directory. This means, ``/bin`` matches ``/bin/bash`` but
|
||||
does not match ``/usr/bin/restic``.
|
||||
A trailing ``/`` is ignored, a leading ``/`` anchors the pattern at the root directory.
|
||||
This means, ``/bin`` matches ``/bin/bash`` but does not match ``/usr/bin/restic``.
|
||||
|
||||
Regular wildcards cannot be used to match over the
|
||||
directory separator ``/``. For example: ``b*ash`` matches ``/bin/bash`` but does not match
|
||||
``/bin/ash``.
|
||||
Regular wildcards cannot be used to match over the directory separator ``/``.
|
||||
For example: ``b*ash`` matches ``/bin/bash`` but does not match ``/bin/ash``.
|
||||
|
||||
For this, the special wildcard ``**`` can be used to match arbitrary
|
||||
sub-directories: The pattern ``foo/**/bar`` matches:
|
||||
@@ -195,6 +194,23 @@ sub-directories: The pattern ``foo/**/bar`` matches:
|
||||
* ``/foo/bar/file``
|
||||
* ``/tmp/foo/bar``
|
||||
|
||||
Spaces in patterns listed in an exclude file can be specified verbatim. That is,
|
||||
in order to exclude a file named ``foo bar star.txt``, put that just as it reads
|
||||
on one line in the exclude file. Please note that beginning and trailing spaces
|
||||
are trimmed - in order to match these, use e.g. a ``*`` at the beginning or end
|
||||
of the filename.
|
||||
|
||||
Spaces in patterns listed in the other exclude options (e.g. ``--exclude`` on the
|
||||
command line) are specified in different ways depending on the operating system
|
||||
and/or shell. Restic itself does not need any escaping, but your shell may need
|
||||
some escaping in order to pass the name/pattern as a single argument to restic.
|
||||
|
||||
On most Unixy shells, you can either quote or use backslashes. For example:
|
||||
|
||||
* ``--exclude='foo bar star/foo.txt'``
|
||||
* ``--exclude="foo bar star/foo.txt"``
|
||||
* ``--exclude=foo\ bar\ star/foo.txt``
|
||||
|
||||
By specifying the option ``--one-file-system`` you can instruct restic
|
||||
to only backup files from the file systems the initially specified files
|
||||
or directories reside on. For example, calling restic like this won't
|
||||
@@ -207,10 +223,13 @@ backup ``/sys`` or ``/dev`` on a Linux system:
|
||||
.. note:: ``--one-file-system`` is currently unsupported on Windows, and will
|
||||
cause the backup to immediately fail with an error.
|
||||
|
||||
By using the ``--files-from`` option you can read the files you want to
|
||||
backup from one or more files. This is especially useful if a lot of files have
|
||||
to be backed up that are not in the same folder or are maybe pre-filtered
|
||||
by other software.
|
||||
Including Files
|
||||
***************
|
||||
|
||||
By using the ``--files-from`` option you can read the files you want to back
|
||||
up from one or more files. This is especially useful if a lot of files have
|
||||
to be backed up that are not in the same folder or are maybe pre-filtered by
|
||||
other software.
|
||||
|
||||
For example maybe you want to backup files which have a name that matches a
|
||||
certain pattern:
|
||||
@@ -232,7 +251,11 @@ args:
|
||||
|
||||
$ restic -r /srv/restic-repo backup --files-from /tmp/files_to_backup /tmp/some_additional_file
|
||||
|
||||
Paths in the listing file can be absolute or relative.
|
||||
Paths in the listing file can be absolute or relative. Please note that
|
||||
patterns listed in a ``--files-from`` file are treated the same way as
|
||||
exclude patterns are, which means that beginning and trailing spaces are
|
||||
trimmed and special characters must be escaped. See the documentation
|
||||
above for more information.
|
||||
|
||||
Comparing Snapshots
|
||||
*******************
|
||||
@@ -355,6 +378,7 @@ environment variables. The following list of environment variables:
|
||||
RESTIC_REPOSITORY Location of repository (replaces -r)
|
||||
RESTIC_PASSWORD_FILE Location of password file (replaces --password-file)
|
||||
RESTIC_PASSWORD The actual password for the repository
|
||||
RESTIC_PASSWORD_COMMAND Command printing the password for the repository to stdout
|
||||
|
||||
AWS_ACCESS_KEY_ID Amazon S3 access key ID
|
||||
AWS_SECRET_ACCESS_KEY Amazon S3 secret access key
|
||||
|
||||
@@ -82,53 +82,85 @@ Furthermore you can group the output by the same filters (host, paths, tags):
|
||||
1 snapshots
|
||||
|
||||
|
||||
Checking a repo's integrity and consistency
|
||||
===========================================
|
||||
Checking integrity and consistency
|
||||
==================================
|
||||
|
||||
Imagine your repository is saved on a server that has a faulty hard
|
||||
drive, or even worse, attackers get privileged access and modify your
|
||||
backup with the intention to make you restore malicious data:
|
||||
drive, or even worse, attackers get privileged access and modify the
|
||||
files in your repository with the intention to make you restore
|
||||
malicious data:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ echo "boom" >> backup/index/d795ffa99a8ab8f8e42cec1f814df4e48b8f49129360fb57613df93739faee97
|
||||
$ echo "boom" > /srv/restic-repo/index/de30f3231ca2e6a59af4aa84216dfe2ef7339c549dc11b09b84000997b139628
|
||||
|
||||
In order to detect these things, it is a good idea to regularly use the
|
||||
``check`` command to test whether everything is alright, your precious
|
||||
backup data is consistent and the integrity is unharmed:
|
||||
Trying to restore a snapshot which has been modified as shown above
|
||||
will yield an error:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic -r /srv/restic-repo --no-cache restore c23e491f --target /tmp/restore-work
|
||||
...
|
||||
Fatal: unable to load index de30f323: load <index/de30f3231c>: invalid data returned
|
||||
|
||||
In order to detect these things before they become a problem, it's a
|
||||
good idea to regularly use the ``check`` command to test whether your
|
||||
repository is healthy and consistent, and that your precious backup
|
||||
data is unharmed. There are two types of checks that can be performed:
|
||||
|
||||
- Structural consistency and integrity, e.g. snapshots, trees and pack files (default)
|
||||
- Integrity of the actual data that you backed up (enabled with flags, see below)
|
||||
|
||||
To verify the structure of the repository, issue the ``check`` command.
|
||||
If the repository is damaged like in the example above, ``check`` will
|
||||
detect this and yield the same error as when you tried to restore:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic -r /srv/restic-repo check
|
||||
Load indexes
|
||||
ciphertext verification failed
|
||||
...
|
||||
load indexes
|
||||
error: error loading index de30f323: load <index/de30f3231c>: invalid data returned
|
||||
Fatal: LoadIndex returned errors
|
||||
|
||||
Trying to restore a snapshot which has been modified as shown above will
|
||||
yield the same error:
|
||||
If the repository structure is intact, restic will show that no errors were found:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic -r /srv/restic-repo restore 79766175 --target /tmp/restore-work
|
||||
Load indexes
|
||||
ciphertext verification failed
|
||||
$ restic -r /src/restic-repo check
|
||||
...
|
||||
load indexes
|
||||
check all packs
|
||||
check snapshots, trees and blobs
|
||||
no errors were found
|
||||
|
||||
By default, ``check`` command does not check that repository data files
|
||||
are unmodified. Use ``--read-data`` parameter to check all repository
|
||||
data files:
|
||||
By default, the ``check`` command does not verify that the actual data files
|
||||
on disk in the repository are unmodified, because doing so requires reading
|
||||
a copy of every data file in the repository. To tell restic to also verify the
|
||||
integrity of the data files in the repository, use the ``--read-data`` flag:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic -r /srv/restic-repo check --read-data
|
||||
...
|
||||
load indexes
|
||||
check all packs
|
||||
check snapshots, trees and blobs
|
||||
read all data
|
||||
[0:00] 100.00% 3 / 3 items
|
||||
duration: 0:00
|
||||
no errors were found
|
||||
|
||||
Use ``--read-data-subset=n/t`` parameter to check subset of repository data
|
||||
files. The parameter takes two values, ``n`` and ``t``. All repository data
|
||||
files are logically divided in ``t`` roughly equal groups and only files that
|
||||
belong to the group number ``n`` are checked. For example, the following
|
||||
commands check all repository data files over 5 separate invocations:
|
||||
.. note:: Since ``--read-data`` has to download all data files in the
|
||||
repository, beware that it might incur higher bandwidth costs than usual
|
||||
and also that it takes more time than the default ``check``.
|
||||
|
||||
Alternatively, use the ``--read-data-subset=n/t`` parameter to check only a
|
||||
subset of the repository data files at a time. The parameter takes two values,
|
||||
``n`` and ``t``. When the check command runs, all data files in the repository
|
||||
are logically divided in ``t`` (roughly equal) groups, and only files that
|
||||
belong to group number ``n`` are checked. For example, the following commands
|
||||
check all repository data files over 5 separate invocations:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@@ -137,4 +169,3 @@ commands check all repository data files over 5 separate invocations:
|
||||
$ restic -r /srv/restic-repo check --read-data-subset=3/5
|
||||
$ restic -r /srv/restic-repo check --read-data-subset=4/5
|
||||
$ restic -r /srv/restic-repo check --read-data-subset=5/5
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ This will restore the file ``foo`` to ``/tmp/restore-work/work/foo``.
|
||||
|
||||
You can use the command ``restic ls latest`` or ``restic find foo`` to find the
|
||||
path to the file within the snapshot. This path you can then pass to
|
||||
`--include` in verbatim to only restore the single file or directory.
|
||||
``--include`` in verbatim to only restore the single file or directory.
|
||||
|
||||
There are case insensitive variants of of ``--exclude`` and ``--include`` called
|
||||
``--iexclude`` and ``--iinclude``. These options will behave the same way but
|
||||
@@ -131,6 +131,6 @@ output the contents in the tar format:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic -r /srv/restic-repo dump /home/other/work latest > restore.tar
|
||||
$ restic -r /srv/restic-repo dump latest /home/other/work > restore.tar
|
||||
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ To only keep the last snapshot of all snapshots with both the tag ``foo`` and
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic forget --tag foo,tag bar --keep-last 1
|
||||
$ restic forget --tag foo,bar --keep-last 1
|
||||
|
||||
All the ``--keep-*`` options above only count
|
||||
hours/days/weeks/months/years which have a snapshot, so those without a
|
||||
@@ -213,12 +213,66 @@ All snapshots are evaluated against all matching ``--keep-*`` counts. A
|
||||
single snapshot on 2017-09-30 (Sat) will count as a daily, weekly and monthly.
|
||||
|
||||
Let's explain this with an example: Suppose you have only made a backup
|
||||
on each Sunday for 12 weeks. Then ``forget --keep-daily 4`` will keep
|
||||
the last four snapshots for the last four Sundays, but remove the rest.
|
||||
Only counting the days which have a backup and ignore the ones without
|
||||
is a safety feature: it prevents restic from removing many snapshots
|
||||
when no new ones are created. If it was implemented otherwise, running
|
||||
``forget --keep-daily 4`` on a Friday would remove all snapshots!
|
||||
on each Sunday for 12 weeks:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic snapshots
|
||||
repository f00c6e2a opened successfully, password is correct
|
||||
ID Time Host Tags Paths
|
||||
---------------------------------------------------------------
|
||||
0a1f9759 2019-09-01 11:00:00 mopped /home/user/work
|
||||
46cfe4d5 2019-09-08 11:00:00 mopped /home/user/work
|
||||
f6b1f037 2019-09-15 11:00:00 mopped /home/user/work
|
||||
eb430a5d 2019-09-22 11:00:00 mopped /home/user/work
|
||||
8cf1cb9a 2019-09-29 11:00:00 mopped /home/user/work
|
||||
5d33b116 2019-10-06 11:00:00 mopped /home/user/work
|
||||
b9553125 2019-10-13 11:00:00 mopped /home/user/work
|
||||
e1a7b58b 2019-10-20 11:00:00 mopped /home/user/work
|
||||
8f8018c0 2019-10-27 11:00:00 mopped /home/user/work
|
||||
59403279 2019-11-03 11:00:00 mopped /home/user/work
|
||||
dfee9fb4 2019-11-10 11:00:00 mopped /home/user/work
|
||||
e1ae2f40 2019-11-17 11:00:00 mopped /home/user/work
|
||||
---------------------------------------------------------------
|
||||
12 snapshots
|
||||
|
||||
Then ``forget --keep-daily 4`` will keep the last four snapshots for the last
|
||||
four Sundays, but remove the rest:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ restic forget --keep-daily 4 --dry-run
|
||||
repository f00c6e2a opened successfully, password is correct
|
||||
Applying Policy: keep the last 4 daily snapshots
|
||||
keep 4 snapshots:
|
||||
ID Time Host Tags Reasons Paths
|
||||
-------------------------------------------------------------------------------
|
||||
8f8018c0 2019-10-27 11:00:00 mopped daily snapshot /home/user/work
|
||||
59403279 2019-11-03 11:00:00 mopped daily snapshot /home/user/work
|
||||
dfee9fb4 2019-11-10 11:00:00 mopped daily snapshot /home/user/work
|
||||
e1ae2f40 2019-11-17 11:00:00 mopped daily snapshot /home/user/work
|
||||
-------------------------------------------------------------------------------
|
||||
4 snapshots
|
||||
|
||||
remove 8 snapshots:
|
||||
ID Time Host Tags Paths
|
||||
---------------------------------------------------------------
|
||||
0a1f9759 2019-09-01 11:00:00 mopped /home/user/work
|
||||
46cfe4d5 2019-09-08 11:00:00 mopped /home/user/work
|
||||
f6b1f037 2019-09-15 11:00:00 mopped /home/user/work
|
||||
eb430a5d 2019-09-22 11:00:00 mopped /home/user/work
|
||||
8cf1cb9a 2019-09-29 11:00:00 mopped /home/user/work
|
||||
5d33b116 2019-10-06 11:00:00 mopped /home/user/work
|
||||
b9553125 2019-10-13 11:00:00 mopped /home/user/work
|
||||
e1a7b58b 2019-10-20 11:00:00 mopped /home/user/work
|
||||
---------------------------------------------------------------
|
||||
8 snapshots
|
||||
|
||||
The result of the ``forget --keep-daily`` operation does not depend on when it
|
||||
is run, it will only count the days for which a snapshot exists. This is a
|
||||
safety feature: it prevents restic from removing snapshots when no new ones are
|
||||
created. Otherwise, running ``forget --keep-daily 4`` on a Friday (without any
|
||||
snapshot Monday to Thursday) would remove all snapshots!
|
||||
|
||||
Another example: Suppose you make daily backups for 100 years. Then
|
||||
``forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --keep-yearly 75``
|
||||
|
||||
@@ -247,8 +247,13 @@ restic is now ready to be used with AWS S3. Try to create a backup:
|
||||
----------------------------------------------------------------------
|
||||
10fdbace 2017-03-26 16:41:50 blackbox /home/philip/restic-demo/test.bin
|
||||
|
||||
A snapshot was created and stored in the S3 bucket. This snapshot may now be
|
||||
restored:
|
||||
A snapshot was created and stored in the S3 bucket. By default backups to AWS S3 will use the ``STANDARD`` storage class. Available storage classes include ``STANDARD``, ``STANDARD_IA``, ``ONEZONE_IA``, ``INTELLIGENT_TIERING``, and ``REDUCED_REDUNDANCY``. A different storage class could have been specified in the above command by using ``-o`` or ``--option``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ./restic backup -o s3.storage-class=REDUCED_REDUNDANCY test.bin
|
||||
|
||||
This snapshot may now be restored:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
||||
@@ -22,9 +22,7 @@ The program can be built with debug support like this:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ go run build.go -mod=vendor -tags debug
|
||||
|
||||
For Go < 1.11, the option ``-mod=vendor`` needs to be removed.
|
||||
$ go run build.go -tags debug
|
||||
|
||||
Afterwards, extensive debug messages are written to the file in
|
||||
environment variable ``DEBUG_LOG``, e.g.:
|
||||
|
||||
BIN
doc/_static/favicon.ico
vendored
BIN
doc/_static/favicon.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -1026,8 +1026,6 @@ _restic_mount()
|
||||
|
||||
flags+=("--allow-other")
|
||||
local_nonpersistent_flags+=("--allow-other")
|
||||
flags+=("--allow-root")
|
||||
local_nonpersistent_flags+=("--allow-root")
|
||||
flags+=("--help")
|
||||
flags+=("-h")
|
||||
local_nonpersistent_flags+=("--help")
|
||||
|
||||
@@ -47,10 +47,10 @@ In the following example, we'll use the file ``restic-0.9.3.tar.gz`` and Go
|
||||
$ go version
|
||||
go version go1.11.1 linux/amd64
|
||||
|
||||
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor -ldflags "-s -w" -tags selfupdate -o restic_linux_amd64 ./cmd/restic
|
||||
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -tags selfupdate -o restic_linux_amd64 ./cmd/restic
|
||||
$ bzip2 restic_linux_amd64
|
||||
|
||||
$ GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor -ldflags "-s -w" -tags selfupdate -o restic_windows_amd64.exe ./cmd/restic
|
||||
$ GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -tags selfupdate -o restic_windows_amd64.exe ./cmd/restic
|
||||
$ touch --reference VERSION restic_windows_amd64.exe
|
||||
$ TZ=Europe/Berlin zip -q -X restic_windows_amd64.zip restic_windows_amd64.exe
|
||||
|
||||
@@ -105,7 +105,7 @@ The following steps are necessary to build the binaries:
|
||||
--volume "$PWD/restic-0.9.3:/restic" \
|
||||
--volume "$PWD/output:/output" \
|
||||
restic/builder \
|
||||
go run -mod=vendor helpers/build-release-binaries/main.go --verbose
|
||||
go run helpers/build-release-binaries/main.go --verbose
|
||||
|
||||
Prepare a New Release
|
||||
*********************
|
||||
@@ -118,6 +118,6 @@ required argument is the new version number (in `Semantic Versioning
|
||||
|
||||
.. code::
|
||||
|
||||
go run -mod=vendor helpers/prepare-release/main.go 0.9.3
|
||||
go run helpers/prepare-release/main.go 0.9.3
|
||||
|
||||
Checks can be skipped on demand via flags, please see ``--help`` for details.
|
||||
|
||||
14
doc/faq.rst
14
doc/faq.rst
@@ -142,6 +142,9 @@ is not slowed down, which is particularly useful for servers.
|
||||
Creating new repo on a Synology NAS via sftp fails
|
||||
--------------------------------------------------
|
||||
|
||||
For using restic with a Synology NAS via sftp, please make sure that the
|
||||
specified path is absolute, it must start with a slash (``/``).
|
||||
|
||||
Sometimes creating a new restic repository on a Synology NAS via sftp fails
|
||||
with an error similar to the following:
|
||||
|
||||
@@ -160,8 +163,8 @@ different than the directory structure on the device and maybe even as exposed
|
||||
via other protocols.
|
||||
|
||||
|
||||
Try removing the /volume1 prefix in your paths. If this does not work, use sftp
|
||||
and ls to explore the SFTP file system hierarchy on your NAS.
|
||||
Try removing the ``/volume1`` prefix in your paths. If this does not work, use
|
||||
sftp and ls to explore the SFTP file system hierarchy on your NAS.
|
||||
|
||||
The following may work:
|
||||
|
||||
@@ -172,4 +175,9 @@ The following may work:
|
||||
Why does restic perform so poorly on Windows?
|
||||
---------------------------------------------
|
||||
|
||||
In some cases the real-time protection of antivirus software can interfere with restic's operations. If you are experiencing bad performance you can try to temporarily disable your antivirus software to find out if it is the cause for your performance problems.
|
||||
In some cases the real-time protection of antivirus software can interfere with
|
||||
restic's operations. If you are experiencing bad performance you can try to
|
||||
temporarily disable your antivirus software to find out if it is the cause for
|
||||
your performance problems. If you are certain that the antivirus software is
|
||||
the cause for this and you want to gain maximum performance, you have to add
|
||||
the restic binary to an exclusions list within the antivirus software.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user