Skip to content

Commit 1e5cfc6

Browse files
Merge customizations for S3
1 parent fc26a2a commit 1e5cfc6

13 files changed

Lines changed: 672 additions & 502 deletions

gems/aws-sdk-core/spec/shared_spec_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
Thread.report_on_exception = current_value if current_value
5353
end
5454

55+
config.around(:each, :suppress_warning) do |example|
56+
original = $VERBOSE
57+
$VERBOSE = nil
58+
example.run
59+
ensure
60+
$VERBOSE = original
61+
end
62+
5563
if defined?(JRUBY_VERSION)
5664
config.around(:each, :jruby_flaky) do |example|
5765
attempt = 0

gems/aws-sdk-s3/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Multipart copies now support `tags_directive`, `annotations_directive`, and `metadata_directive` options for controlling which source properties are copied to the destination.
5+
6+
* Issue - Fix error when performing cross-region multipart copies with `copy_source_region`.
7+
48
1.225.1 (2026-06-10)
59
------------------
610

gems/aws-sdk-s3/lib/aws-sdk-s3/customizations/object.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ class Object
5454
# and any checksums will not change. This is especially useful if the
5555
# source object has parts with varied sizes.
5656
#
57+
# @option options [String] :tags_directive Only used when
58+
# `:multipart_copy` is `true`. When set to `'COPY'`, source object
59+
# tags are fetched and applied to the destination via PutObjectTagging.
60+
# When set to `'REPLACE'`, the provided `:tagging` value is parsed and
61+
# applied via PutObjectTagging. When not set, `:tagging` (if provided)
62+
# is passed to CreateMultipartUpload directly. Works with or without
63+
# `:content_length` — tags are fetched from source regardless of
64+
# whether HeadObject is skipped.
65+
#
66+
# @option options [String] :annotations_directive Only used when
67+
# `:multipart_copy` is `true`. When set to `'COPY'`, source object
68+
# annotations are fetched and applied to the destination after the
69+
# multipart upload completes. Works with or without `:content_length`.
70+
#
71+
# @option options [String] :metadata_directive Only used when
72+
# `:multipart_copy` is `true`. When set to `'REPLACE'`, source metadata
73+
# from HeadObject is not merged into CreateMultipartUpload — only
74+
# caller-supplied values (e.g. `:metadata`, `:content_type`) are used.
75+
# Has no effect when `:content_length` is provided since HeadObject
76+
# is already skipped.
77+
#
5778
# @example Basic object copy
5879
#
5980
# bucket = Aws::S3::Bucket.new('target-bucket')
Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
# frozen_string_literal: true
22

3-
require 'thread'
4-
53
module Aws
64
module S3
75
# @api private
86
class ObjectCopier
9-
107
# @param [S3::Object] object
118
def initialize(object, options = {})
129
@object = object
@@ -23,7 +20,7 @@ def copy_to(target, options = {})
2320

2421
private
2522

26-
def copy_object(source, target, options)
23+
def copy_object(source, target, options) # rubocop:disable Metrics/MethodLength
2724
target_bucket, target_key = copy_target(target)
2825
options[:bucket] = target_bucket
2926
options[:key] = target_key
@@ -38,7 +35,7 @@ def copy_object(source, target, options)
3835
end
3936
end
4037

41-
def copy_source(source)
38+
def copy_source(source) # rubocop:disable Metrics/MethodLength
4239
case source
4340
when String then source
4441
when Hash
@@ -50,54 +47,46 @@ def copy_source(source)
5047
when S3::ObjectVersion
5148
"#{source.bucket_name}/#{escape(source.object_key)}?versionId=#{source.id}"
5249
else
53-
msg = "expected source to be an Aws::S3::Object, Hash, or String"
54-
raise ArgumentError, msg
50+
raise ArgumentError, 'expected source to be an Aws::S3::Object, Hash, or String'
5551
end
5652
end
5753

5854
def copy_target(target)
5955
case target
60-
when String then target.match(/([^\/]+?)\/(.+)/)[1,2]
56+
when String then target.match(%r{([^/]+?)/(.+)})[1, 2]
6157
when Hash then target.values_at(:bucket, :key)
6258
when S3::Object then [target.bucket_name, target.key]
6359
else
64-
msg = "expected target to be an Aws::S3::Object, Hash, or String"
65-
raise ArgumentError, msg
60+
raise ArgumentError, 'expected target to be an Aws::S3::Object, Hash, or String'
6661
end
6762
end
6863

6964
def merge_options(source_or_target, options)
70-
if Hash === source_or_target
71-
source_or_target.inject(options.dup) do |opts, (key, value)|
72-
opts[key] = value unless [:bucket, :key, :version_id].include?(key)
73-
opts
65+
if source_or_target.is_a?(Hash)
66+
source_or_target.each_with_object(options.dup) do |(key, value), opts|
67+
opts[key] = value unless %i[bucket key version_id].include?(key)
7468
end
7569
else
7670
options.dup
7771
end
7872
end
7973

80-
def apply_source_client(source, options)
81-
82-
if source.respond_to?(:client)
83-
options[:copy_source_client] ||= source.client
84-
end
74+
def apply_source_client(source, options) # rubocop:disable Metrics/AbcSize
75+
options[:copy_source_client] ||= source.client if source.respond_to?(:client)
8576

8677
if options[:copy_source_region]
8778
config = @object.client.config
88-
config = config.each_pair.inject({}) { |h, (k,v)| h[k] = v; h }
79+
config = config.each_pair.with_object({}) { |(k, v), h| h[k] = v unless v.nil? }
8980
config[:region] = options.delete(:copy_source_region)
9081
options[:copy_source_client] ||= S3::Client.new(config)
9182
end
9283

9384
options[:copy_source_client] ||= @object.client
94-
9585
end
9686

9787
def escape(str)
9888
Seahorse::Util.uri_path_escape(str)
9989
end
100-
10190
end
10291
end
10392
end

0 commit comments

Comments
 (0)