Warn about duplicate files when syncing - fixes #1506
Error about unsorted directories and test thoroughly
This commit is contained in:
parent
dc56ad9816
commit
575e779b55
2 changed files with 214 additions and 28 deletions
113
fs/sync.go
113
fs/sync.go
|
@ -836,6 +836,77 @@ func (s *syncCopyMove) transfer(dst, src DirEntry, job listDirJob, jobs *[]listD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type matchPair struct {
|
||||||
|
src, dst DirEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the two sorted listings, matching up the items in the two
|
||||||
|
// sorted slices
|
||||||
|
//
|
||||||
|
// Into srcOnly go Entries which only exist in the srcList
|
||||||
|
// Into dstOnly go Entries which only exist in the dstList
|
||||||
|
// Into matches go matchPair's of src and dst which have the same name
|
||||||
|
//
|
||||||
|
// This checks for duplicates and checks the list is sorted.
|
||||||
|
func matchListings(srcList, dstList DirEntries) (srcOnly DirEntries, dstOnly DirEntries, matches []matchPair) {
|
||||||
|
for iSrc, iDst := 0, 0; ; iSrc, iDst = iSrc+1, iDst+1 {
|
||||||
|
var src, dst DirEntry
|
||||||
|
var srcRemote, dstRemote string
|
||||||
|
if iSrc < len(srcList) {
|
||||||
|
src = srcList[iSrc]
|
||||||
|
srcRemote = src.Remote()
|
||||||
|
}
|
||||||
|
if iDst < len(dstList) {
|
||||||
|
dst = dstList[iDst]
|
||||||
|
dstRemote = dst.Remote()
|
||||||
|
}
|
||||||
|
if src == nil && dst == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if src != nil && iSrc > 0 {
|
||||||
|
prev := srcList[iSrc-1].Remote()
|
||||||
|
if srcRemote == prev {
|
||||||
|
Logf(src, "Duplicate file found in source - ignoring")
|
||||||
|
src = nil // ignore the src
|
||||||
|
} else if srcRemote < prev {
|
||||||
|
Errorf(src, "Out of order listing in source")
|
||||||
|
src = nil // ignore the src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dst != nil && iDst > 0 {
|
||||||
|
prev := dstList[iDst-1].Remote()
|
||||||
|
if dstRemote == prev {
|
||||||
|
Logf(dst, "Duplicate file found in destination - ignoring")
|
||||||
|
dst = nil // ignore the dst
|
||||||
|
} else if dstRemote < prev {
|
||||||
|
Errorf(dst, "Out of order listing in destination")
|
||||||
|
dst = nil // ignore the dst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src != nil && dst != nil {
|
||||||
|
if srcRemote < dstRemote {
|
||||||
|
dst = nil
|
||||||
|
iDst-- // retry the dst
|
||||||
|
} else if srcRemote > dstRemote {
|
||||||
|
src = nil
|
||||||
|
iSrc-- // retry the src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Debugf(nil, "src = %v, dst = %v", src, dst)
|
||||||
|
switch {
|
||||||
|
case src == nil && dst == nil:
|
||||||
|
// do nothing
|
||||||
|
case src == nil:
|
||||||
|
dstOnly = append(dstOnly, dst)
|
||||||
|
case dst == nil:
|
||||||
|
srcOnly = append(srcOnly, src)
|
||||||
|
default:
|
||||||
|
matches = append(matches, matchPair{src: src, dst: dst})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// returns errors using processError
|
// returns errors using processError
|
||||||
func (s *syncCopyMove) _run(job listDirJob) (jobs []listDirJob) {
|
func (s *syncCopyMove) _run(job listDirJob) (jobs []listDirJob) {
|
||||||
var (
|
var (
|
||||||
|
@ -873,39 +944,25 @@ func (s *syncCopyMove) _run(job listDirJob) (jobs []listDirJob) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the two listings, matching up the items in the two sorted slices
|
// Work out what to do and do it
|
||||||
for iSrc, iDst := 0, 0; ; iSrc, iDst = iSrc+1, iDst+1 {
|
srcOnly, dstOnly, matches := matchListings(srcList, dstList)
|
||||||
|
for _, src := range srcOnly {
|
||||||
if s.aborting() {
|
if s.aborting() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var src, dst DirEntry
|
s.srcOnly(src, job, &jobs)
|
||||||
if iSrc < len(srcList) {
|
}
|
||||||
src = srcList[iSrc]
|
for _, dst := range dstOnly {
|
||||||
|
if s.aborting() {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if iDst < len(dstList) {
|
s.dstOnly(dst, job, &jobs)
|
||||||
dst = dstList[iDst]
|
}
|
||||||
}
|
for _, match := range matches {
|
||||||
if src == nil && dst == nil {
|
if s.aborting() {
|
||||||
break
|
return nil
|
||||||
}
|
|
||||||
if src != nil && dst != nil {
|
|
||||||
if src.Remote() < dst.Remote() {
|
|
||||||
dst = nil
|
|
||||||
iDst-- // retry the dst
|
|
||||||
} else if src.Remote() > dst.Remote() {
|
|
||||||
src = nil
|
|
||||||
iSrc-- // retry the src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Debugf(nil, "src = %v, dst = %v", src, dst)
|
|
||||||
switch {
|
|
||||||
case src == nil:
|
|
||||||
s.dstOnly(dst, job, &jobs)
|
|
||||||
case dst == nil:
|
|
||||||
s.srcOnly(src, job, &jobs)
|
|
||||||
default:
|
|
||||||
s.transfer(dst, src, job, &jobs)
|
|
||||||
}
|
}
|
||||||
|
s.transfer(match.dst, match.src, job, &jobs)
|
||||||
}
|
}
|
||||||
return jobs
|
return jobs
|
||||||
}
|
}
|
||||||
|
|
129
fs/sync_internal_test.go
Normal file
129
fs/sync_internal_test.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// Internal tests for sync/copy/move
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchListings(t *testing.T) {
|
||||||
|
var (
|
||||||
|
a = mockObject("a")
|
||||||
|
b = mockObject("b")
|
||||||
|
c = mockObject("c")
|
||||||
|
d = mockObject("d")
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
what string
|
||||||
|
input DirEntries // pairs of input src, dst
|
||||||
|
srcOnly DirEntries
|
||||||
|
dstOnly DirEntries
|
||||||
|
matches []matchPair // pairs of output
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
what: "only src or dst",
|
||||||
|
input: DirEntries{
|
||||||
|
a, nil,
|
||||||
|
b, nil,
|
||||||
|
c, nil,
|
||||||
|
d, nil,
|
||||||
|
},
|
||||||
|
srcOnly: DirEntries{
|
||||||
|
a, b, c, d,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
what: "typical sync #1",
|
||||||
|
input: DirEntries{
|
||||||
|
a, nil,
|
||||||
|
b, b,
|
||||||
|
nil, c,
|
||||||
|
nil, d,
|
||||||
|
},
|
||||||
|
srcOnly: DirEntries{
|
||||||
|
a,
|
||||||
|
},
|
||||||
|
dstOnly: DirEntries{
|
||||||
|
c, d,
|
||||||
|
},
|
||||||
|
matches: []matchPair{
|
||||||
|
{b, b},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
what: "typical sync #2",
|
||||||
|
input: DirEntries{
|
||||||
|
a, a,
|
||||||
|
b, b,
|
||||||
|
nil, c,
|
||||||
|
d, d,
|
||||||
|
},
|
||||||
|
dstOnly: DirEntries{
|
||||||
|
c,
|
||||||
|
},
|
||||||
|
matches: []matchPair{
|
||||||
|
{a, a},
|
||||||
|
{b, b},
|
||||||
|
{d, d},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
what: "One duplicate",
|
||||||
|
input: DirEntries{
|
||||||
|
a, a,
|
||||||
|
a, nil,
|
||||||
|
},
|
||||||
|
matches: []matchPair{
|
||||||
|
{a, a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
what: "Two duplicates",
|
||||||
|
input: DirEntries{
|
||||||
|
a, a,
|
||||||
|
a, a,
|
||||||
|
a, nil,
|
||||||
|
},
|
||||||
|
matches: []matchPair{
|
||||||
|
{a, a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
what: "Out of order",
|
||||||
|
input: DirEntries{
|
||||||
|
c, nil,
|
||||||
|
b, b,
|
||||||
|
a, nil,
|
||||||
|
},
|
||||||
|
srcOnly: DirEntries{
|
||||||
|
c,
|
||||||
|
},
|
||||||
|
dstOnly: DirEntries{
|
||||||
|
b,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
var srcList, dstList DirEntries
|
||||||
|
for i := 0; i < len(test.input); i += 2 {
|
||||||
|
src, dst := test.input[i], test.input[i+1]
|
||||||
|
if src != nil {
|
||||||
|
srcList = append(srcList, src)
|
||||||
|
}
|
||||||
|
if dst != nil {
|
||||||
|
dstList = append(dstList, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srcOnly, dstOnly, matches := matchListings(srcList, dstList)
|
||||||
|
assert.Equal(t, test.srcOnly, srcOnly, test.what)
|
||||||
|
assert.Equal(t, test.dstOnly, dstOnly, test.what)
|
||||||
|
assert.Equal(t, test.matches, matches, test.what)
|
||||||
|
// now swap src and dst
|
||||||
|
dstOnly, srcOnly, matches = matchListings(dstList, srcList)
|
||||||
|
assert.Equal(t, test.srcOnly, srcOnly, test.what)
|
||||||
|
assert.Equal(t, test.dstOnly, dstOnly, test.what)
|
||||||
|
assert.Equal(t, test.matches, matches, test.what)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue