Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/help_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func TestJSONHelpManifestV1MachineFields(t *testing.T) {
if jsonHelpFlagByName(t, put.Flags, dryRunFlagName).ValueKind != "boolean" {
t.Fatalf("put --%s value_kind = %q, want boolean", dryRunFlagName, jsonHelpFlagByName(t, put.Flags, dryRunFlagName).ValueKind)
}
assertStringSliceEqual(t, "put --if-exists enum", jsonHelpFlagByName(t, put.Flags, "if-exists").EnumValues, []string{"overwrite", "skip", "fail", "autorename"})
assertStringSliceEqual(t, "put --if-exists enum", jsonHelpFlagByName(t, put.Flags, "if-exists").EnumValues, []string{"overwrite", "skip", "skip-same", "fail", "autorename"})
if !put.StdinStdout.ReadsStdin || put.StdinStdout.WritesBinaryStdout {
t.Fatalf("put stdin_stdout = %+v, want stdin only", put.StdinStdout)
}
Expand Down Expand Up @@ -311,7 +311,7 @@ func TestJSONHelpManifestV1MachineFields(t *testing.T) {
}
assertJSONHelpInputProperty(t, put.InputSchema, "target", "string", "arg", "target", "dropbox_path")
ifExists := assertJSONHelpInputProperty(t, put.InputSchema, "if_exists", "string", "flag", "if-exists", "enum")
assertStringSliceEqual(t, "put input_schema if_exists enum", ifExists.Enum, []string{"autorename", "fail", "overwrite", "skip"})
assertStringSliceEqual(t, "put input_schema if_exists enum", ifExists.Enum, []string{"autorename", "fail", "overwrite", "skip", "skip-same"})
if ifExists.Default != "overwrite" {
t.Fatalf("put if_exists default = %#v, want overwrite", ifExists.Default)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/help_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ var commandManifestRegistry = map[string]jsonCommandManifestMetadata{
"chunksize": {ValueKind: "bytes"},
"debug": {ValueKind: "boolean"},
dryRunFlagName: {ValueKind: "boolean"},
"if-exists": {EnumValues: []string{"overwrite", "skip", "fail", "autorename"}, ValueKind: "enum"},
"if-exists": {EnumValues: []string{"overwrite", "skip", "skip-same", "fail", "autorename"}, ValueKind: "enum"},
"recursive": {ValueKind: "boolean"},
"workers": {ValueKind: "integer"},
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/json_contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ func jsonGoldenErrorOutputExamples() map[string]jsonErrorResponse {
),
"invalid_arguments": newJSONErrorResponse(
jsonErrorExampleCommand("put"),
invalidArgumentsErrorfWithDetails("invalid --if-exists %q (use overwrite, skip, autorename, or fail)", flagValueErrorDetails("if-exists", "replace"), "replace"),
invalidArgumentsErrorfWithDetails("invalid --if-exists %q (use overwrite, skip, skip-same, autorename, or fail)", flagValueErrorDetails("if-exists", "replace"), "replace"),
),
"not_found": newJSONErrorResponse(
jsonErrorExampleCommand("get"),
Expand Down
44 changes: 41 additions & 3 deletions cmd/put.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

"github.com/dropbox/dbxcli/v3/internal/output"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/contenthash"
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files"
"github.com/dustin/go-humanize"
"github.com/mitchellh/ioprogress"
Expand All @@ -45,6 +46,7 @@ const (

putIfExistsOverwrite = "overwrite"
putIfExistsSkip = "skip"
putIfExistsSkipSame = "skip-same"
putIfExistsFail = "fail"
putIfExistsAutorename = "autorename"
)
Expand Down Expand Up @@ -542,10 +544,10 @@ func normalizePutIfExists(ifExists string) (string, error) {
return putIfExistsOverwrite, nil
}
switch ifExists {
case putIfExistsOverwrite, putIfExistsSkip, putIfExistsFail, putIfExistsAutorename:
case putIfExistsOverwrite, putIfExistsSkip, putIfExistsSkipSame, putIfExistsFail, putIfExistsAutorename:
return ifExists, nil
default:
return "", invalidArgumentsErrorfWithDetails("invalid --if-exists %q (use overwrite, skip, autorename, or fail)", flagValueErrorDetails("if-exists", ifExists), ifExists)
return "", invalidArgumentsErrorfWithDetails("invalid --if-exists %q (use overwrite, skip, skip-same, autorename, or fail)", flagValueErrorDetails("if-exists", ifExists), ifExists)
}
}

Expand Down Expand Up @@ -587,6 +589,14 @@ func putFileWithResult(src, dst string, opts putOptions) (putResult, error) {
return putResult{}, err
}

if ifExists == putIfExistsSkipSame && existingMetadata != nil {
if err := comparePutContents(contents, contentsInfo.Size(), existingMetadata, dst); err != nil {
return putResult{}, err
}
putOutput(opts).Status("Skipping %s (same contents)", dst)
return newPutResult(putStatusSkipped, putKindFile, src, dst, existingMetadata)
}

commitInfo := files.NewCommitInfo(dst)
commitInfo.Mode.Tag = writeModeForIfExists(ifExists)
commitInfo.StrictConflict = ifExists != putIfExistsOverwrite
Expand Down Expand Up @@ -618,6 +628,28 @@ func putFileWithResult(src, dst string, opts putOptions) (putResult, error) {
return newPutResult(putUploadStatus(ifExists, dst, metadata), putKindFile, src, dst, metadata)
}

// comparePutContents only permits skipping when equality can be established.
func comparePutContents(contents io.Reader, size int64, metadata files.IsMetadata, dst string) error {
remote, ok := metadata.(*files.FileMetadata)
if !ok || remote == nil {
return pathConflictErrorWithPath(dst, "destination %q is not a file", dst)
}
if uint64(size) != remote.Size {
return pathConflictErrorWithPath(dst, "destination %q has different contents", dst)
}
if remote.ContentHash == "" {
return pathConflictErrorWithPath(dst, "cannot compare destination %q: content hash is unavailable", dst)
}
localHash, err := contenthash.Compute(contents)
if err != nil {
return fmt.Errorf("compute local content hash: %w", err)
}
if localHash != remote.ContentHash {
return pathConflictErrorWithPath(dst, "destination %q has different contents", dst)
}
return nil
}

// putUploadStatus reports "autorenamed" when --if-exists=autorename caused the
// server to store the file under a different path than requested; otherwise it
// reports the normal "uploaded" status.
Expand Down Expand Up @@ -683,6 +715,8 @@ func actionForExistingDestination(dst string, ifExists string, metadata files.Is
switch ifExists {
case putIfExistsSkip:
return putDestinationSkip, metadata, nil
case putIfExistsSkipSame:
return putDestinationUpload, metadata, nil
case putIfExistsFail:
return putDestinationUpload, nil, pathConflictErrorWithPath(dst, "destination %q already exists", dst)
default:
Expand Down Expand Up @@ -1079,11 +1113,15 @@ var putCmd = &cobra.Command{
- Use - as source to read from stdin (target is required).
Stdin is spooled to a temp file before upload and may use disk
space up to the full input size.
- Use --if-exists skip-same to skip files with identical contents and fail
when contents differ or the remote content hash is unavailable.
Dry-run only plans uploads; it does not compare contents.
- Files larger than 32MiB use Dropbox upload sessions. Each chunk is one
upload-session request; chunk size must be a multiple of 4MiB and no more
than 128MiB.
`,
Example: ` dbxcli put file.txt /destination/file.txt
dbxcli put file.txt /destination/file.txt --if-exists skip-same
dbxcli put -r ./project /backup/project
dbxcli put -w 1 -c 134217728 large.zip /backup/large.zip
printf 'hello' | dbxcli put - /hello.txt
Expand All @@ -1099,5 +1137,5 @@ func init() {
putCmd.Flags().IntP("workers", "w", 4, "Number of concurrent upload workers for chunked large-file uploads")
putCmd.Flags().Int64P("chunksize", "c", 1<<24, "Chunk size in bytes for chunked large-file uploads; must be a multiple of 4MiB and no more than 128MiB")
putCmd.Flags().BoolP("debug", "d", false, "Print debug timing")
putCmd.Flags().String("if-exists", putIfExistsOverwrite, "What to do when the destination file exists: overwrite, skip, autorename, or fail")
putCmd.Flags().String("if-exists", putIfExistsOverwrite, "What to do when the destination file exists: overwrite, skip, skip-same, autorename, or fail")
}
Loading
Loading