Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] master from restic:master #82

Merged
merged 37 commits into from
Aug 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
696c18e
Add possibility to set snapshot ID (used in test)
aawsome Dec 28, 2020
57f4003
Generalize fuse snapshot dirs implemetation
aawsome Sep 2, 2020
1751afa
Make snapshots dirs in mount command customizable
aawsome Sep 3, 2020
f678f7c
fuse: cleanup test
MichaelEischer Jul 24, 2022
2db7733
fuse: remove unused MetaDir
MichaelEischer Jul 30, 2022
0a6fa60
add option for setting min pack size
MichaelEischer Jul 2, 2022
0269381
prune: add repack-small parameter
metalsp0rk Apr 30, 2022
6a6d313
prune: reduce priority of repacking small packs
MichaelEischer Jun 11, 2022
1e3f05c
repository: prevent header overfill
metalsp0rk Apr 30, 2022
e43be84
document minPackSize
metalsp0rk Apr 30, 2022
420ddc0
rework pack size parameter documentation
MichaelEischer Jun 12, 2022
8a44258
update restic help snippets in documentation
MichaelEischer Jun 12, 2022
d7e2892
Add changelog for packsize option
MichaelEischer Jul 2, 2022
1b076cd
rename option to --pack-size
MichaelEischer Jul 2, 2022
324935c
Only repack small files if there are multiple of them
MichaelEischer Jul 2, 2022
176b387
Always repack very small pack files
MichaelEischer Jul 2, 2022
7f3b2be
s3: Disable multipart uploads below 200MB
MichaelEischer Jul 13, 2022
eaf4360
Add note that pack-size is not an exact limit
MichaelEischer Jul 16, 2022
55a11c1
Reword prune --repack-small description
MichaelEischer Jul 16, 2022
7266f07
repository: StreamPack in parts if there are too large gaps
MichaelEischer Jul 23, 2022
82c268c
Remove unused hooks mechanism
MichaelEischer Jul 16, 2022
38becfc
debug: enable debug support for release builds
MichaelEischer Jul 16, 2022
1ed775e
debug: support roundtripper logging also for release builds
MichaelEischer Jul 16, 2022
b3cdee6
update documentation to reflect DEBUG_LOG for release builds
MichaelEischer Jul 16, 2022
a3e48da
Add changelog for DEBUG_LOG available in release builds
MichaelEischer Jul 16, 2022
caa1798
fuse: Redesign snapshot dirstruct
MichaelEischer Jul 30, 2022
83b4c50
Mention --snapshot-template and --time-template in changelog
MichaelEischer Aug 5, 2022
09497ae
Merge pull request #3826 from MichaelEischer/debug-log-for-release
MichaelEischer Aug 7, 2022
74ae760
Merge pull request #2913 from aawsome/mount-snapshot-slashes
MichaelEischer Aug 7, 2022
cfa80e2
mount: remove unused inode field from root node
Feb 24, 2020
0b7291b
mount: Fix parent inode used by snapshots dir
MichaelEischer Aug 7, 2022
0b9b4c5
Update tests to Go 1.19
lbausch Aug 4, 2022
b82f482
Bump golangci-lint version
lbausch Aug 4, 2022
f3fdc66
restic: Use stable sorting in snapshot policy
MichaelEischer Aug 7, 2022
8fa64a8
Merge pull request #3036 from greatroar/refactor-fuse
MichaelEischer Aug 7, 2022
2930a10
Merge pull request #3731 from metalsp0rk/feature/min-packsize-flag
MichaelEischer Aug 7, 2022
9ad3ad5
Merge pull request #3850 from lbausch/go1.19
MichaelEischer Aug 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
pull_request:

env:
latest_go: "1.18.x"
latest_go: "1.19.x"
GO111MODULE: on

jobs:
Expand All @@ -19,24 +19,30 @@ jobs:
# list of jobs to run:
include:
- job_name: Windows
go: 1.18.x
go: 1.19.x
os: windows-latest
install_verb: install

- job_name: macOS
go: 1.18.x
go: 1.19.x
os: macOS-latest
test_fuse: false
install_verb: install

- job_name: Linux
go: 1.18.x
go: 1.19.x
os: ubuntu-latest
test_cloud_backends: true
test_fuse: true
check_changelog: true
install_verb: install

- job_name: Linux
go: 1.18.x
os: ubuntu-latest
test_fuse: true
install_verb: install

- job_name: Linux
go: 1.17.x
os: ubuntu-latest
Expand Down Expand Up @@ -260,7 +266,7 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.45
version: v1.48
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
args: --verbose --timeout 5m
Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ environment was used and so on. Please tell us at least the following things:
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 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:
In addition, you can instruct restic 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
Expand Down
8 changes: 8 additions & 0 deletions changelog/unreleased/issue-1842
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Change: Support debug log creation in release builds

Creating a debug log was only possible in debug builds which required users to
manually build restic. We changed the release builds to allow creating debug
logs by setting the environment variable `DEBUG_LOG=logname.log`.

https://github.com/restic/restic/issues/1842
https://github.com/restic/restic/pull/3826
12 changes: 12 additions & 0 deletions changelog/unreleased/issue-2291
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Enhancement: Allow pack size customization

Restic now uses a target pack size of 16 MiB by default. It can be customized
using the `--pack-size size` option. Supported pack sizes range between 4 and
128 MiB.

It is possible to migrate an existing repository to _larger_ pack files using
`prune --repack-small`. This will rewrite every pack file which is
significantly smaller than the target size.

https://github.com/restic/restic/issues/2291
https://github.com/restic/restic/pull/3731
10 changes: 10 additions & 0 deletions changelog/unreleased/issue-2907
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Enhancement: Make snapshot directory structure of mount command custimizable

We've added the possibility to customize the snapshot directory structure of
the mount command using templates passed to `--snapshot-template`. The
formatting of the time for a snapshot is now controlled using `--time-template`
and supports subdirectories to for example group snapshots by year. Please
refer to the help output of the `mount` command for further details.

https://github.com/restic/restic/issues/2907
https://github.com/restic/restic/pull/2913
8 changes: 7 additions & 1 deletion cmd/restic/cmd_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ func runInit(opts InitOptions, gopts GlobalOptions, args []string) error {
return errors.Fatalf("create repository at %s failed: %v\n", location.StripPassword(gopts.Repo), err)
}

s := repository.New(be, repository.Options{Compression: gopts.Compression})
s, err := repository.New(be, repository.Options{
Compression: gopts.Compression,
PackSize: gopts.PackSize * 1024 * 1024,
})
if err != nil {
return err
}

