Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c133f51
ZJIT: Remove from `Invariants` on invalidation
XrXr May 7, 2026
203d127
[ruby/prism] Use less `visit_token` in the ripper translator
Earlopain May 7, 2026
d5e2779
Use rb_gc_get_ec in rb_gc_event_hook
peterzhu2118 May 6, 2026
72d032e
Update to ruby/mspec@dffcdf7
eregon May 7, 2026
9f47780
Update to ruby/spec@680fc69
eregon May 7, 2026
d4727cd
Ruby::Box fix stale cached values for exception-related global variab…
dak2 May 5, 2026
834a828
[DOC] Improve docs for ObjectSpace.count_objects_size
peterzhu2118 May 8, 2026
4658d6b
[ruby/rubygems] Fix bundle config gemfile unset behavior
afurm Apr 30, 2026
af7605d
Move conditional variable declarations
nobu May 8, 2026
216c49f
Use macros for specific purposes
nobu May 8, 2026
ee9e920
[ruby/rubygems] Switch bundle add to use optimistic versioning by def…
jeremyevans May 3, 2026
29229d8
[ruby/rubygems] Consolidate Override lookup with Override.find_for
hsbt May 7, 2026
c0a9fdc
[ruby/rubygems] Validate override version requirement at Gemfile eval…
hsbt May 7, 2026
13313f9
[ruby/rubygems] Assert that override directives never leak into the l…
hsbt May 7, 2026
e58a72e
[ruby/rubygems] Accept required_ruby_version and required_rubygems_ve…
hsbt May 7, 2026
acbdd87
[ruby/rubygems] Apply per-gem metadata overrides in Resolver
hsbt May 7, 2026
266e33c
[ruby/rubygems] Unlock direct deps too for metadata-field overrides
hsbt May 7, 2026
4fa0924
[ruby/rubygems] Cover required_ruby_version and required_rubygems_ver…
hsbt May 7, 2026
36b2ed2
[ruby/rubygems] Allow :all target for metadata-field overrides
hsbt May 7, 2026
54a0750
[ruby/rubygems] Fall back from per-gem lookup to an :all override
hsbt May 7, 2026
217d53b
[ruby/rubygems] Unlock every locked spec when an :all override is pre…
hsbt May 7, 2026
139e281
[ruby/rubygems] Cover :all target metadata-field override end-to-end
hsbt May 7, 2026
3e1a5fa
[ruby/rubygems] Honor metadata overrides in MatchMetadata
hsbt May 7, 2026
3a93bdc
[ruby/rubygems] Cover install-time compatibility for metadata overrides
hsbt May 7, 2026
f451ea9
[ruby/rubygems] Capture Gemfile source location for each override
hsbt May 7, 2026
501fecb
[ruby/rubygems] Show active overrides when resolution fails
hsbt May 7, 2026
0d5a5a6
[ruby/rubygems] Document Phase 2 override DSL extensions in gemfile.5
hsbt May 8, 2026
0beb804
[ruby/rubygems] Thread overrides explicitly instead of through a Bund…
hsbt May 7, 2026
dff3200
[ruby/rubygems] Stop blanket-unlocking the lockfile on :all overrides
hsbt May 7, 2026
7988b2c
[ruby/rubygems] Propagate overrides into Definition#resolve lockfile-…
hsbt May 7, 2026
5ef1dc0
[ruby/rubygems] Preserve overrides when SpecSet self-derives a valida…
hsbt May 7, 2026
6a50622
[ruby/rubygems] Avoid forcing a remote gemspec load when seeding Lazy…
hsbt May 8, 2026
5970638
[ruby/rubygems] Move overrides off SpecSet onto LazySpecification
hsbt May 8, 2026
95ce1c5
[ruby/rubygems] Honor LazySpec overrides in SpecSet#complete_platform
hsbt May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,9 @@ Init_eval(void)
rb_gvar_ractor_local("$@");
rb_gvar_ractor_local("$!");

rb_gvar_box_dynamic("$@");
rb_gvar_box_dynamic("$!");

rb_define_global_function("raise", f_raise, -1);
rb_define_global_function("fail", f_raise, -1);

Expand Down
23 changes: 13 additions & 10 deletions ext/objspace/objspace.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,23 +221,26 @@ type2sym(enum ruby_value_type i)

