Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4f53e5d
ZJIT: Skip failing `test_specialize_inlined_megamorphic_receiver`
XrXr Sep 1, 2026
8d80fd4
Mark rb_zjit_throw as NORETURN
k0kubun Sep 1, 2026
0ea980a
ZJIT: Fix test for new distribution size (#18593)
tekknolagi Sep 1, 2026
b59ea8e
ZJIT: Reserve a label reference's bytes by writing them (#18519)
k0kubun Sep 1, 2026
18ef46d
ZJIT: Drop `PartialEq` from `FrameState`
XrXr Sep 1, 2026
837df27
string.c: remove impossible condition
byroot Sep 1, 2026
c92faa5
[DOC] Doc for File.identical?
BurdetteLamar Sep 1, 2026
ae0d477
[ruby/time] Fix wrong result in Time.rfc2822 documentation
thyogo Sep 2, 2026
2742123
[ruby/rubygems] Normalize legacy Latin-1 bytes for pre 1.8 ruby gems
skatkov Aug 31, 2026
900dbd0
[ruby/rubygems] simplify code
skatkov Aug 31, 2026
03a9557
[ruby/rubygems] Preserve non-UTF-8 legacy gem metadata
skatkov Sep 1, 2026
e746974
[ruby/rubygems] return a test that executes Psych.safe_load
skatkov Sep 1, 2026
d3c5230
[ruby/rubygems] Let the tests control GITHUB_ACTIONS
hsbt Aug 31, 2026
7a8392d
[ruby/rubygems] Only auto-attest when GITHUB_ACTIONS is "true"
hsbt Aug 31, 2026
84724ea
[ruby/rubygems] Prevent silent attestation downgrade on push
hsbt Aug 31, 2026
29baff2
[ruby/rubygems] Follow the remote's default branch when the tracked o…
hsbt Sep 2, 2026
c3436df
[ruby/rubygems] Restrict the shared global gem cache to remote sources
hsbt Aug 28, 2026
8088aee
[ruby/rubygems] Copy local gems into the cache dir atomically
hsbt Aug 28, 2026
0fd8326
[ruby/rubygems] Gate the global cache specs on the version that ships…
hsbt Aug 28, 2026
e04de71
[ruby/rubygems] Remove Gem::BasicSpecification#datadir
hsbt Sep 2, 2026
1e391d9
Bump the github-actions group across 1 directory with 2 updates
dependabot[bot] Sep 2, 2026
d311b34
parse.y: clear errno before parsing float literals
ydah Aug 29, 2026
bd2a734
[ruby/rubygems] Avoid duplicate relative path prefix in bundle exec
samuel-williams-shopify Jun 4, 2026
90e8610
JIT: Reduce jit_entry_calls/jit_exception_calls to 32 bits
peterzhu2118 Sep 2, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/check_sast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
persist-credentials: false

- name: Run zizmor
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3
continue-on-error: true

analyze:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
rustup install ${{ matrix.rust_version }} --profile minimal
rustup default ${{ matrix.rust_version }}

- uses: taiki-e/install-action@742a3317eac7bd62f91cd888b4eead5e784ba833 # v2.87.1
- uses: taiki-e/install-action@1ed6d7be6168f6c9046541087ff549b6bc581fdf # v2.87.2
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
ruby-version: '3.1'
bundler: none

- uses: taiki-e/install-action@742a3317eac7bd62f91cd888b4eead5e784ba833 # v2.87.1
- uses: taiki-e/install-action@1ed6d7be6168f6c9046541087ff549b6bc581fdf # v2.87.2
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
32 changes: 20 additions & 12 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -2482,21 +2482,29 @@ rb_file_sticky_p(VALUE obj, VALUE fname)

/*
* call-seq:
* File.identical?(file_1, file_2) -> true or false
* File.identical?(object_0, object_1) -> true or false
*
* Returns <code>true</code> if the named files are identical.
* Returns whether the given objects represent filesystem entries that are identical;
* each object may be a string path or an IO object:
*
* _file_1_ and _file_2_ can be an IO object.
* # Paths.
* File.identical?('README.md', 'README.md') # => true # Same path.
* File.identical?('README.md', './README.md') # => true # Same entry.
* File.identical?('.', '.') # => true # Directory.
* File.identical?('README.md', 'LEGAL') # => false
* File.identical?('README.md', 'nosuch') # => false # Non-existent entry.
* # Links and File object.
* File.link('README.md', 'link') # Symbolic link.
* File.symlink('README.md', 'symlink') # Hard link.
* file = File.open('README.md', 'r') # File object.
* File.identical?('README.md', 'link') # => true
* File.identical?('README.md', 'symlink') # => true
* File.identical?('README.md', file) # => true
* # Clean up.
* File.unlink('link')
* File.unlink('symlink')
* file.close
*
* open("a", "w") {}
* p File.identical?("a", "a") #=> true
* p File.identical?("a", "./a") #=> true
* File.link("a", "b")
* p File.identical?("a", "b") #=> true
* File.symlink("a", "c")
* p File.identical?("a", "c") #=> true
* open("d", "w") {}
* p File.identical?("a", "d") #=> false
*/

static VALUE
Expand Down
16 changes: 15 additions & 1 deletion lib/bundler/cli/exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def run
bin_path.delete_suffix!(".bat") if Gem.win_platform?
kernel_load(bin_path, *args)
else
bin_path = "./" + bin_path unless File.absolute_path?(bin_path)
bin_path = explicit_path(bin_path)
kernel_exec(bin_path, *args)
end
else
Expand Down Expand Up @@ -71,6 +71,20 @@ def process_title(file, args)
"#{file} #{args.join(" ")}".strip
end

def explicit_path(path)
if File.absolute_path?(path) || explicit_relative_path?(path)
path
else
".#{File::SEPARATOR}#{path}"
end
end

def explicit_relative_path?(path)
[File::SEPARATOR, File::ALT_SEPARATOR].compact.any? do |separator|
path.start_with?(".#{separator}", "..#{separator}")
end
end

def directly_loadable?(file)
if Gem.win_platform?
script_wrapper?(file)
Expand Down
67 changes: 64 additions & 3 deletions lib/bundler/source/git/git_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,13 @@ def git_remote_fetch(args)
return out if status.success?

if err.include?("couldn't find remote ref") || err.include?("not our ref")
raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref, credential_filtered_uri)
default_branch = renamed_remote_default_branch if tracking_remote_default_branch?
if default_branch
out = follow_remote_default_branch(args, default_branch)
return out if out
end

raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref || current_branch, credential_filtered_uri)
else
if shallow?
args -= depth_args
Expand Down Expand Up @@ -298,6 +304,61 @@ def not_pinned?
branch_option || ref.nil?
end

def tracking_remote_default_branch?
explicit_ref.nil? && commit.nil?
end

# Returns nil on any failure, leaving HEAD untouched so the caller reports
# the original fetch failure. Runs inside the retry block, so it must not
# touch the caller's command locals.
def follow_remote_default_branch(args, default_branch)
reference = "refs/heads/#{default_branch}"
command = fetch_command(args, "#{reference}:#{reference}")
check_allowed(command)

out, err, status = capture(command, path)
unless status.success?
Bundler.ui.debug "Could not fetch #{reference} from #{credential_filtered_uri}: #{err}"
return
end

previous_branch = current_branch
begin
git "symbolic-ref", "HEAD", reference, dir: path
rescue GitError => e
Bundler.ui.debug "Could not repoint the cached clone at #{reference}: #{e.message}"
return
end
@current_branch = nil
Bundler.ui.warn "#{credential_filtered_uri} no longer has #{previous_branch}, " \
"now following its default branch #{default_branch}"
out
end

# The cached clone's HEAD branch is gone from the remote, so the remote's
# own idea of its default branch is the only thing left to follow.
def renamed_remote_default_branch
default_branch = remote_default_branch
return if default_branch.nil? || default_branch == current_branch

default_branch
end

def remote_default_branch
command = ["ls-remote", "--symref", "--", configured_uri, "HEAD"]
check_allowed(command)

out, err, status = capture(command, path)
unless status.success?
Bundler.ui.debug "Could not ask #{credential_filtered_uri} for its default branch: #{err}"
return
end

# A remote is free to advertise a ref name that is not valid UTF-8, and
# matching that as text raises out of the GitError family.
out.b[%r{^ref:\s+refs/heads/(.+?)\s+HEAD}, 1]
end

def pinned_to_full_sha?
full_sha_revision?(ref)
end
Expand Down Expand Up @@ -465,8 +526,8 @@ def extra_clone_args
args
end

