mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-02 18:22:40 +01:00
feat(shell): add -delete option to remote.copy.local (#10228)
* shell: add -delete option to remote.copy.local Add a -delete flag to remote.copy.local that removes files and directories from remote storage when they no longer exist locally, similar to rsync --delete. This makes the command usable for scheduled one-shot backups that also propagate local deletions. - -include/-exclude patterns also limit which remote files are deleted - size/age filters only apply to copying, since remote entries have no local attributes to filter on - orphaned remote directories are removed after their contents, deepest first, and only when no name filter is set (a recursive RemoveDirectory could otherwise remove intentionally kept files) - deletion is skipped entirely if any copy failed - -dryRun shows DELETE lines for review before committing to anything Fixes seaweedfs/seaweedfs#8609 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * shell: fix remote.copy.local -delete deleting files outside -dir Traverse lists remote objects by key prefix with no delimiter, so a -dir pointing at a subdirectory also matches siblings that merely share its name prefix (foo -> foobar). Those are not under the local traversal root, so -delete treated them as extraneous and removed them. Scope deletion candidates to paths under dirToCopy. Also drop the directory-removal path: RemoveDirectory is a no-op on every backend and Traverse never emits directory entries, so it only ever printed success for work it never did. --------- Co-authored-by: Jason Lin <jason@jtx.com.tw> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
3 files changed
+410
-42
No files matched your search
@@ -152,20 +152,29 @@ func newFileFilter(remoteMountCommand *flag.FlagSet) (ff *FileFilter) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ff *FileFilter) matches(entry *filer_pb.Entry) bool {
|
||||
if entry.Attributes == nil {
|
||||
return false
|
||||
}
|
||||
// matchesName applies only the name-based include/exclude patterns,
|
||||
// usable for remote entries where local attributes are not available.
|
||||
func (ff *FileFilter) matchesName(name string) bool {
|
||||
if *ff.include != "" {
|
||||
if ok, _ := filepath.Match(*ff.include, entry.Name); !ok {
|
||||
if ok, _ := filepath.Match(*ff.include, name); !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if *ff.exclude != "" {
|
||||
if ok, _ := filepath.Match(*ff.exclude, entry.Name); ok {
|
||||
if ok, _ := filepath.Match(*ff.exclude, name); ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ff *FileFilter) matches(entry *filer_pb.Entry) bool {
|
||||
if entry.Attributes == nil {
|
||||
return false
|
||||
}
|
||||
if !ff.matchesName(entry.Name) {
|
||||
return false
|
||||
}
|
||||
if *ff.minSize != -1 {
|
||||
if int64(entry.Attributes.FileSize) < *ff.minSize {
|
||||
return false
|
||||
|
||||
Reference in new issue
Block a user