err = s.Init(gopts.ctx, version, gopts.password, chunkerPolynomial)
if err != nil {
Expand Down
52 changes: 38 additions & 14 deletions cmd/restic/cmd_mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ read-only mount.
Snapshot Directories
====================

If you need a different template for all directories that contain snapshots,
you can pass a template via --snapshot-template. Example without colons:
If you need a different template for directories that contain snapshots,
you can pass a time template via --time-template and path templates via
--path-template.

--snapshot-template "2006-01-02_15-04-05"
Example time template without colons:

--time-template "2006-01-02_15-04-05"

You need to specify a sample format for exactly the following timestamp:

Expand All @@ -43,6 +46,20 @@ 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

For path templates, you can use the following patterns which will be replaced:
%i by short snapshot ID
%I by long snapshot ID
%u by username
%h by hostname
%t by tags
%T by timestamp as specified by --time-template

The default path templates are:
"ids/%i"
"snapshots/%T"
"hosts/%h/%T"
"tags/%t/%T"

EXIT STATUS
===========

Expand All @@ -62,7 +79,8 @@ type MountOptions struct {
Hosts []string
Tags restic.TagLists
Paths []string
SnapshotTemplate string
TimeTemplate string
PathTemplates []string
}

var mountOptions MountOptions
Expand All @@ -79,16 +97,21 @@ func init() {
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`")

mountFlags.StringVar(&mountOptions.SnapshotTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
mountFlags.StringArrayVar(&mountOptions.PathTemplates, "path-template", nil, "set `template` for path names (can be specified multiple times)")
mountFlags.StringVar(&mountOptions.TimeTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
mountFlags.StringVar(&mountOptions.TimeTemplate, "time-template", time.RFC3339, "set `template` to use for times")
_ = mountFlags.MarkDeprecated("snapshot-template", "use --time-template")
}

func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
if opts.SnapshotTemplate == "" {
return errors.Fatal("snapshot template string cannot be empty")
if opts.TimeTemplate == "" {
return errors.Fatal("time template string cannot be empty")
}
if strings.ContainsAny(opts.SnapshotTemplate, `\/`) {
return errors.Fatal("snapshot template string contains a slash (/) or backslash (\\) character")

if strings.HasPrefix(opts.TimeTemplate, "/") || strings.HasSuffix(opts.TimeTemplate, "/") {
return errors.Fatal("time template string cannot start or end with '/'")
}

if len(args) == 0 {
return errors.Fatal("wrong number of parameters")
}
Expand Down Expand Up @@ -154,11 +177,12 @@ func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
}

cfg := fuse.Config{
OwnerIsRoot: opts.OwnerRoot,
Hosts: opts.Hosts,
Tags: opts.Tags,
Paths: opts.Paths,
SnapshotTemplate: opts.SnapshotTemplate,
OwnerIsRoot: opts.OwnerRoot,
Hosts: opts.Hosts,
Tags: opts.Tags,
Paths: opts.Paths,
TimeTemplate: opts.TimeTemplate,
PathTemplates: opts.PathTemplates,
}
root := fuse.NewRoot(repo, cfg)

Expand Down
34 changes: 30 additions & 4 deletions cmd/restic/cmd_prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type PruneOptions struct {
MaxRepackBytes uint64

RepackCachableOnly bool
RepackSmall bool
RepackUncompressed bool
}

Expand All @@ -70,6 +71,7 @@ func addPruneOptions(c *cobra.Command) {
f.StringVar(&pruneOptions.MaxUnused, "max-unused", "5%", "tolerate given `limit` of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')")
f.StringVar(&pruneOptions.MaxRepackSize, "max-repack-size", "", "maximum `size` to repack (allowed suffixes: k/K, m/M, g/G, t/T)")
f.BoolVar(&pruneOptions.RepackCachableOnly, "repack-cacheable-only", false, "only repack packs which are cacheable")
f.BoolVar(&pruneOptions.RepackSmall, "repack-small", false, "repack pack files below 80%% of target pack size")
f.BoolVar(&pruneOptions.RepackUncompressed, "repack-uncompressed", false, "repack all uncompressed data")
}

Expand Down Expand Up @@ -422,7 +424,14 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
repackPacks := restic.NewIDSet()

var repackCandidates []packInfoWithID
var repackSmallCandidates []packInfoWithID
repoVersion := repo.Config().Version
// only repack very small files by default
targetPackSize := repo.PackSize() / 25
if opts.RepackSmall {
// consider files with at least 80% of the target size as large enough
targetPackSize = repo.PackSize() / 5 * 4
}

// loop over all packs and decide what to do
bar := newProgressMax(!gopts.Quiet, uint64(len(indexPack)), "packs processed")
Expand Down Expand Up @@ -477,8 +486,12 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
stats.packs.keep++

case p.unusedBlobs == 0 && p.tpe != restic.InvalidBlob && !mustCompress:
// All blobs in pack are used and not mixed => keep pack!
stats.packs.keep++
if packSize >= int64(targetPackSize) {
// All blobs in pack are used and not mixed => keep pack!
stats.packs.keep++
} else {
repackSmallCandidates = append(repackSmallCandidates, packInfoWithID{ID: id, packInfo: p})
}

default:
// all other packs are candidates for repacking
Expand Down Expand Up @@ -521,11 +534,19 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
}
}

if len(repackSmallCandidates) < 10 {
// too few small files to be worth the trouble, this also prevents endlessly repacking
// if there is just a single pack file below the target size
stats.packs.keep += uint(len(repackSmallCandidates))
} else {
repackCandidates = append(repackCandidates, repackSmallCandidates...)
}

// Sort repackCandidates such that packs with highest ratio unused/used space are picked first.
// This is equivalent to sorting by unused / total space.
// Instead of unused[i] / used[i] > unused[j] / used[j] we use
// unused[i] * used[j] > unused[j] * used[i] as uint32*uint32 < uint64
// Morover packs containing trees are sorted to the beginning
// Moreover packs containing trees and too small packs are sorted to the beginning
sort.Slice(repackCandidates, func(i, j int) bool {
pi := repackCandidates[i].packInfo
pj := repackCandidates[j].packInfo
Expand All @@ -534,6 +555,10 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
return true
case pj.tpe != restic.DataBlob && pi.tpe == restic.DataBlob:
return false
case pi.unusedSize+pi.usedSize < uint64(targetPackSize) && pj.unusedSize+pj.usedSize >= uint64(targetPackSize):
return true
case pj.unusedSize+pj.usedSize < uint64(targetPackSize) && pi.unusedSize+pi.usedSize >= uint64(targetPackSize):
return false
}
return pi.unusedSize*pj.usedSize > pj.unusedSize*pi.usedSize
})
Expand All @@ -552,6 +577,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
for _, p := range repackCandidates {
reachedUnusedSizeAfter := (stats.size.unused-stats.size.remove-stats.size.repackrm < maxUnusedSizeAfter)
reachedRepackSize := stats.size.repack+p.unusedSize+p.usedSize >= opts.MaxRepackBytes
packIsLargeEnough := p.unusedSize+p.usedSize >= uint64(targetPackSize)

switch {
case reachedRepackSize:
Expand All @@ -561,7 +587,7 @@ func decidePackAction(ctx context.Context, opts PruneOptions, gopts GlobalOption
// repacking non-data packs / uncompressed-trees is only limited by repackSize
repack(p.ID, p.packInfo)

case reachedUnusedSizeAfter:
case reachedUnusedSizeAfter && packIsLargeEnough:
// for all other packs stop repacking if tolerated unused size is reached.
stats.packs.keep++

Expand Down
14 changes: 13 additions & 1 deletion cmd/restic/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -62,6 +63,7 @@ type GlobalOptions struct {
NoCache bool
CleanupCache bool
Compression repository.CompressionMode
PackSize uint

backend.TransportOptions
limiter.Limits
Expand Down Expand Up @@ -102,6 +104,9 @@ func init() {
return nil
})

// parse target pack size from env, on error the default value will be used
targetPackSize, _ := strconv.ParseUint(os.Getenv("RESTIC_PACK_SIZE"), 10, 32)

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.RepositoryFile, "repository-file", "", os.Getenv("RESTIC_REPOSITORY_FILE"), "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)")
Expand All @@ -121,6 +126,7 @@ func init() {
f.Var(&globalOptions.Compression, "compression", "compression mode (only available for repository format version 2), one of (auto|off|max)")
f.IntVar(&globalOptions.Limits.UploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)")
f.IntVar(&globalOptions.Limits.DownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)")
f.UintVar(&globalOptions.PackSize, "pack-size", uint(targetPackSize), "set target pack size in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)")
f.StringSliceVarP(&globalOptions.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)")
// Use our "generate" command instead of the cobra provided "completion" command
cmdRoot.CompletionOptions.DisableDefaultCmd = true
Expand Down Expand Up @@ -440,7 +446,13 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
}
}

s := repository.New(be, repository.Options{Compression: opts.Compression})
s, err := repository.New(be, repository.Options{
Compression: opts.Compression,
PackSize: opts.PackSize * 1024 * 1024,
})
if err != nil {
return nil, err
}

passwordTriesLeft := 1
if stdinIsTerminal() && opts.password == "" {
Expand Down
2 changes: 1 addition & 1 deletion cmd/restic/integration_fuse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func waitForMount(t testing.TB, dir string) {

func testRunMount(t testing.TB, gopts GlobalOptions, dir string) {
opts := MountOptions{
SnapshotTemplate: time.RFC3339,
TimeTemplate: time.RFC3339,
}
rtest.OK(t, runMount(opts, gopts, []string{dir}))
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/restic/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,11 @@ func testPruneVariants(t *testing.T, unsafeNoSpaceRecovery bool) {
checkOpts := CheckOptions{ReadData: true}
testPrune(t, opts, checkOpts)
})
t.Run("Small", func(t *testing.T) {
opts := PruneOptions{MaxUnused: "unlimited", RepackSmall: true}
checkOpts := CheckOptions{ReadData: true, CheckUnused: true}
testPrune(t, opts, checkOpts)
})
}

func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
Expand Down
1 change: 1 addition & 0 deletions doc/040_backup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ environment variables. The following lists these environment variables:
RESTIC_CACHE_DIR Location of the cache directory
RESTIC_COMPRESSION Compression mode (only available for repository format version 2)
RESTIC_PROGRESS_FPS Frames per second by which the progress bar is updated
RESTIC_PACK_SIZE Target size for pack files

TMPDIR Location for temporary files

Expand Down
Loading