def fetch_command(args)
["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, refspec].compact
def fetch_command(args, spec = refspec)
["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, spec].compact
end

def clone_command(args)
Expand Down
11 changes: 0 additions & 11 deletions lib/rubygems/basic_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,17 +182,6 @@ def full_require_paths
end
end

##
# The path to the data directory for this gem.

def datadir
# TODO: drop the extra ", gem_name" which is uselessly redundant
File.expand_path(File.join(gems_dir, full_name, "data", name))
end

extend Gem::Deprecate
rubygems_deprecate :datadir, :none, "4.1"

##
# Full path of the target library file.
# If the file is not in this gem, return nil.
Expand Down
84 changes: 54 additions & 30 deletions lib/rubygems/commands/push_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def initialize
end

add_option("--attestation FILE",
"Push with sigstore attestations") do |value, options|
"Push with sigstore attestations",
" (FILE must be a JSON sigstore bundle)") do |value, options|
options[:attestations] << value
end

Expand Down Expand Up @@ -96,7 +97,7 @@ def send_gem(name)
def send_push_request(name, args)
# Always honor explicit --attestation option
# Auto-attestation is only supported on rubygems.org with GitHub Actions (not JRuby)
if options[:attestations].any? || (RUBY_ENGINE != "jruby" && attestation_supported_host? && ENV["GITHUB_ACTIONS"])
if options[:attestations].any? || (RUBY_ENGINE != "jruby" && attestation_supported_host? && ENV["GITHUB_ACTIONS"] == "true")
send_push_request_with_attestation(name, args)
else
send_push_request_without_attestation(name, args)
Expand All @@ -117,14 +118,23 @@ def send_push_request_without_attestation(name, args)
def send_push_request_with_attestation(name, args)
attestations = if options[:attestations].any?
options[:attestations].map do |attestation|
Gem.read_binary(attestation)
load_attestation(attestation)
end
else
bundle_path = attest!(name)
# Only the opportunistic signing step falls back. The request below stays
# outside this rescue because once the server may have seen the attested
# push, a network error must not trigger an unattested retry.
begin
[Gem.read_binary(bundle_path)]
ensure
File.unlink(bundle_path) if bundle_path && File.exist?(bundle_path)
[attest!(name)]
rescue StandardError => e
message = "Failed to create an attestation, pushing without one.\n"
message += if Gem.configuration.really_verbose
e.full_message
else
e.message
end
alert_warning message
return send_push_request_without_attestation(name, args)
end
end
bundles = "[" + attestations.join(",") + "]"
Expand All @@ -136,38 +146,52 @@ def send_push_request_with_attestation(name, args)
], "multipart/form-data")
request.add_field "Authorization", api_key
end
rescue StandardError => e
message = "Failed to push with attestation, retrying without attestation.\n"
message += if Gem.configuration.really_verbose
e.full_message
else
e.message
end

def load_attestation(file)
data = begin
Gem.read_binary(file)
rescue SystemCallError, IOError, ArgumentError => e
raise Gem::Exception, "Failed to read attestation #{file}: #{e.message}"
end
validate_attestation_json(data, file)
end

def validate_attestation_json(data, source)
require "json"

parsed = begin
JSON.parse(data)
rescue JSON::ParserError => e
raise Gem::Exception, "Attestation #{source} is not valid JSON: #{e.message}"
end
alert_warning message
send_push_request_without_attestation(name, args)
raise Gem::Exception, "Attestation #{source} is not a JSON object" unless parsed.is_a?(Hash)
data
end

def attest!(name)
require "open3"
require "shellwords"
require "tempfile"

tempfile = Tempfile.new([File.basename(name, ".*"), ".sigstore.json"])
bundle = tempfile.path
tempfile.close(false)

env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h
# Gem.ruby is quoted if it contains whitespace, so split it into argv
# elements to keep the quotes out of the spawned command.
out, st = Open3.capture2e(
env,
*Shellwords.split(Gem.ruby), "-S", "gem", "exec", "--conservative",
"sigstore-cli", "sign", name, "--bundle", bundle,
unsetenv_others: true
)
raise Gem::Exception, "Failed to sign gem:\n\n#{out}" unless st.success?

bundle

Tempfile.create([File.basename(name, ".*"), ".sigstore.json"]) do |tempfile|
tempfile.close
bundle = tempfile.path

# Gem.ruby is quoted if it contains whitespace, so split it into argv
# elements to keep the quotes out of the spawned command.
out, st = Open3.capture2e(
env,
*Shellwords.split(Gem.ruby), "-S", "gem", "exec", "--conservative",
"sigstore-cli", "sign", name, "--bundle", bundle,
unsetenv_others: true
)
raise Gem::Exception, "Failed to sign gem:\n\n#{out}" unless st.success?

validate_attestation_json(Gem.read_binary(bundle), "generated by sigstore-cli")
end
end

def get_hosts_for(name)
Expand Down
7 changes: 6 additions & 1 deletion lib/rubygems/config_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,12 @@ class Gem::ConfigFile

##
# Use a global cache for .gem files shared across all Ruby installations.
# When enabled, gems are cached to ~/.cache/gem/gems (or XDG_CACHE_HOME/gem/gems).
# When enabled, gems fetched from a remote source are cached to
# ~/.cache/gem/gems (or XDG_CACHE_HOME/gem/gems). Gems installed from a
# local path are not, since a cached copy is later reused without being
# verified again. <tt>gem fetch</tt> still writes to the working directory,
# and an unwritable cache directory falls back to the cache of the
# installation.

attr_accessor :global_gem_cache

Expand Down
Loading