mirror of https://github.com/restic/restic.git
backup: implement --skip-if-unchanged
This commit is contained in:
parent
7b4f81d964
commit
6869bdaaa8
|
@ -87,6 +87,7 @@ type BackupOptions struct {
|
|||
DryRun bool
|
||||
ReadConcurrency uint
|
||||
NoScan bool
|
||||
SkipIfUnchanged bool
|
||||
}
|
||||
|
||||
var backupOptions BackupOptions
|
||||
|
@ -133,6 +134,7 @@ func init() {
|
|||
if runtime.GOOS == "windows" {
|
||||
f.BoolVar(&backupOptions.UseFsSnapshot, "use-fs-snapshot", false, "use filesystem snapshot where possible (currently only Windows VSS)")
|
||||
}
|
||||
f.BoolVar(&backupOptions.SkipIfUnchanged, "skip-if-unchanged", false, "skip snapshot creation if identical to parent snapshot")
|
||||
|
||||
// parse read concurrency from env, on error the default value will be used
|
||||
readConcurrency, _ := strconv.ParseUint(os.Getenv("RESTIC_READ_CONCURRENCY"), 10, 32)
|
||||
|
@ -638,13 +640,14 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
}
|
||||
|
||||
snapshotOpts := archiver.SnapshotOptions{
|
||||
Excludes: opts.Excludes,
|
||||
Tags: opts.Tags.Flatten(),
|
||||
BackupStart: backupStart,
|
||||
Time: timeStamp,
|
||||
Hostname: opts.Host,
|
||||
ParentSnapshot: parentSnapshot,
|
||||
ProgramVersion: "restic " + version,
|
||||
Excludes: opts.Excludes,
|
||||
Tags: opts.Tags.Flatten(),
|
||||
BackupStart: backupStart,
|
||||
Time: timeStamp,
|
||||
Hostname: opts.Host,
|
||||
ParentSnapshot: parentSnapshot,
|
||||
ProgramVersion: "restic " + version,
|
||||
SkipIfUnchanged: opts.SkipIfUnchanged,
|
||||
}
|
||||
|
||||
if !gopts.JSON {
|
||||
|
@ -665,9 +668,6 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
|
||||
// Report finished execution
|
||||
progressReporter.Finish(id, summary, opts.DryRun)
|
||||
if !gopts.JSON && !opts.DryRun {
|
||||
progressPrinter.P("snapshot %s saved\n", id.Str())
|
||||
}
|
||||
if !success {
|
||||
return ErrInvalidSourceData
|
||||
}
|
||||
|
|
|
@ -641,3 +641,18 @@ func TestBackupEmptyPassword(t *testing.T) {
|
|||
testListSnapshots(t, env.gopts, 1)
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
||||
|
||||
func TestBackupSkipIfUnchanged(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testSetupBackupData(t, env)
|
||||
opts := BackupOptions{SkipIfUnchanged: true}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
}
|
||||
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
||||
|
|
|
@ -173,7 +173,8 @@ Summary is the last output line in a successful backup.
|
|||
+---------------------------+---------------------------------------------------------+
|
||||
| ``total_duration`` | Total time it took for the operation to complete |
|
||||
+---------------------------+---------------------------------------------------------+
|
||||
| ``snapshot_id`` | ID of the new snapshot |
|
||||
| ``snapshot_id`` | ID of the new snapshot. Field is omitted if snapshot |
|
||||
| | creation was skipped |
|
||||
+---------------------------+---------------------------------------------------------+
|
||||
|
||||
|
||||
|
|
|
@ -767,6 +767,8 @@ type SnapshotOptions struct {
|
|||
Time time.Time
|
||||
ParentSnapshot *restic.Snapshot
|
||||
ProgramVersion string
|
||||
// SkipIfUnchanged omits the snapshot creation if it is identical to the parent snapshot.
|
||||
SkipIfUnchanged bool
|
||||
}
|
||||
|
||||
// loadParentTree loads a tree referenced by snapshot id. If id is null, nil is returned.
|
||||
|
@ -880,6 +882,13 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
|
|||
return nil, restic.ID{}, nil, err
|
||||
}
|
||||
|
||||
if opts.ParentSnapshot != nil && opts.SkipIfUnchanged {
|
||||
ps := opts.ParentSnapshot
|
||||
if ps.Tree != nil && rootTreeID.Equal(*ps.Tree) {
|
||||
return nil, restic.ID{}, arch.summary, nil
|
||||
}
|
||||
}
|
||||
|
||||
sn, err := restic.NewSnapshot(targets, opts.Tags, opts.Hostname, opts.Time)
|
||||
if err != nil {
|
||||
return nil, restic.ID{}, nil, err
|
||||
|
|
|
@ -164,6 +164,11 @@ func (b *JSONProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
|||
|
||||
// Finish prints the finishing messages.
|
||||
func (b *JSONProgress) Finish(snapshotID restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) {
|
||||
id := ""
|
||||
// empty if snapshot creation was skipped
|
||||
if !snapshotID.IsNull() {
|
||||
id = snapshotID.String()
|
||||
}
|
||||
b.print(summaryOutput{
|
||||
MessageType: "summary",
|
||||
FilesNew: summary.Files.New,
|
||||
|
@ -179,7 +184,7 @@ func (b *JSONProgress) Finish(snapshotID restic.ID, start time.Time, summary *ar
|
|||
TotalFilesProcessed: summary.Files.New + summary.Files.Changed + summary.Files.Unchanged,
|
||||
TotalBytesProcessed: summary.ProcessedBytes,
|
||||
TotalDuration: time.Since(start).Seconds(),
|
||||
SnapshotID: snapshotID.String(),
|
||||
SnapshotID: id,
|
||||
DryRun: dryRun,
|
||||
})
|
||||
}
|
||||
|
@ -235,6 +240,6 @@ type summaryOutput struct {
|
|||
TotalFilesProcessed uint `json:"total_files_processed"`
|
||||
TotalBytesProcessed uint64 `json:"total_bytes_processed"`
|
||||
TotalDuration float64 `json:"total_duration"` // in seconds
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
SnapshotID string `json:"snapshot_id,omitempty"`
|
||||
DryRun bool `json:"dry_run,omitempty"`
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ func (b *TextProgress) Reset() {
|
|||
}
|
||||
|
||||
// Finish prints the finishing messages.
|
||||
func (b *TextProgress) Finish(_ restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) {
|
||||
func (b *TextProgress) Finish(id restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) {
|
||||
b.P("\n")
|
||||
b.P("Files: %5d new, %5d changed, %5d unmodified\n", summary.Files.New, summary.Files.Changed, summary.Files.Unchanged)
|
||||
b.P("Dirs: %5d new, %5d changed, %5d unmodified\n", summary.Dirs.New, summary.Dirs.Changed, summary.Dirs.Unchanged)
|
||||
|
@ -145,4 +145,12 @@ func (b *TextProgress) Finish(_ restic.ID, start time.Time, summary *archiver.Su
|
|||
ui.FormatBytes(summary.ProcessedBytes),
|
||||
ui.FormatDuration(time.Since(start)),
|
||||
)
|
||||
|
||||
if !dryRun {
|
||||
if id.IsNull() {
|
||||
b.P("skipped creating snapshot\n")
|
||||
} else {
|
||||
b.P("snapshot %s saved\n", id.Str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue