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:
commit
1b94ae1c00
10
changelog/unreleased/pull-2205
Normal file
10
changelog/unreleased/pull-2205
Normal 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
|
@ -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{}
|
||||||
|
@ -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
|
||||||
***********************
|
***********************
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user