/*
* call-seq:
* ObjectSpace.count_objects_size([result_hash]) -> hash
* ObjectSpace.count_objects_size(result_hash = {}) -> result_hash
*
* Counts objects size (in bytes) for each type.
*
* Note that this information is incomplete. You need to deal with
* this information as only a *HINT*. Especially, total size of
* T_DATA may be wrong.
* Note that the returned size may not be accurate, so it should only
* be used as a hint. Specifically, the size for +T_DATA+ may be
* inaccurate because these are custom objects defined in Ruby and
* native extensions and so they may not accurately report their
* memory size.
*
* It returns a hash that looks like:
*
* It returns a hash as:
* {TOTAL: 1461154, T_CLASS: 158280, T_MODULE: 20672, T_STRING: 527249, ...}
*
* If the optional argument, result_hash, is given,
* it is overwritten and returned.
* This is intended to avoid probe effect.
* The contents of the returned hash are implementation specific and
* may be changed in future versions without notice.
*
* The contents of the returned hash is implementation defined.
* It may be changed in future.
* If the optional argument, +result_hash+, is given,
* it is overwritten and returned.
* This is intended to avoid the probe effect.
*
* This method is only expected to work with C Ruby.
*/
Expand Down
29 changes: 11 additions & 18 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -3913,10 +3913,6 @@ static VALUE
copy_home_path(VALUE result, const char *dir)
{
char *buf;
#if defined DOSISH || defined __CYGWIN__
char *p, *bend;
rb_encoding *enc;
#endif
long dirlen;
int encidx;

Expand All @@ -3925,11 +3921,11 @@ copy_home_path(VALUE result, const char *dir)
memcpy(buf = RSTRING_PTR(result), dir, dirlen);
encidx = rb_filesystem_encindex();
rb_enc_associate_index(result, encidx);
#if defined DOSISH || defined __CYGWIN__
enc = rb_enc_from_index(encidx);
#if defined FILE_ALT_SEPARATOR
rb_encoding *enc = rb_enc_from_index(encidx);
bool mb_enc = enc_mbclen_needed(enc);
for (bend = (p = buf) + dirlen; p < bend; Inc(p, bend, mb_enc, enc)) {
if (*p == '\\') {
for (char *p = buf, *bend = p + dirlen; p < bend; Inc(p, bend, mb_enc, enc)) {
if (*p == FILE_ALT_SEPARATOR) {
*p = '/';
}
}
Expand Down Expand Up @@ -4173,14 +4169,14 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na
BUFINIT();
p = e;
}
#if defined DOSISH || defined __CYGWIN__
#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
if (s < fend && isdirsep(*s)) {
/* specified full path, but not drive letter nor UNC */
/* we need to get the drive letter or UNC share name */
p = skipprefix(buf, p, mb_enc, enc);
}
else
#endif /* defined DOSISH || defined __CYGWIN__ */
#endif /* defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC */
p = chompdirsep(skiproot(buf, p), p, mb_enc, enc);
}
else {
Expand Down Expand Up @@ -4237,8 +4233,8 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na
#endif /* USE_NTFS */
break;
case '/':
#if defined DOSISH || defined __CYGWIN__
case '\\':
#if defined FILE_ALT_SEPARATOR
case FILE_ALT_SEPARATOR:
#endif
b = ++s;
break;
Expand All @@ -4262,8 +4258,8 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na
#endif /* USE_NTFS */
break;
case '/':
#if defined DOSISH || defined __CYGWIN__
case '\\':
#if defined FILE_ALT_SEPARATOR
case FILE_ALT_SEPARATOR:
#endif
if (s > b) {
WITH_ROOTDIFF(BUFCOPY(b, s-b));
Expand Down Expand Up @@ -4985,9 +4981,6 @@ static inline const char *
enc_find_basename(const char *name, long *baselen, long *alllen, bool mb_enc, rb_encoding *enc)
{
const char *p, *q, *e, *end;
#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
const char *root;
#endif
long f = 0, n = -1;

long len = (alllen ? (size_t)*alllen : strlen(name));
Expand All @@ -4999,7 +4992,7 @@ enc_find_basename(const char *name, long *baselen, long *alllen, bool mb_enc, rb
end = name + len;
name = skipprefix(name, end, mb_enc, enc);
#if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC
root = name;
const char *root = name;
#endif

while (name < end && isdirsep(*name)) {
Expand Down
2 changes: 1 addition & 1 deletion gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ rb_gc_event_hook(VALUE obj, rb_event_flag_t event)
{
if (LIKELY(!rb_gc_event_hook_required_p(event))) return;

rb_execution_context_t *ec = GET_EC();
rb_execution_context_t *ec = rb_gc_get_ec();
if (!ec->cfp) return;

EXEC_EVENT_HOOK(ec, event, ec->cfp->self, 0, 0, 0, obj);
Expand Down
2 changes: 2 additions & 0 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -6415,6 +6415,8 @@ garbage_collect(rb_objspace_t *objspace, unsigned int reason)
static int
gc_start(rb_objspace_t *objspace, unsigned int reason)
{
rb_gc_initialize_vm_context(&objspace->vm_context);

unsigned int do_full_mark = !!(reason & GPR_FLAG_FULL_MARK);

if (!rb_darray_size(objspace->heap_pages.sorted)) return TRUE; /* heap is not ready */
Expand Down
23 changes: 14 additions & 9 deletions lib/bundler/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ def initialize(*args)

current_cmd = args.last[:current_command].name

Bundler.configure_custom_gemfile(options[:gemfile])

# lock --lockfile works differently than install --lockfile
unless current_cmd == "lock"
custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile]
if custom_lockfile && !custom_lockfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile)
reset_settings = true
# `bundle config` manages stored settings, so avoid promoting settings
# like `gemfile` or `lockfile` to environment variables before it runs.
unless current_cmd == "config"
Bundler.configure_custom_gemfile(options[:gemfile])

# lock --lockfile works differently than install --lockfile
unless current_cmd == "lock"
custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile]
if custom_lockfile && !custom_lockfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile)
reset_settings = true
end
end
end

Expand Down Expand Up @@ -399,7 +403,8 @@ def binstubs(*gems)
method_option "glob", type: :string, banner: "The location of a dependency's .gemspec, expanded within Ruby (single quotes recommended)"
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it"
method_option "optimistic", type: :boolean, banner: "Adds optimistic declaration of version to gem"
method_option "optimistic", type: :boolean, banner: "Ignored (now default behavior)"
method_option "pessimistic", type: :boolean, banner: "Adds pessimistic declaration of version to gem"
method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem"
def add(*gems)
require_relative "cli/add"
Expand Down
4 changes: 2 additions & 2 deletions lib/bundler/cli/add.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def inject_dependencies

Injector.inject(dependencies,
conservative_versioning: options[:version].nil?, # Perform conservative versioning only when version is not specified
optimistic: options[:optimistic],
pessimistic: options[:pessimistic],
strict: options[:strict])
end

Expand All @@ -46,7 +46,7 @@ def validate_options!

raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"]

raise InvalidOption, "You cannot specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic]
raise InvalidOption, "You cannot specify `--strict` and `--pessimistic` at the same time." if options[:strict] && options[:pessimistic]

# raise error when no gems are specified
raise InvalidOption, "Please specify gems to add." if gems.empty?
Expand Down
12 changes: 10 additions & 2 deletions lib/bundler/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version
@locked_deps = @locked_gems.dependencies
Override.attach(@locked_gems.specs, @overrides)
@originally_locked_specs = SpecSet.new(@locked_gems.specs)
@originally_locked_sources = @locked_gems.sources
@locked_checksums = @locked_gems.checksums
Expand Down Expand Up @@ -644,7 +645,7 @@ def apply_overrides_to(deps)
end

def apply_override_to(dep)
override = @overrides.find {|o| o.target == dep.name && o.field == :version }
override = Override.find_for(@overrides, dep.name, :version)
return dep unless override
new_dep = dep.dup
new_dep.instance_variable_set(:@requirement, override.apply_to(dep.requirement))
Expand Down Expand Up @@ -1061,12 +1062,19 @@ def converge_dependencies

def converge_overrides_outside_dependencies
@overrides.each do |override|
# :all overrides are intentionally not pre-unlocked. They take effect on
# fresh resolution (no lockfile) or when the user runs `bundle update`.
# Forcing a full re-resolve from a single :all directive would surprise
# users with unrelated dependency churn.
next unless override.target.is_a?(String)

name = override.target
next if @changed_dependencies.include?(name)
next if @dependencies.any? {|d| d.name == name }
next if @originally_locked_specs[name].empty?
# version: overrides on direct deps are detected in the per-dep
# converge_dependencies loop via apply_override_to + matches_spec?.
# Other fields are not visible there, so they always reach here.
next if override.field == :version && @dependencies.any? {|d| d.name == name }

@gems_to_unlock << name
@changed_dependencies << name
Expand Down
14 changes: 10 additions & 4 deletions lib/bundler/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def github(repo, options = {})
with_source(git_source) { yield }
end

SUPPORTED_OVERRIDE_FIELDS = [:version].freeze
SUPPORTED_OVERRIDE_FIELDS = [:version, :required_ruby_version, :required_rubygems_version].freeze
SUPPORTED_OVERRIDE_SYMBOL_OPERATIONS = [:ignore_upper].freeze

def override(target, **operations)
Expand All @@ -201,8 +201,9 @@ def override(target, **operations)
validate_override_uniqueness!(target, field)
end

source_location = caller_locations(1, 1)&.first
operations.each do |field, operation|
@overrides << Override.new(target, field, operation)
@overrides << Override.new(target, field, operation, source_location: source_location)
end
end

Expand Down Expand Up @@ -274,19 +275,24 @@ def validate_override_target!(target)

def validate_override_field!(field)
return if SUPPORTED_OVERRIDE_FIELDS.include?(field)
raise ArgumentError, "unsupported override field `#{field}:`; only `version:` is currently supported"
supported = SUPPORTED_OVERRIDE_FIELDS.map {|f| "`#{f}:`" }.join(", ")
raise ArgumentError, "unsupported override field `#{field}:`; supported fields: #{supported}"
end

def validate_override_operation!(operation)
case operation
when String, nil
when String
Gem::Requirement.new(operation)
when nil
# ok
when Symbol
return if SUPPORTED_OVERRIDE_SYMBOL_OPERATIONS.include?(operation)
raise ArgumentError, "unsupported override operation: #{operation.inspect}"
else
raise ArgumentError, "override operation must be a String, Symbol, or nil, got #{operation.inspect}"
end
rescue Gem::Requirement::BadRequirementError => e
raise ArgumentError, "invalid override version requirement #{operation.inspect}: #{e.message}"
end

def validate_override_uniqueness!(target, field)
Expand Down
6 changes: 3 additions & 3 deletions lib/bundler/injector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ def conservative_version(spec)
def version_prefix
if @options[:strict]
"= "
elsif @options[:optimistic]
">= "
else
elsif @options[:pessimistic]
"~> "
else
">= "
end
end

Expand Down
5 changes: 3 additions & 2 deletions lib/bundler/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,13 @@ def load_plugins
end

def ensure_specs_are_compatible!
overrides = @definition.overrides
@definition.specs.each do |spec|
unless spec.matches_current_ruby?
unless spec.matches_current_ruby_with_overrides?(overrides)
raise InstallError, "#{spec.full_name} requires ruby version #{spec.required_ruby_version}, " \
"which is incompatible with the current version, #{Gem.ruby_version}"
end
unless spec.matches_current_rubygems?
unless spec.matches_current_rubygems_with_overrides?(overrides)
raise InstallError, "#{spec.full_name} requires rubygems version #{spec.required_rubygems_version}, " \
"which is incompatible with the current version, #{Gem.rubygems_version}"
end
Expand Down
6 changes: 4 additions & 2 deletions lib/bundler/lazy_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class LazySpecification
include ForcePlatform

attr_reader :name, :version, :platform, :materialization
attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version
attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version, :overrides

#
# For backwards compatibility with existing lockfiles, if the most specific
Expand All @@ -30,6 +30,7 @@ def self.from_spec(s)
lazy_spec.dependencies = s.runtime_dependencies
lazy_spec.required_ruby_version = s.required_ruby_version
lazy_spec.required_rubygems_version = s.required_rubygems_version
lazy_spec.overrides = s.overrides if s.is_a?(LazySpecification)
lazy_spec
end

Expand Down Expand Up @@ -234,8 +235,9 @@ def materialize(query)
# about the mismatch higher up the stack, right before trying to install the
# bad gem.
def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bundle?)
override_list = overrides || []
search = candidates.reverse.find do |spec|
spec.is_a?(StubSpecification) || spec.matches_current_metadata?
spec.is_a?(StubSpecification) || spec.matches_current_metadata_with_overrides?(override_list)
end
if search.nil? && fallback_to_non_installable
search = candidates.last
Expand Down
7 changes: 5 additions & 2 deletions lib/bundler/man/bundle-add.1
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ Do not print progress information to the standard output\.
Adds the gem to the Gemfile but does not install it\.
.TP
\fB\-\-optimistic\fR
Adds optimistic declaration of version\.
Ignored (now default behavior)
.TP
\fB\-\-pessimistic\fR
Adds pessimistic declaration of version\.
.TP
\fB\-\-strict\fR
Adds strict declaration of version\.
Expand All @@ -62,7 +65,7 @@ You can add the \fBrails\fR gem with version greater than 1\.1 (not including 1\
.IP "3." 4
You can use the \fBhttps://gems\.example\.com\fR custom source and assign the gem to a group\.
.IP
\fBbundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR
\fBbundle add rails \-\-version ">= 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR
.IP "4." 4
The following adds the \fBgem\fR entry to the Gemfile without installing the gem\. You can install gems later via \fBbundle install\fR\.
.IP
Expand Down
7 changes: 5 additions & 2 deletions lib/bundler/man/bundle-add.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
Adds the gem to the Gemfile but does not install it.

* `--optimistic`:
Adds optimistic declaration of version.
Ignored (now default behavior)

* `--pessimistic`:
Adds pessimistic declaration of version.

* `--strict`:
Adds strict declaration of version.
Expand All @@ -70,7 +73,7 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
3. You can use the `https://gems.example.com` custom source and assign the gem
to a group.

`bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development"`
`bundle add rails --version ">= 5.0.0" --source "https://gems.example.com" --group "development"`

4. The following adds the `gem` entry to the Gemfile without installing the
gem. You can install gems later via `bundle install`.
Expand Down
Loading