Commit f5cc2cb9 authored by Félix Sipma's avatar Félix Sipma

Update upstream source from tag 'upstream/0.9.1+ds'

Update to upstream version '0.9.1+ds'
with Debian dir b824e285db8790a94cc835213ee25b6105b1a9e4
parents 3b34353e 66c10b91
......@@ -5,7 +5,7 @@ matrix:
include:
- os: linux
go: "1.9.x"
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0
env: RESTIC_TEST_FUSE=0 RESTIC_TEST_CLOUD_BACKENDS=0 RESTIC_BUILD_SOLARIS=0
# only run fuse and cloud backends tests on Travis for the latest Go on Linux
- os: linux
......
Changelog for restic 0.9.1 (2018-06-10)
=======================================
The following sections list the changes in restic 0.9.1 relevant to
restic users. The changes are ordered by importance.
Summary
-------
* Fix #1801: Add limiting bandwidth to the rclone backend
* Fix #1822: Allow uploading large files to MS Azure
* Fix #1825: Correct `find` to not skip snapshots
* Fix #1833: Fix caching files on error
* Fix #1834: Resolve deadlock
Details
-------
* Bugfix #1801: Add limiting bandwidth to the rclone backend
The rclone backend did not respect `--limit-upload` or `--limit-download`. Oftentimes it's
not necessary to use this, as the limiting in rclone itself should be used because it gives much
better results, but in case a remote instance of rclone is used (e.g. called via ssh), it is still
relevant to limit the bandwidth from restic to rclone.
https://github.com/restic/restic/issues/1801
* Bugfix #1822: Allow uploading large files to MS Azure
Sometimes, restic creates files to be uploaded to the repository which are quite large, e.g.
when saving directories with many entries or very large files. The MS Azure API does not allow
uploading files larger that 256MiB directly, rather restic needs to upload them in blocks of
100MiB. This is now implemented.
https://github.com/restic/restic/issues/1822
* Bugfix #1825: Correct `find` to not skip snapshots
Under certain circumstances, the `find` command was found to skip snapshots containing
directories with files to look for when the directories haven't been modified at all, and were
already printed as part of a different snapshot. This is now corrected.
In addition, we've switched to our own matching/pattern implementation, so now things like
`restic find "/home/user/foo/**/main.go"` are possible.
https://github.com/restic/restic/issues/1825
https://github.com/restic/restic/issues/1823
* Bugfix #1833: Fix caching files on error
During `check` it may happen that different threads access the same file in the backend, which
is then downloaded into the cache only once. When that fails, only the thread which is
responsible for downloading the file signals the correct error. The other threads just assume
that the file has been downloaded successfully and then get an error when they try to access the
cached file.
https://github.com/restic/restic/issues/1833
* Bugfix #1834: Resolve deadlock
When the "scanning" process restic runs to find out how much data there is does not finish before
the backup itself is done, restic stops doing anything. This is resolved now.
https://github.com/restic/restic/issues/1834
https://github.com/restic/restic/pull/1835
Changelog for restic 0.9.0 (2018-05-21)
=======================================
......@@ -22,7 +89,6 @@ Summary
* Enh #1477: Accept AWS_SESSION_TOKEN for the s3 backend
* Enh #1648: Ignore AWS permission denied error when creating a repository
* Enh #1649: Add illumos/Solaris support
* Enh #1676: Improve backup speed: Skip initial scan phase in quiet mode
* Enh #1709: Improve messages `restic check` prints
* Enh #827: Add --new-password-file flag for non-interactive password changes
* Enh #1735: Allow keeping a time range of snaphots
......@@ -217,15 +283,6 @@ Details
https://github.com/restic/restic/pull/1649
* Enhancement #1676: Improve backup speed: Skip initial scan phase in quiet mode
We've improved the backup speed when the quiet flag (`-q` or `--quiet`) is set by skipping the
initial scan which gathers information for displaying the progress bar and the ETA
estimation.
https://github.com/restic/restic/issues/1160
https://github.com/restic/restic/pull/1676
* Enhancement #1709: Improve messages `restic check` prints
Some messages `restic check` prints are not really errors, so from now on restic does not treat
......
# restic project governance
## Overview
The restic project uses a governance model commonly described as Benevolent
Dictator For Life (BDFL). This document outlines our understanding of what this
means. It is derived from the [i3 window manager project
governance](https://raw.githubusercontent.com/i3/i3/next/.github/GOVERNANCE.md).
## Roles
* user: anyone who interacts with the restic project
* core contributor: a handful of people who have contributed significantly to
the project by any means (issue triage, support, documentation, code, etc.).
Core contributors are recognizable via GitHub’s "Member" badge.
* Benevolent Dictator For Life (BDFL): a single individual who makes decisions
when consensus cannot be reached. restic's current BDFL is [@fd0](https://github.com/fd0).
## Decision making process
In general, we try to reach consensus in discussions. In case consensus cannot
be reached, the BDFL makes a decision.
## Contribution process
The contribution process is described in a separate document called
[CONTRIBUTING](CONTRIBUTING.md).
......@@ -94,8 +94,8 @@
[[projects]]
name = "github.com/kurin/blazer"
packages = ["b2","base","internal/b2assets","internal/b2types","internal/blog","x/window"]
revision = "b7c9cf27cae3aec98c2caaeb5181608bfe05b17c"
version = "v0.3.1"
revision = "318e9768bf9a0fe52a64b9f8fe74f4f5caef6452"
version = "v0.4.4"
[[projects]]
name = "github.com/marstr/guid"
......
......@@ -230,6 +230,7 @@ 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{}) {
......
Enhancement: Improve backup speed: Skip initial scan phase in quiet mode
We've improved the backup speed when the quiet flag (`-q` or `--quiet`) is set
by skipping the initial scan which gathers information for displaying the
progress bar and the ETA estimation.
https://github.com/restic/restic/pull/1676
https://github.com/restic/restic/issues/1160
Bugfix: Add limiting bandwidth to the rclone backend
The rclone backend did not respect `--limit-upload` or `--limit-download`.
Oftentimes it's not necessary to use this, as the limiting in rclone itself
should be used because it gives much better results, but in case a remote
instance of rclone is used (e.g. called via ssh), it is still relevant to limit
the bandwidth from restic to rclone.
https://github.com/restic/restic/issues/1801
Bugfix: Allow uploading large files to MS Azure
Sometimes, restic creates files to be uploaded to the repository which are
quite large, e.g. when saving directories with many entries or very large
files. The MS Azure API does not allow uploading files larger that 256MiB
directly, rather restic needs to upload them in blocks of 100MiB. This is now
implemented.
https://github.com/restic/restic/issues/1822
Bugfix: Correct `find` to not skip snapshots
Under certain circumstances, the `find` command was found to skip snapshots
containing directories with files to look for when the directories haven't been
modified at all, and were already printed as part of a different snapshot. This
is now corrected.
In addition, we've switched to our own matching/pattern implementation, so now
things like `restic find "/home/user/foo/**/main.go"` are possible.
https://github.com/restic/restic/issues/1825
https://github.com/restic/restic/issues/1823
Bugfix: Fix caching files on error
During `check` it may happen that different threads access the same file in the
backend, which is then downloaded into the cache only once. When that fails,
only the thread which is responsible for downloading the file signals the
correct error. The other threads just assume that the file has been downloaded
successfully and then get an error when they try to access the cached file.
https://github.com/restic/restic/issues/1833
Bugfix: Resolve deadlock
When the "scanning" process restic runs to find out how much data there is does
not finish before the backup itself is done, restic stops doing anything. This
is resolved now.
https://github.com/restic/restic/issues/1834
https://github.com/restic/restic/pull/1835
......@@ -336,6 +336,14 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
return err
}
timeStamp := time.Now()
if opts.TimeStamp != "" {
timeStamp, err = time.Parse(TimeFormat, opts.TimeStamp)
if err != nil {
return errors.Fatalf("error in time option: %v\n", err)
}
}
var t tomb.Tomb
p := ui.NewBackup(term, gopts.verbosity)
......@@ -402,14 +410,6 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
return true
}
timeStamp := time.Now()
if opts.TimeStamp != "" {
timeStamp, err = time.Parse(TimeFormat, opts.TimeStamp)
if err != nil {
return errors.Fatalf("error in time option: %v\n", err)
}
}
var targetFS fs.FS = fs.Local{}
if opts.Stdin {
p.V("read data from stdin")
......
......@@ -3,7 +3,6 @@ package main
import (
"context"
"encoding/json"
"path/filepath"
"strings"
"time"
......@@ -11,7 +10,9 @@ import (
"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/walker"
)
var cmdFind = &cobra.Command{
......@@ -94,7 +95,7 @@ type statefulOutput struct {
hits int
}
func (s *statefulOutput) PrintJSON(prefix string, node *restic.Node) {
func (s *statefulOutput) PrintJSON(path string, node *restic.Node) {
type findNode restic.Node
b, err := json.Marshal(struct {
// Add these attributes
......@@ -111,7 +112,7 @@ func (s *statefulOutput) PrintJSON(prefix string, node *restic.Node) {
Content byte `json:"content,omitempty"`
Subtree byte `json:"subtree,omitempty"`
}{
Path: filepath.Join(prefix, node.Name),
Path: path,
Permissions: node.Mode.String(),
findNode: (*findNode)(node),
})
......@@ -138,22 +139,22 @@ func (s *statefulOutput) PrintJSON(prefix string, node *restic.Node) {
s.hits++
}
func (s *statefulOutput) PrintNormal(prefix string, node *restic.Node) {
func (s *statefulOutput) PrintNormal(path string, node *restic.Node) {
if s.newsn != s.oldsn {
if s.oldsn != nil {
Verbosef("\n")
}
s.oldsn = s.newsn
Verbosef("Found matching entries in snapshot %s\n", s.oldsn.ID())
Verbosef("Found matching entries in snapshot %s\n", s.oldsn.ID().Str())
}
Printf(formatNode(prefix, node, s.ListLong) + "\n")
Printf(formatNode(path, node, s.ListLong) + "\n")
}
func (s *statefulOutput) Print(prefix string, node *restic.Node) {
func (s *statefulOutput) Print(path string, node *restic.Node) {
if s.JSON {
s.PrintJSON(prefix, node)
s.PrintJSON(path, node)
} else {
s.PrintNormal(prefix, node)
s.PrintNormal(path, node)
}
}
......@@ -174,74 +175,75 @@ func (s *statefulOutput) Finish() {
// Finder bundles information needed to find a file or directory.
type Finder struct {
repo restic.Repository
pat findPattern
out statefulOutput
notfound restic.IDSet
repo restic.Repository
pat findPattern
out statefulOutput
ignoreTrees restic.IDSet
}
func (f *Finder) findInTree(ctx context.Context, treeID restic.ID, prefix string) error {
if f.notfound.Has(treeID) {
debug.Log("%v skipping tree %v, has already been checked", prefix, treeID)
return nil
}
debug.Log("%v checking tree %v\n", prefix, treeID)
func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error {
debug.Log("searching in snapshot %s\n for entries within [%s %s]", sn.ID(), f.pat.oldest, f.pat.newest)
tree, err := f.repo.LoadTree(ctx, treeID)
if err != nil {
return err
if sn.Tree == nil {
return errors.Errorf("snapshot %v has no tree", sn.ID().Str())
}
var found bool
for _, node := range tree.Nodes {
debug.Log(" testing entry %q\n", node.Name)
f.out.newsn = sn
return walker.Walk(ctx, f.repo, *sn.Tree, f.ignoreTrees, func(nodepath string, node *restic.Node, err error) (bool, error) {
if err != nil {
return false, err
}
if node == nil {
return false, nil
}
name := node.Name
if f.pat.ignoreCase {
name = strings.ToLower(name)
}
m, err := filepath.Match(f.pat.pattern, name)
foundMatch, err := filter.Match(f.pat.pattern, nodepath)
if err != nil {
return err
return false, err
}
if m {
if !f.pat.oldest.IsZero() && node.ModTime.Before(f.pat.oldest) {
debug.Log(" ModTime is older than %s\n", f.pat.oldest)
continue
var (
ignoreIfNoMatch = true
errIfNoMatch error
)
if node.Type == "dir" {
childMayMatch, err := filter.ChildMatch(f.pat.pattern, nodepath)
if err != nil {
return false, err
}
if !f.pat.newest.IsZero() && node.ModTime.After(f.pat.newest) {
debug.Log(" ModTime is newer than %s\n", f.pat.newest)
continue
if !childMayMatch {
ignoreIfNoMatch = true
errIfNoMatch = walker.SkipNode
} else {
ignoreIfNoMatch = false
}
debug.Log(" found match\n")
found = true
f.out.Print(prefix, node)
}
if node.Type == "dir" {
if err := f.findInTree(ctx, *node.Subtree, filepath.Join(prefix, node.Name)); err != nil {
return err
}
if !foundMatch {
return ignoreIfNoMatch, errIfNoMatch
}
}
if !found {
f.notfound.Insert(treeID)
}
return nil
}
if !f.pat.oldest.IsZero() && node.ModTime.Before(f.pat.oldest) {
debug.Log(" ModTime is older than %s\n", f.pat.oldest)
return ignoreIfNoMatch, errIfNoMatch
}
func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error {
debug.Log("searching in snapshot %s\n for entries within [%s %s]", sn.ID(), f.pat.oldest, f.pat.newest)
if !f.pat.newest.IsZero() && node.ModTime.After(f.pat.newest) {
debug.Log(" ModTime is newer than %s\n", f.pat.newest)
return ignoreIfNoMatch, errIfNoMatch
}
f.out.newsn = sn
return f.findInTree(ctx, *sn.Tree, string(filepath.Separator))
debug.Log(" found match\n")
f.out.Print(nodepath, node)
return false, nil
})
}
func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
......@@ -289,10 +291,10 @@ func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
defer cancel()
f := &Finder{
repo: repo,
pat: pat,
out: statefulOutput{ListLong: opts.ListLong, JSON: globalOptions.JSON},
notfound: restic.NewIDSet(),
repo: repo,
pat: pat,
out: statefulOutput{ListLong: opts.ListLong, JSON: globalOptions.JSON},
ignoreTrees: restic.NewIDSet(),
}
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, opts.Snapshots) {
if err = f.findInSnapshot(ctx, sn); err != nil {
......
......@@ -2,13 +2,12 @@ package main
import (
"context"
"path/filepath"
"github.com/spf13/cobra"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/walker"
)
var cmdLs = &cobra.Command{
......@@ -46,26 +45,6 @@ func init() {
flags.StringArrayVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
}
func printTree(ctx context.Context, repo *repository.Repository, id *restic.ID, prefix string) error {
tree, err := repo.LoadTree(ctx, *id)
if err != nil {
return err
}
for _, entry := range tree.Nodes {
Printf("%s\n", formatNode(prefix, entry, lsOptions.ListLong))
if entry.Type == "dir" && entry.Subtree != nil {
entryPath := prefix + string(filepath.Separator) + entry.Name
if err = printTree(ctx, repo, entry.Subtree, entryPath); err != nil {
return err
}
}
}
return nil
}
func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
if len(args) == 0 && opts.Host == "" && len(opts.Tags) == 0 && len(opts.Paths) == 0 {
return errors.Fatal("Invalid arguments, either give one or more snapshot IDs or set filters.")
......@@ -85,7 +64,18 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
Verbosef("snapshot %s of %v at %s):\n", sn.ID().Str(), sn.Paths, sn.Time)
if err = printTree(gopts.ctx, repo, sn.Tree, ""); err != nil {
err := walker.Walk(ctx, repo, *sn.Tree, nil, func(nodepath string, node *restic.Node, err error) (bool, error) {
if err != nil {
return false, err
}
if node == nil {
return false, nil
}
Printf("%s\n", formatNode(nodepath, node, lsOptions.ListLong))
return false, nil
})
if err != nil {
return err
}
}
......
......@@ -3,7 +3,6 @@ package main
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/restic/restic/internal/restic"
......@@ -63,10 +62,9 @@ func formatDuration(d time.Duration) string {
return formatSeconds(sec)
}
func formatNode(prefix string, n *restic.Node, long bool) string {
nodepath := prefix + string(filepath.Separator) + n.Name
func formatNode(path string, n *restic.Node, long bool) string {
if !long {
return nodepath
return path
}
var mode os.FileMode
......@@ -92,6 +90,6 @@ func formatNode(prefix string, n *restic.Node, long bool) string {
return fmt.Sprintf("%s %5d %5d %6d %s %s%s",
mode|n.Mode, n.UID, n.GID, n.Size,
n.ModTime.Format(TimeFormat), nodepath,
n.ModTime.Format(TimeFormat), path,
target)
}
......@@ -561,17 +561,18 @@ func open(s string, gopts GlobalOptions, opts options.Options) (restic.Backend,
}
// wrap the transport so that the throughput via HTTP is limited
rt = limiter.NewStaticLimiter(gopts.LimitUploadKb, gopts.LimitDownloadKb).Transport(rt)
lim := limiter.NewStaticLimiter(gopts.LimitUploadKb, gopts.LimitDownloadKb)
rt = lim.Transport(rt)
switch loc.Scheme {
case "local":
be, err = local.Open(cfg.(local.Config))
// wrap the backend in a LimitBackend so that the throughput is limited
be = limiter.LimitBackend(be, limiter.NewStaticLimiter(gopts.LimitUploadKb, gopts.LimitDownloadKb))
be = limiter.LimitBackend(be, lim)
case "sftp":
be, err = sftp.Open(cfg.(sftp.Config))
// wrap the backend in a LimitBackend so that the throughput is limited
be = limiter.LimitBackend(be, limiter.NewStaticLimiter(gopts.LimitUploadKb, gopts.LimitDownloadKb))
be = limiter.LimitBackend(be, lim)
case "s3":
be, err = s3.Open(cfg.(s3.Config), rt)
case "gs":
......@@ -585,7 +586,7 @@ func open(s string, gopts GlobalOptions, opts options.Options) (restic.Backend,
case "rest":
be, err = rest.Open(cfg.(rest.Config), rt)
case "rclone":
be, err = rclone.Open(cfg.(rclone.Config))
be, err = rclone.Open(cfg.(rclone.Config), lim)
default:
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
......@@ -648,7 +649,7 @@ func create(s string, opts options.Options) (restic.Backend, error) {
case "rest":
return rest.Create(cfg.(rest.Config), rt)
case "rclone":
return rclone.Open(cfg.(rclone.Config))
return rclone.Open(cfg.(rclone.Config), nil)
}
debug.Log("invalid repository scheme: %v", s)
......
......@@ -387,23 +387,23 @@ func TestBackupExclude(t *testing.T) {
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
snapshots, snapshotID := lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts))
files := testRunLs(t, env.gopts, snapshotID)
rtest.Assert(t, includes(files, filepath.Join(string(filepath.Separator), "testdata", "foo.tar.gz")),
rtest.Assert(t, includes(files, "/testdata/foo.tar.gz"),
"expected file %q in first snapshot, but it's not included", "foo.tar.gz")
opts.Excludes = []string{"*.tar.gz"}
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
snapshots, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts))
files = testRunLs(t, env.gopts, snapshotID)
rtest.Assert(t, !includes(files, filepath.Join(string(filepath.Separator), "testdata", "foo.tar.gz")),
rtest.Assert(t, !includes(files, "/testdata/foo.tar.gz"),
"expected file %q not in first snapshot, but it's included", "foo.tar.gz")
opts.Excludes = []string{"*.tar.gz", "private/secret"}
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
_, snapshotID = lastSnapshot(snapshots, loadSnapshotMap(t, env.gopts))
files = testRunLs(t, env.gopts, snapshotID)
rtest.Assert(t, !includes(files, filepath.Join(string(filepath.Separator), "testdata", "foo.tar.gz")),
rtest.Assert(t, !includes(files, "/testdata/foo.tar.gz"),
"expected file %q not in first snapshot, but it's included", "foo.tar.gz")
rtest.Assert(t, !includes(files, filepath.Join(string(filepath.Separator), "testdata", "private", "secret", "passwords.txt")),
rtest.Assert(t, !includes(files, "/testdata/private/secret/passwords.txt"),
"expected file %q not in first snapshot, but it's included", "passwords.txt")
}
......
......@@ -74,7 +74,7 @@ installed from the official repos, e.g. with ``apt-get``:
RHEL & CentOS
=============
restic can be installed via copr repository.
restic can be installed via copr repository, for RHEL7/CentOS you can try the following:
.. code-block:: console
......@@ -82,6 +82,18 @@ restic can be installed via copr repository.
$ yum copr enable copart/restic
$ yum install restic
If that doesn't work, you can try adding the repository directly, for CentOS6 use:
.. code-block:: console
$ yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/copart/restic/repo/epel-6/copart-restic-epel-6.repo
For CentOS7 use:
.. code-block:: console
$ yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/copart/restic/repo/epel-7/copart-restic-epel-7.repo
Fedora
======
......
......@@ -164,6 +164,9 @@ The ``forget`` command accepts the following parameters:
years, months, and days, e.g. ``2y5m7d`` will keep all snapshots made in the
two years, five months, and seven days before the latest snapshot.
Multiple policies will be ORed together so as to be as inclusive as possible
for keeping snapshots.
Additionally, you can restrict removing snapshots to those which have a
particular hostname with the ``--hostname`` parameter, or tags with the
``--tag`` option. When multiple tags are specified, only the snapshots
......
......@@ -58,6 +58,9 @@ func (s *Scanner) Scan(ctx context.Context, targets []string) error {
}
}
if ctx.Err() != nil {
return ctx.Err()
}
s.Result("", stats)
return nil
}
......@@ -107,6 +110,9 @@ func (s *Scanner) scan(ctx context.Context, stats ScanStats, target string) (Sca
stats.Others++
}
if ctx.Err() != nil {
return stats, ctx.Err()
}
s.Result(target, stats)
return stats, nil
}
......@@ -2,6 +2,7 @@ package azure
import (
"context"
"encoding/base64"
"io"
"io/ioutil"
"net/http"
......@@ -64,13 +65,13 @@ func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
}
// Open opens the Azure backend at specified container.
func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
func Open(cfg Config, rt http.RoundTripper) (*Backend, error) {
return open(cfg, rt)
}
// Create opens the Azure backend at specified container and creates the container if
// it does not exist yet.
func Create(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
func Create(cfg Config, rt http.RoundTripper) (*Backend, error) {
be, err := open(cfg, rt)
if err != nil {
......@@ -129,8 +130,18 @@ func (be *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindRe
debug.Log("InsertObject(%v, %v)", be.container.Name, objName)
// wrap the reader so that net/http client cannot close the reader
err := be.container.GetBlobReference(objName).CreateBlockBlobFromReader(ioutil.NopCloser(rd), nil)
var err error
if rd.Length() < 256*1024*1024 {
// wrap the reader so that net/http client cannot close the reader
dataReader := ioutil.NopCloser(rd)
// if it's smaller than 256miB, then just create the file directly from the reader
err = be.container.GetBlobReference(objName).CreateBlockBlobFromReader(dataReader, nil)
} else {
// otherwise use the more complicated method
err = be.saveLarge(ctx, objName, rd)