@@ -22,45 +22,61 @@ type Output interface {
2222 BeginDirectory (ctx context.Context , relativePath string , e fs.Directory ) error
2323 FinishDirectory (ctx context.Context , relativePath string , e fs.Directory ) error
2424 WriteFile (ctx context.Context , relativePath string , e fs.File ) error
25+ FileExists (ctx context.Context , relativePath string , e fs.File ) bool
2526 CreateSymlink (ctx context.Context , relativePath string , e fs.Symlink ) error
27+ SymlinkExists (ctx context.Context , relativePath string , e fs.Symlink ) bool
2628 Close (ctx context.Context ) error
2729}
2830
2931// Stats represents restore statistics.
3032type Stats struct {
3133 RestoredTotalFileSize int64
3234 EnqueuedTotalFileSize int64
35+ SkippedTotalFileSize int64
3336
3437 RestoredFileCount int32
3538 RestoredDirCount int32
3639 RestoredSymlinkCount int32
3740 EnqueuedFileCount int32
3841 EnqueuedDirCount int32
3942 EnqueuedSymlinkCount int32
43+ SkippedCount int32
44+ IgnoredErrorCount int32
4045}
4146
4247func (s * Stats ) clone () Stats {
4348 return Stats {
4449 RestoredTotalFileSize : atomic .LoadInt64 (& s .RestoredTotalFileSize ),
4550 EnqueuedTotalFileSize : atomic .LoadInt64 (& s .EnqueuedTotalFileSize ),
46- RestoredFileCount : atomic .LoadInt32 (& s .RestoredFileCount ),
47- RestoredDirCount : atomic .LoadInt32 (& s .RestoredDirCount ),
48- RestoredSymlinkCount : atomic .LoadInt32 (& s .RestoredSymlinkCount ),
49- EnqueuedFileCount : atomic .LoadInt32 (& s .EnqueuedFileCount ),
50- EnqueuedDirCount : atomic .LoadInt32 (& s .EnqueuedDirCount ),
51- EnqueuedSymlinkCount : atomic .LoadInt32 (& s .EnqueuedSymlinkCount ),
51+ SkippedTotalFileSize : atomic .LoadInt64 (& s .SkippedTotalFileSize ),
52+
53+ RestoredFileCount : atomic .LoadInt32 (& s .RestoredFileCount ),
54+ RestoredDirCount : atomic .LoadInt32 (& s .RestoredDirCount ),
55+ RestoredSymlinkCount : atomic .LoadInt32 (& s .RestoredSymlinkCount ),
56+ EnqueuedFileCount : atomic .LoadInt32 (& s .EnqueuedFileCount ),
57+ EnqueuedDirCount : atomic .LoadInt32 (& s .EnqueuedDirCount ),
58+ EnqueuedSymlinkCount : atomic .LoadInt32 (& s .EnqueuedSymlinkCount ),
59+ SkippedCount : atomic .LoadInt32 (& s .SkippedCount ),
60+ IgnoredErrorCount : atomic .LoadInt32 (& s .IgnoredErrorCount ),
5261 }
5362}
5463
5564// Options provides optional restore parameters.
5665type Options struct {
5766 Parallel int
5867 ProgressCallback func (ctx context.Context , s Stats )
68+ Incremental bool
69+ IgnoreErrors bool
5970}
6071
6172// Entry walks a snapshot root with given root entry and restores it to the provided output.
6273func Entry (ctx context.Context , rep repo.Repository , output Output , rootEntry fs.Entry , options Options ) (Stats , error ) {
63- c := copier {output : output , q : parallelwork .NewQueue ()}
74+ c := copier {
75+ output : output ,
76+ q : parallelwork .NewQueue (),
77+ incremental : options .Incremental ,
78+ ignoreErrors : options .IgnoreErrors ,
79+ }
6480
6581 c .q .ProgressCallback = func (ctx context.Context , enqueued , active , completed int64 ) {
6682 options .ProgressCallback (ctx , c .stats .clone ())
@@ -91,12 +107,52 @@ func Entry(ctx context.Context, rep repo.Repository, output Output, rootEntry fs
91107}
92108
93109type copier struct {
94- stats Stats
95- output Output
96- q * parallelwork.Queue
110+ stats Stats
111+ output Output
112+ q * parallelwork.Queue
113+ incremental bool
114+ ignoreErrors bool
97115}
98116
99117func (c * copier ) copyEntry (ctx context.Context , e fs.Entry , targetPath string , onCompletion func () error ) error {
118+ if c .incremental {
119+ // in incremental mode, do not copy if the output already exists
120+ switch e := e .(type ) {
121+ case fs.File :
122+ if c .output .FileExists (ctx , targetPath , e ) {
123+ log (ctx ).Debugf ("skipping file %v because it already exists and metadata matches" , targetPath )
124+ atomic .AddInt32 (& c .stats .SkippedCount , 1 )
125+ atomic .AddInt64 (& c .stats .SkippedTotalFileSize , e .Size ())
126+
127+ return onCompletion ()
128+ }
129+
130+ case fs.Symlink :
131+ if c .output .SymlinkExists (ctx , targetPath , e ) {
132+ atomic .AddInt32 (& c .stats .SkippedCount , 1 )
133+ log (ctx ).Debugf ("skipping symlink %v because it already exists" , targetPath )
134+
135+ return onCompletion ()
136+ }
137+ }
138+ }
139+
140+ err := c .copyEntryInternal (ctx , e , targetPath , onCompletion )
141+ if err == nil {
142+ return nil
143+ }
144+
145+ if c .ignoreErrors {
146+ atomic .AddInt32 (& c .stats .IgnoredErrorCount , 1 )
147+ log (ctx ).Warningf ("ignored error %v on %v" , err , targetPath )
148+
149+ return nil
150+ }
151+
152+ return err
153+ }
154+
155+ func (c * copier ) copyEntryInternal (ctx context.Context , e fs.Entry , targetPath string , onCompletion func () error ) error {
100156 switch e := e .(type ) {
101157 case fs.Directory :
102158 log (ctx ).Debugf ("dir: '%v'" , targetPath )
0 commit comments