2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-26 23:06:32 +00:00

Merge pull request 2205 from heikobornholdt/ignore-inode

Add --ignore-inode option to backup cmd
This commit is contained in:
Alexander Neumann 2019-03-16 12:16:31 +01:00
commit 1b94ae1c00
5 changed files with 48 additions and 11 deletions

View File

@ -0,0 +1,10 @@
Enhancement: Add --ignore-inode option to backup cmd
This option handles backup of virtual filesystems that do not keep fixed
inodes for files, like Fuse-based, pCloud, etc. Ignoring inode changes allows
to consider the file as unchanged if last modification date and size
are unchanged.
https://github.com/restic/restic/pull/2205
https://github.com/restic/restic/pull/2047
https://github.com/restic/restic/issues/1631

View File

@ -85,6 +85,7 @@ type BackupOptions struct {
FilesFrom []string FilesFrom []string
TimeStamp string TimeStamp string
WithAtime bool WithAtime bool
IgnoreInode bool
} }
var backupOptions BackupOptions var backupOptions BackupOptions
@ -112,6 +113,7 @@ func init() {
f.StringArrayVar(&backupOptions.FilesFrom, "files-from", nil, "read the files to backup from file (can be combined with file args/can be specified multiple times)") f.StringArrayVar(&backupOptions.FilesFrom, "files-from", nil, "read the files to backup from file (can be combined with file args/can be specified multiple times)")
f.StringVar(&backupOptions.TimeStamp, "time", "", "time of the backup (ex. '2012-11-01 22:08:41') (default: now)") f.StringVar(&backupOptions.TimeStamp, "time", "", "time of the backup (ex. '2012-11-01 22:08:41') (default: now)")
f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories") f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories")
f.BoolVar(&backupOptions.IgnoreInode, "ignore-inode", false, "ignore inode number changes when checking for modified files")
} }
// filterExisting returns a slice of all existing items, or an error if no // filterExisting returns a slice of all existing items, or an error if no
@ -549,6 +551,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
arch.CompleteItem = p.CompleteItem arch.CompleteItem = p.CompleteItem
arch.StartFile = p.StartFile arch.StartFile = p.StartFile
arch.CompleteBlob = p.CompleteBlob arch.CompleteBlob = p.CompleteBlob
arch.IgnoreInode = opts.IgnoreInode
if parentSnapshotID == nil { if parentSnapshotID == nil {
parentSnapshotID = &restic.ID{} parentSnapshotID = &restic.ID{}

View File

@ -279,6 +279,10 @@ written, and the next backup needs to write new metadata again. If you really
want to save the access time for files and directories, you can pass the want to save the access time for files and directories, you can pass the
``--with-atime`` option to the ``backup`` command. ``--with-atime`` option to the ``backup`` command.
In filesystems that do not support inode consistency, like FUSE-based ones and pCloud, it is
possible to ignore inode on changed files comparison by passing ``--ignore-inode`` to
``backup`` command.
Reading data from stdin Reading data from stdin
*********************** ***********************

View File

@ -79,6 +79,7 @@ type Archiver struct {
// be saved. Enabling it may result in much metadata, so it's off by // be saved. Enabling it may result in much metadata, so it's off by
// default. // default.
WithAtime bool WithAtime bool
IgnoreInode bool
} }
// Options is used to configure the archiver. // Options is used to configure the archiver.
@ -133,6 +134,7 @@ func New(repo restic.Repository, fs fs.FS, opts Options) *Archiver {
CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {}, CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {},
StartFile: func(string) {}, StartFile: func(string) {},
CompleteBlob: func(string, uint64) {}, CompleteBlob: func(string, uint64) {},
IgnoreInode: false,
} }
return arch return arch
@ -383,7 +385,7 @@ func (arch *Archiver) Save(ctx context.Context, snPath, target string, previous
} }
// use previous node if the file hasn't changed // use previous node if the file hasn't changed
if previous != nil && !fileChanged(fi, previous) { if previous != nil && !fileChanged(fi, previous, arch.IgnoreInode) {
debug.Log("%v hasn't changed, returning old node", target) debug.Log("%v hasn't changed, returning old node", target)
arch.CompleteItem(snPath, previous, previous, ItemStats{}, time.Since(start)) arch.CompleteItem(snPath, previous, previous, ItemStats{}, time.Since(start))
arch.CompleteBlob(snPath, previous.Size) arch.CompleteBlob(snPath, previous.Size)
@ -436,7 +438,7 @@ func (arch *Archiver) Save(ctx context.Context, snPath, target string, previous
// fileChanged returns true if the file's content has changed since the node // fileChanged returns true if the file's content has changed since the node
// was created. // was created.
func fileChanged(fi os.FileInfo, node *restic.Node) bool { func fileChanged(fi os.FileInfo, node *restic.Node, ignoreInode bool) bool {
if node == nil { if node == nil {
return true return true
} }
@ -458,7 +460,7 @@ func fileChanged(fi os.FileInfo, node *restic.Node) bool {
} }
// check inode // check inode
if node.Inode != extFI.Inode { if !ignoreInode && node.Inode != extFI.Inode {
return true return true
} }

View File

@ -555,9 +555,11 @@ func TestFileChanged(t *testing.T) {
} }
var tests = []struct { var tests = []struct {
Name string Name string
Content []byte Content []byte
Modify func(t testing.TB, filename string) Modify func(t testing.TB, filename string)
IgnoreInode bool
Check bool
}{ }{
{ {
Name: "same-content-new-file", Name: "same-content-new-file",
@ -596,6 +598,18 @@ func TestFileChanged(t *testing.T) {
save(t, filename, defaultContent) save(t, filename, defaultContent)
}, },
}, },
{
Name: "ignore-inode",
Modify: func(t testing.TB, filename string) {
fi := lstat(t, filename)
remove(t, filename)
sleep()
save(t, filename, defaultContent)
setTimestamp(t, filename, fi.ModTime(), fi.ModTime())
},
IgnoreInode: true,
Check: true,
},
} }
for _, test := range tests { for _, test := range tests {
@ -613,15 +627,19 @@ func TestFileChanged(t *testing.T) {
fiBefore := lstat(t, filename) fiBefore := lstat(t, filename)
node := nodeFromFI(t, filename, fiBefore) node := nodeFromFI(t, filename, fiBefore)
if fileChanged(fiBefore, node) { if fileChanged(fiBefore, node, false) {
t.Fatalf("unchanged file detected as changed") t.Fatalf("unchanged file detected as changed")
} }
test.Modify(t, filename) test.Modify(t, filename)
fiAfter := lstat(t, filename) fiAfter := lstat(t, filename)
if !fileChanged(fiAfter, node) { if test.Check == fileChanged(fiAfter, node, test.IgnoreInode) {
t.Fatalf("modified file detected as unchanged") if test.Check {
t.Fatalf("unmodified file detected as changed")
} else {
t.Fatalf("modified file detected as unchanged")
}
} }
}) })
} }
@ -637,7 +655,7 @@ func TestFilChangedSpecialCases(t *testing.T) {
t.Run("nil-node", func(t *testing.T) { t.Run("nil-node", func(t *testing.T) {
fi := lstat(t, filename) fi := lstat(t, filename)
if !fileChanged(fi, nil) { if !fileChanged(fi, nil, false) {
t.Fatal("nil node detected as unchanged") t.Fatal("nil node detected as unchanged")
} }
}) })
@ -646,7 +664,7 @@ func TestFilChangedSpecialCases(t *testing.T) {
fi := lstat(t, filename) fi := lstat(t, filename)
node := nodeFromFI(t, filename, fi) node := nodeFromFI(t, filename, fi)
node.Type = "symlink" node.Type = "symlink"
if !fileChanged(fi, node) { if !fileChanged(fi, node, false) {
t.Fatal("node with changed type detected as unchanged") t.Fatal("node with changed type detected as unchanged")
} }
}) })