Skip to content

Commit 2277801

Browse files
committed
Add support for versions using git revision suffixes
1 parent e37c650 commit 2277801

File tree

3 files changed

+348
-19
lines changed

3 files changed

+348
-19
lines changed

gradle/lib/dependabot/gradle/metadata_finder.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ class MetadataFinder < Dependabot::MetadataFinders::Base
2626

2727
sig { override.returns(T.nilable(Dependabot::Source)) }
2828
def look_up_source
29-
return nil if Distributions.distribution_requirements?(dependency.requirements)
29+
30+
if Distributions.distribution_requirements?(dependency.requirements)
31+
return Source.new(
32+
provider: "github",
33+
repo: "https://github.com/gradle/gradle"
34+
)
35+
end
3036

3137
tmp_source = look_up_source_in_pom(dependency_pom_file)
3238
return tmp_source if tmp_source

maven/lib/dependabot/maven/shared/shared_version_finder.rb

Lines changed: 157 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,39 +44,179 @@ class SharedVersionFinder < Dependabot::Package::PackageLatestVersionFinder
4444

4545
MAVEN_SNAPSHOT_QUALIFIER = /-SNAPSHOT$/i
4646

47+
# Minimum and maximum lengths for Git SHAs
48+
MIN_GIT_SHA_LENGTH = 7
49+
MAX_GIT_SHA_LENGTH = 40
50+
51+
# Regex for a valid Git SHA
52+
# - Only hexadecimal characters (0-9, a-f)
53+
# - Case-insensitive
54+
# - At least one letter a-f to avoid purely numeric strings
55+
GIT_COMMIT = T.let(
56+
/\A(?=[0-9a-f]{#{MIN_GIT_SHA_LENGTH},#{MAX_GIT_SHA_LENGTH}}\z)(?=.*[a-f])/i,
57+
Regexp
58+
)
59+
4760
sig { params(comparison_version: Dependabot::Version).returns(T::Boolean) }
4861
def matches_dependency_version_type?(comparison_version)
4962
return true unless dependency.version
5063

51-
current_version_string = dependency.version
52-
candidate_version_string = comparison_version.to_s
64+
current = dependency.version
65+
candidate = comparison_version.to_s
66+
67+
return true if pre_release_compatible?(current, candidate)
5368

54-
current_is_pre_release = current_version_string&.match?(MAVEN_PRE_RELEASE_QUALIFIERS)
55-
candidate_is_pre_release = candidate_version_string.match?(MAVEN_PRE_RELEASE_QUALIFIERS)
69+
return true if upgrade_to_stable?(current, candidate)
5670

57-
# Pre-releases are only compatible with other pre-releases
58-
# When this happens, the suffix does not need to match exactly
59-
# This allows transitions between 1.0.0-RC1 and 1.0.0-CR2, for example
60-
return true if current_is_pre_release && candidate_is_pre_release
71+
suffix_compatible?(current, candidate)
72+
end
6173

62-
current_is_snapshot = current_version_string&.match?(MAVEN_SNAPSHOT_QUALIFIER)
63-
# If the current version is a pre-release or a snapshot, allow upgrading to a stable release
64-
# This can help move from pre-release to the stable version that supersedes it,
65-
# but this should not happen vice versa as a stable release should not be downgraded to a pre-release
66-
return true if (current_is_pre_release || current_is_snapshot) && !candidate_is_pre_release
74+
private
6775

68-
current_suffix = extract_version_suffix(current_version_string)
69-
candidate_suffix = extract_version_suffix(candidate_version_string)
76+
# Determines whether two versions have compatible suffixes.
77+
#
78+
# Suffix compatibility is evaluated based on the type of suffix present:
79+
#
80+
# - Java runtime suffixes (JRE/JDK): Must have matching major versions and
81+
# compatible runtime types (JRE can upgrade to JDK, but not vice versa)
82+
#
83+
# - Git commit SHAs: Both versions must contain SHAs (the actual SHA values
84+
# don't need to match, as different commits of the same version are compatible)
85+
#
86+
# - Other suffixes: Must match exactly (e.g., platform identifiers, build tags)
87+
#
88+
# - No suffix: Both versions must have no suffix
89+
#
90+
# @example Java runtime compatibility
91+
# suffix_compatible?("1.0.0.jre8", "1.0.0.jre8") # => true (same JRE version)
92+
# suffix_compatible?("1.0.0.jre8", "1.0.0.jdk8") # => true (JRE → JDK upgrade)
93+
# suffix_compatible?("1.0.0.jdk8", "1.0.0.jre8") # => false (JDK → JRE downgrade)
94+
# suffix_compatible?("1.0.0.jre8", "1.0.0.jre11") # => false (version mismatch)
95+
#
96+
# @example Git SHA compatibility
97+
# suffix_compatible?("1.0-a1b2c3d", "1.0-e5f6789") # => true (both have SHAs)
98+
# suffix_compatible?("1.0-a1b2c3d", "1.0.0") # => false (SHA vs. no SHA)
99+
#
100+
# @example Exact suffix matching
101+
# suffix_compatible?("1.0.0-linux", "1.0.0-linux") # => true (exact match)
102+
# suffix_compatible?("1.0.0-linux", "1.0.0-win") # => false (different platform)
103+
# suffix_compatible?("1.0.0", "1.0.0") # => true (both have no suffix)
104+
# suffix_compatible?("1.0.0", "1.0.0-beta") # => false (suffix mismatch)
105+
sig { params(current: T.nilable(String), candidate: String).returns(T::Boolean) }
106+
def suffix_compatible?(current, candidate)
107+
current_suffix = extract_version_suffix(current)
108+
candidate_suffix = extract_version_suffix(candidate)
70109

71110
if jre_or_jdk?(current_suffix) && jre_or_jdk?(candidate_suffix)
72111
return compatible_java_runtime?(T.must(current_suffix), T.must(candidate_suffix))
73112
end
74113

114+
return true if contains_git_sha?(current_suffix) && contains_git_sha?(candidate_suffix)
115+
75116
# If both versions share the exact suffix or no suffix, they are compatible
76117
current_suffix == candidate_suffix
77118
end
78119

79-
private
120+
# Determines whether a given string is a valid Git commit SHA.
121+
#
122+
# Accepts both short SHAs (7-40 characters) and full SHAs (40 characters).
123+
# Handles versions with a leading 'v' prefix (e.g., "v018aa6b0d3").
124+
#
125+
# @example Valid Git SHAs
126+
# git_sha?("a1b2c3d") # => true (7-char short SHA)
127+
# git_sha?("a1b2c3d4e5f6") # => true (12-char SHA)
128+
# git_sha?("a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4") # => true (40-char full SHA)
129+
# git_sha?("v018aa6b0d3") # => true (with 'v' prefix)
130+
#
131+
# @example Invalid inputs
132+
# git_sha?("1.2.3") # => false (version number)
133+
# git_sha?("abc") # => false (too short, < 7 chars)
134+
# git_sha?("ghijklm") # => false (invalid hex characters)
135+
# git_sha?(nil) # => false (nil input)
136+
sig { params(version: T.nilable(String)).returns(T::Boolean) }
137+
def git_sha?(version)
138+
return false unless version
139+
140+
normalized = version.start_with?("v") ? version[1..-1] : version
141+
!!T.must(normalized).match?(GIT_COMMIT)
142+
end
143+
144+
# Determines whether a version string contains a Git commit SHA.
145+
#
146+
# This method checks if any part of a version string (when split by common
147+
# delimiters like '-', '.', or '_') is a valid Git SHA. It also handles
148+
# cases where delimiters within the SHA itself have been replaced with
149+
# underscores or other characters.
150+
151+
# @example Standard delimiter-separated SHAs
152+
# contains_git_sha?("1.0.0-a1b2c3d") # => true (SHA after hyphen)
153+
# contains_git_sha?("2.3.4.a1b2c3d4e5") # => true (SHA after dot)
154+
# contains_git_sha?("v1.2_a1b2c3d") # => true (SHA after underscore)
155+
#
156+
# @example Embedded SHAs with modified delimiters
157+
# contains_git_sha?("va_b_018a_a_6b_0d3") # => true (SHA with underscores replacing chars)
158+
# contains_git_sha?("1.0.a.1.b.2.c.3.d") # => true (SHA scattered across segments)
159+
#
160+
# @example Non-SHA versions
161+
# contains_git_sha?("1.2.3") # => false (regular version)
162+
# contains_git_sha?("abc") # => false (too short)
163+
# contains_git_sha?(nil) # => false (nil input)
164+
sig { params(version: T.nilable(String)).returns(T::Boolean) }
165+
def contains_git_sha?(version)
166+
return false unless version
167+
168+
# Check if any delimiter-separated part is a SHA
169+
version.split(/[-._]/).any? { |part| git_sha?(part) } ||
170+
# Check if removing delimiters reveals a SHA (e.g., "va_b_018a_a_6b_0d3")
171+
git_sha?(version.gsub(/[-._]/, ""))
172+
end
173+
174+
# Determines whether two versions are compatible based on pre-release status.
175+
#
176+
# Two versions are considered compatible if both are pre-release versions.
177+
# This allows upgrades between different pre-release qualifiers of the same
178+
# base version (e.g., RC1 → CR2, ALPHA → BETA)
179+
#
180+
# @example Compatible pre-release transitions
181+
# pre_release_compatible?("1.0.0-RC1", "1.0.0-RC2") # => true (same qualifier)
182+
# pre_release_compatible?("1.0.0-RC1", "1.0.0-CR2") # => true (different qualifier, same stage)
183+
# pre_release_compatible?("2.0.0-ALPHA", "2.0.0-BETA") # => true (progression)
184+
# pre_release_compatible?("1.5-M1", "1.5-MILESTONE2") # => true (equivalent qualifiers)
185+
sig { params(current: T.nilable(String), candidate: String).returns(T::Boolean) }
186+
def pre_release_compatible?(current, candidate)
187+
pre_release?(current) && pre_release?(candidate)
188+
end
189+
190+
sig { params(version: T.nilable(String)).returns(T::Boolean) }
191+
def pre_release?(version)
192+
version&.match?(MAVEN_PRE_RELEASE_QUALIFIERS) || false
193+
end
194+
195+
sig { params(version: T.nilable(String)).returns(T::Boolean) }
196+
def snapshot?(version)
197+
version&.match?(MAVEN_SNAPSHOT_QUALIFIER) || false
198+
end
199+
200+
# This method allows upgrades from unstable versions (pre-releases or snapshots)
201+
# to stable releases, which is a common and expected upgrade path.
202+
# However, it prevents downgrades from stable releases back to pre-releases,
203+
# as this would violate semantic versioning expectations.
204+
#
205+
# @example Valid upgrades to stable
206+
# upgrade_to_stable?("1.0.0-RC1", "1.0.0") # => true (pre-release → stable)
207+
# upgrade_to_stable?("2.0.0-SNAPSHOT", "2.0.0") # => true (snapshot → stable)
208+
# upgrade_to_stable?("1.5-BETA", "1.5") # => true (beta → stable)
209+
# upgrade_to_stable?("3.0.0-ALPHA2", "3.0.0-FINAL") # => true (pre-release → release qualifier)
210+
#
211+
# @example Invalid transitions (returns false)
212+
# upgrade_to_stable?("1.0.0", "1.0.1-RC1") # => false (stable → pre-release not allowed)
213+
# upgrade_to_stable?("2.0.0", "2.1.0") # => false (stable → stable, use other logic)
214+
# upgrade_to_stable?("1.0.0-RC1", "1.0.0-BETA") # => false (pre-release → pre-release)
215+
# upgrade_to_stable?(nil, "1.0.0") # => false (no current version)
216+
sig { params(current: T.nilable(String), candidate: String).returns(T::Boolean) }
217+
def upgrade_to_stable?(current, candidate)
218+
(pre_release?(current) || snapshot?(current)) && !pre_release?(candidate)
219+
end
80220

81221
# Determines whether two Java runtime suffixes are compatible.
82222
#
@@ -182,8 +322,7 @@ def extract_version_suffix(version_string)
182322
# e.g., "1.0.0-1" or "1.0.0_2" are not considered to have a meaningful suffix
183323
return nil if suffix.match?(/^_?\d+$/)
184324

185-
# Must contain a hyphen to be considered a valid suffix
186-
return suffix if suffix.include?("-") || suffix.include?("_")
325+
return suffix if suffix.include?("-") || suffix.include?("_") || git_sha?(suffix)
187326
end
188327

189328
nil

0 commit comments

Comments
 (0)