initial commit
This commit is contained in:
parent
c1532179d4
commit
0727585044
5 changed files with 147 additions and 63 deletions
|
@ -74,35 +74,84 @@ func splitPath(p string) []string {
|
||||||
return append(s, f)
|
return append(s, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
|
func cleanupPathList(pathComponentsList [][]string) [][]string {
|
||||||
|
// Build a set of paths for quick lookup
|
||||||
|
pathSet := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, splittedPath := range pathComponentsList {
|
||||||
|
pathStr := path.Join(splittedPath...)
|
||||||
|
pathSet[pathStr] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out subpaths that are covered by parent directories
|
||||||
|
filteredList := [][]string{}
|
||||||
|
|
||||||
|
for _, splittedPath := range pathComponentsList {
|
||||||
|
isCovered := false
|
||||||
|
// Check if any prefix of the path exists in the pathSet
|
||||||
|
for i := len(splittedPath) - 1; i >= 1; i-- {
|
||||||
|
prefixPath := path.Join(splittedPath[0:i]...)
|
||||||
|
if _, exists := pathSet[prefixPath]; exists {
|
||||||
|
isCovered = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isCovered {
|
||||||
|
filteredList = append(filteredList, splittedPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredList
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterPathComponents(pathComponentsList [][]string, prefix string) [][]string {
|
||||||
|
var filteredList [][]string
|
||||||
|
for _, pathComponents := range pathComponentsList {
|
||||||
|
if len(pathComponents) > 1 && pathComponents[0] == prefix {
|
||||||
|
filteredList = append(filteredList, pathComponents[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredList
|
||||||
|
}
|
||||||
|
|
||||||
|
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponentsList [][]string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
|
||||||
// If we print / we need to assume that there are multiple nodes at that
|
// If we print / we need to assume that there are multiple nodes at that
|
||||||
// level in the tree.
|
// level in the tree.
|
||||||
if pathComponents[0] == "" {
|
if pathComponentsList[0][0] == "" {
|
||||||
if err := canWriteArchiveFunc(); err != nil {
|
if err := canWriteArchiveFunc(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d.DumpTree(ctx, tree, "/")
|
return d.DumpTree(ctx, tree, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
item := filepath.Join(prefix, pathComponents[0])
|
|
||||||
l := len(pathComponents)
|
|
||||||
for _, node := range tree.Nodes {
|
for _, node := range tree.Nodes {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
// iterate over all pathComponents
|
||||||
|
for _, pathComponents := range pathComponentsList {
|
||||||
|
item := filepath.Join(prefix, pathComponents[0])
|
||||||
|
l := len(pathComponents)
|
||||||
|
|
||||||
// If dumping something in the highest level it will just take the
|
// If dumping something in the highest level it will just take the
|
||||||
// first item it finds and dump that according to the switch case below.
|
// first item it finds and dump that according to the switch case below.
|
||||||
if node.Name == pathComponents[0] {
|
if node.Name == pathComponents[0] {
|
||||||
switch {
|
switch {
|
||||||
case l == 1 && node.Type == restic.NodeTypeFile:
|
case l == 1 && node.Type == restic.NodeTypeFile:
|
||||||
return d.WriteNode(ctx, node)
|
d.WriteNode(ctx, node)
|
||||||
|
break out
|
||||||
case l > 1 && node.Type == restic.NodeTypeDir:
|
case l > 1 && node.Type == restic.NodeTypeDir:
|
||||||
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
|
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "cannot load subtree for %q", item)
|
return errors.Wrapf(err, "cannot load subtree for %q", item)
|
||||||
}
|
}
|
||||||
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
|
|
||||||
|
newComponentsList := filterPathComponents(pathComponentsList, node.Name)
|
||||||
|
|
||||||
|
printFromTree(ctx, subtree, repo, item, newComponentsList, d, canWriteArchiveFunc)
|
||||||
|
break out
|
||||||
case node.Type == restic.NodeTypeDir:
|
case node.Type == restic.NodeTypeDir:
|
||||||
if err := canWriteArchiveFunc(); err != nil {
|
if err := canWriteArchiveFunc(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -111,19 +160,24 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoade
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d.DumpTree(ctx, subtree, item)
|
d.DumpTree(ctx, subtree, item)
|
||||||
|
break out
|
||||||
case l > 1:
|
case l > 1:
|
||||||
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
|
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
|
||||||
case node.Type != restic.NodeTypeFile:
|
case node.Type != restic.NodeTypeFile:
|
||||||
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
|
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("path %q not found in snapshot", item)
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
//return fmt.Errorf("path %q not found in snapshot", item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []string) error {
|
func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []string) error {
|
||||||
if len(args) != 2 {
|
if len(args) < 2 {
|
||||||
return errors.Fatal("no file and no snapshot ID specified")
|
return errors.Fatal("no file and no snapshot ID specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,11 +188,8 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotIDString := args[0]
|
snapshotIDString := args[0]
|
||||||
pathToPrint := args[1]
|
|
||||||
|
|
||||||
debug.Log("dump file %q from %q", pathToPrint, snapshotIDString)
|
debug.Log("dump file started for %d paths from %q", (len(args) - 1), snapshotIDString)
|
||||||
|
|
||||||
splittedPath := splitPath(path.Clean(pathToPrint))
|
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,7 +239,21 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
||||||
}
|
}
|
||||||
|
|
||||||
d := dump.New(opts.Archive, repo, outputFileWriter)
|
d := dump.New(opts.Archive, repo, outputFileWriter)
|
||||||
err = printFromTree(ctx, tree, repo, "/", splittedPath, d, canWriteArchiveFunc)
|
defer d.Close()
|
||||||
|
|
||||||
|
var splittedPathList [][]string
|
||||||
|
|
||||||
|
for i := 1; i < len(args); i++ {
|
||||||
|
pathToPrint := args[i]
|
||||||
|
debug.Log("dump file %q from %q", pathToPrint, snapshotIDString)
|
||||||
|
|
||||||
|
splittedPath := splitPath(path.Clean(pathToPrint))
|
||||||
|
splittedPathList = append(splittedPathList, splittedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
splittedPathList = cleanupPathList(splittedPathList)
|
||||||
|
|
||||||
|
err = printFromTree(ctx, tree, repo, "/", splittedPathList, d, canWriteArchiveFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("cannot dump file: %v", err)
|
return errors.Fatalf("cannot dump file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package dump
|
package dump
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
@ -19,6 +22,9 @@ type Dumper struct {
|
||||||
format string
|
format string
|
||||||
repo restic.Loader
|
repo restic.Loader
|
||||||
w io.Writer
|
w io.Writer
|
||||||
|
gzipWriter *gzip.Writer
|
||||||
|
zipWriter *zip.Writer
|
||||||
|
tarWriter *tar.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(format string, repo restic.Loader, w io.Writer) *Dumper {
|
func New(format string, repo restic.Loader, w io.Writer) *Dumper {
|
||||||
|
@ -30,6 +36,28 @@ func New(format string, repo restic.Loader, w io.Writer) *Dumper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Dumper) Close() error {
|
||||||
|
if d.tarWriter != nil {
|
||||||
|
err := d.tarWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Close tarWriter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.gzipWriter != nil {
|
||||||
|
err := d.gzipWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Close gzipWriter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.zipWriter != nil {
|
||||||
|
return d.zipWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Dumper) DumpTree(ctx context.Context, tree *restic.Tree, rootPath string) error {
|
func (d *Dumper) DumpTree(ctx context.Context, tree *restic.Tree, rootPath string) error {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -89,6 +89,8 @@ func WriteTest(t *testing.T, format string, cd CheckDump) {
|
||||||
if err := d.DumpTree(ctx, tree, tt.target); err != nil {
|
if err := d.DumpTree(ctx, tree, tt.target); err != nil {
|
||||||
t.Fatalf("Dumper.Run error = %v", err)
|
t.Fatalf("Dumper.Run error = %v", err)
|
||||||
}
|
}
|
||||||
|
d.Close()
|
||||||
|
|
||||||
if err := cd(t, tmpdir, dst); err != nil {
|
if err := cd(t, tmpdir, dst); err != nil {
|
||||||
t.Errorf("WriteDump() = does not match: %v", err)
|
t.Errorf("WriteDump() = does not match: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,16 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *Dumper) dumpTar(ctx context.Context, ch <-chan *restic.Node) (err error) {
|
func (d *Dumper) dumpTar(ctx context.Context, ch <-chan *restic.Node) (err error) {
|
||||||
w := tar.NewWriter(d.w)
|
if d.tarWriter == nil {
|
||||||
|
d.tarWriter = tar.NewWriter(d.w)
|
||||||
defer func() {
|
|
||||||
if err == nil {
|
|
||||||
err = w.Close()
|
|
||||||
err = errors.Wrap(err, "Close")
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
for node := range ch {
|
for node := range ch {
|
||||||
if err := d.dumpNodeTar(ctx, node, w); err != nil {
|
if err := d.dumpNodeTar(ctx, node); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +42,7 @@ func tarIdentifier(id uint32) int {
|
||||||
return int(id)
|
return int(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node, w *tar.Writer) error {
|
func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node) error {
|
||||||
relPath, err := filepath.Rel("/", node.Path)
|
relPath, err := filepath.Rel("/", node.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -93,11 +87,11 @@ func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node, w *tar.Writ
|
||||||
header.Name += "/"
|
header.Name += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
err = w.WriteHeader(header)
|
err = d.tarWriter.WriteHeader(header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing header for %q: %w", node.Path, err)
|
return fmt.Errorf("writing header for %q: %w", node.Path, err)
|
||||||
}
|
}
|
||||||
return d.writeNode(ctx, w, node)
|
return d.writeNode(ctx, d.tarWriter, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseXattrs(xattrs []restic.ExtendedAttribute) map[string]string {
|
func parseXattrs(xattrs []restic.ExtendedAttribute) map[string]string {
|
||||||
|
|
|
@ -10,24 +10,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *Dumper) dumpZip(ctx context.Context, ch <-chan *restic.Node) (err error) {
|
func (d *Dumper) dumpZip(ctx context.Context, ch <-chan *restic.Node) (err error) {
|
||||||
w := zip.NewWriter(d.w)
|
if d.zipWriter == nil {
|
||||||
|
d.zipWriter = zip.NewWriter(d.w)
|
||||||
defer func() {
|
|
||||||
if err == nil {
|
|
||||||
err = w.Close()
|
|
||||||
err = errors.Wrap(err, "Close")
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
for node := range ch {
|
for node := range ch {
|
||||||
if err := d.dumpNodeZip(ctx, node, w); err != nil {
|
if err := d.dumpNodeZip(ctx, node); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Writer) error {
|
func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node) error {
|
||||||
relPath, err := filepath.Rel("/", node.Path)
|
relPath, err := filepath.Rel("/", node.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -44,7 +39,7 @@ func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Wri
|
||||||
header.Name += "/"
|
header.Name += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
w, err := zw.CreateHeader(header)
|
w, err := d.zipWriter.CreateHeader(header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "ZipHeader")
|
return errors.Wrap(err, "ZipHeader")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue