github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/sync/sync.go (about) 1 // Package sync provides the sync command. 2 package sync 3 4 import ( 5 "context" 6 "io" 7 "os" 8 9 mutex "sync" // renamed as "sync" already in use 10 11 "github.com/rclone/rclone/cmd" 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/config/flags" 14 "github.com/rclone/rclone/fs/operations" 15 "github.com/rclone/rclone/fs/operations/operationsflags" 16 "github.com/rclone/rclone/fs/sync" 17 "github.com/spf13/cobra" 18 ) 19 20 var ( 21 createEmptySrcDirs = false 22 opt = operations.LoggerOpt{} 23 loggerFlagsOpt = operationsflags.AddLoggerFlagsOptions{} 24 ) 25 26 func init() { 27 cmd.Root.AddCommand(commandDefinition) 28 cmdFlags := commandDefinition.Flags() 29 flags.BoolVarP(cmdFlags, &createEmptySrcDirs, "create-empty-src-dirs", "", createEmptySrcDirs, "Create empty source dirs on destination after sync", "") 30 operationsflags.AddLoggerFlags(cmdFlags, &opt, &loggerFlagsOpt) 31 // TODO: add same flags to move and copy 32 } 33 34 var lock mutex.Mutex 35 36 func syncLoggerFn(ctx context.Context, sigil operations.Sigil, src, dst fs.DirEntry, err error) { 37 lock.Lock() 38 defer lock.Unlock() 39 40 if err == fs.ErrorIsDir && !opt.FilesOnly && opt.DestAfter != nil { 41 opt.PrintDestAfter(ctx, sigil, src, dst, err) 42 return 43 } 44 45 _, srcOk := src.(fs.Object) 46 _, dstOk := dst.(fs.Object) 47 var filename string 48 if !srcOk && !dstOk { 49 return 50 } else if srcOk && !dstOk { 51 filename = src.String() 52 } else { 53 filename = dst.String() 54 } 55 56 if sigil.Writer(opt) != nil { 57 operations.SyncFprintf(sigil.Writer(opt), "%s\n", filename) 58 } 59 if opt.Combined != nil { 60 operations.SyncFprintf(opt.Combined, "%c %s\n", sigil, filename) 61 fs.Debugf(nil, "Sync Logger: %s: %c %s\n", sigil.String(), sigil, filename) 62 } 63 if opt.DestAfter != nil { 64 opt.PrintDestAfter(ctx, sigil, src, dst, err) 65 } 66 } 67 68 // GetSyncLoggerOpt gets the options corresponding to the logger flags 69 func GetSyncLoggerOpt(ctx context.Context, fdst fs.Fs, command *cobra.Command) (operations.LoggerOpt, func(), error) { 70 closers := []io.Closer{} 71 72 opt.LoggerFn = syncLoggerFn 73 if opt.TimeFormat == "max" { 74 opt.TimeFormat = operations.FormatForLSFPrecision(fdst.Precision()) 75 } 76 opt.SetListFormat(ctx, command.Flags()) 77 opt.NewListJSON(ctx, fdst, "") 78 79 open := func(name string, pout *io.Writer) error { 80 if name == "" { 81 return nil 82 } 83 if name == "-" { 84 *pout = os.Stdout 85 return nil 86 } 87 out, err := os.Create(name) 88 if err != nil { 89 return err 90 } 91 *pout = out 92 closers = append(closers, out) 93 return nil 94 } 95 96 if err := open(loggerFlagsOpt.Combined, &opt.Combined); err != nil { 97 return opt, nil, err 98 } 99 if err := open(loggerFlagsOpt.MissingOnSrc, &opt.MissingOnSrc); err != nil { 100 return opt, nil, err 101 } 102 if err := open(loggerFlagsOpt.MissingOnDst, &opt.MissingOnDst); err != nil { 103 return opt, nil, err 104 } 105 if err := open(loggerFlagsOpt.Match, &opt.Match); err != nil { 106 return opt, nil, err 107 } 108 if err := open(loggerFlagsOpt.Differ, &opt.Differ); err != nil { 109 return opt, nil, err 110 } 111 if err := open(loggerFlagsOpt.ErrFile, &opt.Error); err != nil { 112 return opt, nil, err 113 } 114 if err := open(loggerFlagsOpt.DestAfter, &opt.DestAfter); err != nil { 115 return opt, nil, err 116 } 117 118 close := func() { 119 for _, closer := range closers { 120 err := closer.Close() 121 if err != nil { 122 fs.Errorf(nil, "Failed to close report output: %v", err) 123 } 124 } 125 } 126 127 return opt, close, nil 128 } 129 130 func anyNotBlank(s ...string) bool { 131 for _, x := range s { 132 if x != "" { 133 return true 134 } 135 } 136 return false 137 } 138 139 var commandDefinition = &cobra.Command{ 140 Use: "sync source:path dest:path", 141 Short: `Make source and dest identical, modifying destination only.`, 142 Long: ` 143 Sync the source to the destination, changing the destination 144 only. Doesn't transfer files that are identical on source and 145 destination, testing by size and modification time or MD5SUM. 146 Destination is updated to match source, including deleting files 147 if necessary (except duplicate objects, see below). If you don't 148 want to delete files from destination, use the 149 [copy](/commands/rclone_copy/) command instead. 150 151 **Important**: Since this can cause data loss, test first with the 152 ` + "`--dry-run` or the `--interactive`/`-i`" + ` flag. 153 154 rclone sync --interactive SOURCE remote:DESTINATION 155 156 Note that files in the destination won't be deleted if there were any 157 errors at any point. Duplicate objects (files with the same name, on 158 those providers that support it) are also not yet handled. 159 160 It is always the contents of the directory that is synced, not the 161 directory itself. So when source:path is a directory, it's the contents of 162 source:path that are copied, not the directory name and contents. See 163 extended explanation in the [copy](/commands/rclone_copy/) command if unsure. 164 165 If dest:path doesn't exist, it is created and the source:path contents 166 go there. 167 168 It is not possible to sync overlapping remotes. However, you may exclude 169 the destination from the sync with a filter rule or by putting an 170 exclude-if-present file inside the destination directory and sync to a 171 destination that is inside the source directory. 172 173 Rclone will sync the modification times of files and directories if 174 the backend supports it. If metadata syncing is required then use the 175 ` + "`--metadata`" + ` flag. 176 177 Note that the modification time and metadata for the root directory 178 will **not** be synced. See https://github.com/rclone/rclone/issues/7652 179 for more info. 180 181 **Note**: Use the ` + "`-P`" + `/` + "`--progress`" + ` flag to view real-time transfer statistics 182 183 **Note**: Use the ` + "`rclone dedupe`" + ` command to deal with "Duplicate object/directory found in source/destination - ignoring" errors. 184 See [this forum post](https://forum.rclone.org/t/sync-not-clearing-duplicates/14372) for more info. 185 186 ## Logger Flags 187 188 The ` + "`--differ`" + `, ` + "`--missing-on-dst`" + `, ` + "`--missing-on-src`" + `, ` + 189 "`--match`" + ` and ` + "`--error`" + ` flags write paths, one per line, to the file name (or 190 stdout if it is ` + "`-`" + `) supplied. What they write is described in the 191 help below. For example ` + "`--differ`" + ` will write all paths which are 192 present on both the source and destination but different. 193 194 The ` + "`--combined`" + ` flag will write a file (or stdout) which contains all 195 file paths with a symbol and then a space and then the path to tell 196 you what happened to it. These are reminiscent of diff files. 197 198 - ` + "`= path`" + ` means path was found in source and destination and was identical 199 - ` + "`- path`" + ` means path was missing on the source, so only in the destination 200 - ` + "`+ path`" + ` means path was missing on the destination, so only in the source 201 - ` + "`* path`" + ` means path was present in source and destination but different. 202 - ` + "`! path`" + ` means there was an error reading or hashing the source or dest. 203 204 The ` + "`--dest-after`" + ` flag writes a list file using the same format flags 205 as [` + "`lsf`" + `](/commands/rclone_lsf/#synopsis) (including [customizable options 206 for hash, modtime, etc.](/commands/rclone_lsf/#synopsis)) 207 Conceptually it is similar to rsync's ` + "`--itemize-changes`" + `, but not identical 208 -- it should output an accurate list of what will be on the destination 209 after the sync. 210 211 Note that these logger flags have a few limitations, and certain scenarios 212 are not currently supported: 213 214 - ` + "`--max-duration`" + ` / ` + "`CutoffModeHard`" + ` 215 - ` + "`--compare-dest`" + ` / ` + "`--copy-dest`" + ` 216 - server-side moves of an entire dir at once 217 - High-level retries, because there would be duplicates (use ` + "`--retries 1`" + ` to disable) 218 - Possibly some unusual error scenarios 219 220 Note also that each file is logged during the sync, as opposed to after, so it 221 is most useful as a predictor of what SHOULD happen to each file 222 (which may or may not match what actually DID.) 223 `, 224 Annotations: map[string]string{ 225 "groups": "Sync,Copy,Filter,Listing,Important", 226 }, 227 Run: func(command *cobra.Command, args []string) { 228 cmd.CheckArgs(2, 2, command, args) 229 fsrc, srcFileName, fdst := cmd.NewFsSrcFileDst(args) 230 cmd.Run(true, true, command, func() error { 231 ctx := context.Background() 232 opt, close, err := GetSyncLoggerOpt(ctx, fdst, command) 233 if err != nil { 234 return err 235 } 236 defer close() 237 238 if anyNotBlank(loggerFlagsOpt.Combined, loggerFlagsOpt.MissingOnSrc, loggerFlagsOpt.MissingOnDst, 239 loggerFlagsOpt.Match, loggerFlagsOpt.Differ, loggerFlagsOpt.ErrFile, loggerFlagsOpt.DestAfter) { 240 ctx = operations.WithSyncLogger(ctx, opt) 241 } 242 243 if srcFileName == "" { 244 return sync.Sync(ctx, fdst, fsrc, createEmptySrcDirs) 245 } 246 return operations.CopyFile(ctx, fdst, fsrc, srcFileName, srcFileName) 247 }) 248 }, 249 }