dependabot-common 0.385.0 → 0.386.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58033d13e6a5c0bee14915505c98d752b8a6f2e26ffb96a4ba6d95ca66057bc3
4
- data.tar.gz: 0a6c8eebbffb115434da53fbd171236690f1594a617f16fb4e29b1addfc81308
3
+ metadata.gz: 4e4c26ec485eed8ebb4e35d9edfa4ab996658f450708439941ff8ca253ce0457
4
+ data.tar.gz: '045951912dda5a814efcf305b11ae28e5c6cd97a1e7f72f1c610650d2f9031cb'
5
5
  SHA512:
6
- metadata.gz: 5defea48ad000e82511496f0cb413945bf6ba2a5070361af5f1fa5e6411a11679a24de9e852c478f48fba3299d3678865e2faf97158c310ee429f6ac368fee5c
7
- data.tar.gz: fcf737b08310353b51bc3b32a53f24546e8bf30aa58a9a0b9e91e025921728bdcba378da8b68f9dc592b1bec349ff487a5d21a2683ab1ad76fa1d8b748ca066b
6
+ metadata.gz: bb49cbf946335c722ca445b0c314909eb0cf1790b241b68461881873cf254afddb7b7b10a168879ded348af64bdb30f5093882c1221b8f3c7e3ca69bbf6bd59b
7
+ data.tar.gz: ed0b8a8a019adeed81707ee2aae27e1e7e50d8f4c0da2d66cfca4a952df713173706d091062d02cb5a5aa4c18b819747b76ec5f114595fdbb804c4bef9df208b
@@ -3,6 +3,8 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
 
6
+ require "dependabot/dependency_file"
7
+
6
8
  module Dependabot
7
9
  module DependencyGraphers
8
10
  # This is a small value class that specifies the information we expect to be returned for each
@@ -20,6 +22,29 @@ module Dependabot
20
22
  const :dependencies, T::Array[String]
21
23
  end
22
24
 
25
+ # A manifest group is a subset of a directory's dependency files that stands on its own as a valid
26
+ # parser input, plus the single file that the group's dependencies should be attributed to.
27
+ #
28
+ # - `primary` is the attribution target (the file that owns the group's dependencies in the snapshot).
29
+ # - `files` is everything the parser needs to resolve the group, including the primary and any sibling
30
+ # files pulled in only to satisfy cross-references.
31
+ #
32
+ # Most ecosystems have exactly one group per directory (a manifest + optional lockfile).
33
+ #
34
+ # Ecosystems where multiple independent manifests routinely share a directory override `manifest_groups`
35
+ # to return one group per independent manifest using ecosystem-specific rules (e.g. Python layered requirements)
36
+ class ManifestGroup < T::ImmutableStruct
37
+ const :primary, Dependabot::DependencyFile
38
+ const :files, T::Array[Dependabot::DependencyFile]
39
+ end
40
+
41
+ # The resolved output for a single manifest group: the file to attribute to, and the dependencies
42
+ # resolved from that group's files.
43
+ class ManifestGroupSnapshot < T::ImmutableStruct
44
+ const :manifest_file, Dependabot::DependencyFile
45
+ const :resolved_dependencies, T::Hash[String, ResolvedDependency]
46
+ end
47
+
23
48
  class Base
24
49
  extend T::Sig
25
50
  extend T::Helpers
@@ -78,8 +103,86 @@ module Dependabot
78
103
  end
79
104
  end
80
105
 
106
+ # Partitions the parsed directory into one or more manifest groups.
107
+ #
108
+ # The default is a single group spanning the whole directory, attributed to `relevant_dependency_file`.
109
+ #
110
+ # Ecosystems where multiple independent manifests share a directory should override this to return
111
+ # one group per manifest.
112
+ sig { overridable.returns(T::Array[ManifestGroup]) }
113
+ def manifest_groups
114
+ [ManifestGroup.new(primary: relevant_dependency_file, files: dependency_files)]
115
+ end
116
+
117
+ # Resolves each manifest group into a snapshot.
118
+ #
119
+ # When there is a single group, the common case for most ecosystems, we attribute all resolved dependencies
120
+ # to the group's primary without further work required.
121
+ #
122
+ # When there are multiple groups, we instantiate a new scoped grapher for the group's files and resolve each
123
+ # independently.
124
+ sig { returns(T::Array[ManifestGroupSnapshot]) }
125
+ def manifest_group_snapshots
126
+ @manifest_group_snapshots ||= T.let(
127
+ build_manifest_group_snapshots,
128
+ T.nilable(T::Array[ManifestGroupSnapshot])
129
+ )
130
+ end
131
+
81
132
  private
82
133
 
134
+ sig { returns(T::Array[ManifestGroupSnapshot]) }
135
+ def build_manifest_group_snapshots
136
+ groups = manifest_groups
137
+
138
+ if groups.one?
139
+ group = T.must(groups.first)
140
+ return [ManifestGroupSnapshot.new(
141
+ manifest_file: group.primary,
142
+ resolved_dependencies: resolved_dependencies
143
+ )]
144
+ end
145
+
146
+ groups.map do |group|
147
+ scoped = scoped_grapher(group.files)
148
+ snapshot = ManifestGroupSnapshot.new(
149
+ manifest_file: group.primary,
150
+ resolved_dependencies: scoped.resolved_dependencies
151
+ )
152
+ # Ensure we propagate any error flags from the scoped grapher.
153
+ absorb_error_state(scoped)
154
+ snapshot
155
+ end
156
+ end
157
+
158
+ # Propagates a scoped grapher's subdependency error state onto this grapher so callers can inspect the
159
+ # aggregate result of resolving every group.
160
+ sig { params(scoped: Dependabot::DependencyGraphers::Base).void }
161
+ def absorb_error_state(scoped)
162
+ return unless scoped.errored_fetching_subdependencies
163
+
164
+ errored_fetching_subdependencies!
165
+ # We keep the last subdependency error we see - the full error dialogue will be present in the logs,
166
+ # this just ensures job summary dialogues have something to show.
167
+ @subdependency_error = scoped.subdependency_error unless scoped.subdependency_error.nil?
168
+ end
169
+
170
+ # Builds a grapher of the same class scoped to a subset of the directory's files, reusing the current
171
+ # file parser's configuration. Used to resolve a single manifest group in isolation.
172
+ sig { params(files: T::Array[Dependabot::DependencyFile]).returns(Dependabot::DependencyGraphers::Base) }
173
+ def scoped_grapher(files)
174
+ scoped_parser = file_parser.class.new(
175
+ dependency_files: files,
176
+ source: file_parser.source,
177
+ repo_contents_path: file_parser.repo_contents_path,
178
+ credentials: file_parser.credentials,
179
+ reject_external_code: file_parser.reject_external_code?,
180
+ options: file_parser.options
181
+ )
182
+
183
+ self.class.new(file_parser: scoped_parser)
184
+ end
185
+
83
186
  sig { returns(Dependabot::FileParsers::Base) }
84
187
  attr_reader :file_parser
85
188
 
@@ -0,0 +1,138 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/errors"
7
+ require "dependabot/pull_request_creator"
8
+
9
+ module Dependabot
10
+ class PullRequestCreator
11
+ class BranchNameTemplate
12
+ extend T::Sig
13
+
14
+ class Error < Dependabot::DependabotError; end
15
+
16
+ SOLO_PLACEHOLDERS = T.let(
17
+ %w(prefix package_manager directory target_branch dependency version name).freeze,
18
+ T::Array[String]
19
+ )
20
+ GROUP_PLACEHOLDERS = T.let(
21
+ %w(prefix package_manager directory target_branch group_name name).freeze,
22
+ T::Array[String]
23
+ )
24
+ MULTI_ECO_PLACEHOLDERS = T.let(
25
+ %w(prefix target_branch group_name name).freeze,
26
+ T::Array[String]
27
+ )
28
+
29
+ # ---------------------------------------------------------------
30
+ # 1. validate_template(template, strategy:)
31
+ # Ensures every {token} is allowed for the given strategy,
32
+ # brackets are well-formed, and no forbidden placeholders used.
33
+ # ---------------------------------------------------------------
34
+ sig { params(template: String, strategy: Symbol).returns(T::Boolean) }
35
+ def self.validate_template(template, strategy:) # rubocop:disable Naming/PredicateMethod
36
+ raise Error, "Template must be a non-empty string." if template.empty?
37
+
38
+ allowed = allowed_placeholders(strategy)
39
+ messages = collect_template_errors(template, allowed, strategy)
40
+
41
+ raise Error, "#{messages.join("\n")}\nAllowed: #{allowed.map { |v| "{#{v}}" }.join(', ')}" if messages.any?
42
+
43
+ true
44
+ end
45
+
46
+ sig { params(strategy: Symbol).returns(T::Array[String]) }
47
+ def self.allowed_placeholders(strategy)
48
+ case strategy
49
+ when :solo then SOLO_PLACEHOLDERS
50
+ when :group then GROUP_PLACEHOLDERS
51
+ when :multi_ecosystem then MULTI_ECO_PLACEHOLDERS
52
+ else
53
+ raise Error, "Unknown strategy: #{strategy}"
54
+ end
55
+ end
56
+
57
+ sig { params(template: String, allowed: T::Array[String], strategy: Symbol).returns(T::Array[String]) }
58
+ def self.collect_template_errors(template, allowed, strategy)
59
+ used = template.scan(/\{(\w+)\}/).flatten
60
+ unknown = used.uniq.reject { |t| allowed.include?(t) }
61
+ unknown -= ["package_manager"] if strategy == :multi_ecosystem && used.include?("package_manager")
62
+ malformed = template.gsub(/\{\w+\}/, "").match?(/[{}]/)
63
+
64
+ messages = T.let([], T::Array[String])
65
+ messages << "Unknown placeholder(s): #{unknown.map { |v| "{#{v}}" }.join(', ')}" unless unknown.empty?
66
+ messages << "Malformed or unclosed braces detected." if malformed
67
+
68
+ if strategy == :multi_ecosystem && used.include?("package_manager")
69
+ messages << "{package_manager} is not available for multi-ecosystem groups (spans multiple ecosystems)."
70
+ end
71
+
72
+ messages
73
+ end
74
+
75
+ # ---------------------------------------------------------------
76
+ # 2. validate_ref_name(name)
77
+ # Validates the final branch name against Git ref rules.
78
+ # ---------------------------------------------------------------
79
+ sig { params(name: String).returns(T::Boolean) }
80
+ def self.validate_ref_name(name) # rubocop:disable Naming/PredicateMethod
81
+ illegal = %r{
82
+ [\x00-\x1F\x7F\ ~^:?*\[\\] |
83
+ \.\. |
84
+ @\{ |
85
+ // |
86
+ /\. |
87
+ \A/ |
88
+ /\z |
89
+ \.lock\z |
90
+ \A- |
91
+ \.\z |
92
+ \A@\z
93
+ }x
94
+
95
+ raise Error, "Resolved branch name \"#{name}\" is not a valid Git ref." if name.match?(illegal)
96
+
97
+ true
98
+ end
99
+
100
+ # ---------------------------------------------------------------
101
+ # 3. render(template, vars, strategy:, digest: nil)
102
+ # Full pipeline: validate template -> substitute -> append digest
103
+ # -> validate ref.
104
+ # ---------------------------------------------------------------
105
+ sig do
106
+ params(
107
+ template: String,
108
+ vars: T::Hash[String, String],
109
+ strategy: Symbol,
110
+ digest: T.nilable(String)
111
+ ).returns(String)
112
+ end
113
+ def self.render(template, vars, strategy:, digest: nil)
114
+ validate_template(template, strategy: strategy)
115
+
116
+ # Substitute placeholders
117
+ name = template.gsub(/\{(\w+)\}/) do
118
+ key = T.must(Regexp.last_match(1))
119
+ raise Error, "Missing value for placeholder \"{#{key}}\"." unless vars.key?(key)
120
+
121
+ T.must(vars[key])
122
+ end
123
+
124
+ # Auto-append digest for Group/MultiEcosystem
125
+ name = "#{name}-#{digest}" if digest && strategy != :solo
126
+
127
+ # Collapse empty path segments (e.g. {target_branch} when nil) and strip edge slashes
128
+ name = name.squeeze("/").gsub(%r{\A/|/\z}, "")
129
+ raise Error, "Resolved branch name is empty." if name.empty?
130
+
131
+ # Validate git ref after all transformations
132
+ validate_ref_name(name)
133
+
134
+ name
135
+ end
136
+ end
137
+ end
138
+ end
@@ -4,6 +4,8 @@
4
4
  require "digest"
5
5
  require "sorbet-runtime"
6
6
 
7
+ require "dependabot/pull_request_creator/branch_name_template"
8
+
7
9
  module Dependabot
8
10
  class PullRequestCreator
9
11
  class BranchNamer
@@ -28,6 +30,15 @@ module Dependabot
28
30
  sig { returns(T.nilable(Integer)) }
29
31
  attr_reader :max_length
30
32
 
33
+ sig { returns(T.nilable(String)) }
34
+ attr_reader :word_separator
35
+
36
+ sig { returns(T.nilable(String)) }
37
+ attr_reader :branch_name_case
38
+
39
+ sig { returns(T.nilable(String)) }
40
+ attr_reader :template
41
+
31
42
  sig do
32
43
  params(
33
44
  dependencies: T::Array[Dependency],
@@ -35,7 +46,10 @@ module Dependabot
35
46
  target_branch: T.nilable(String),
36
47
  separator: String,
37
48
  prefix: String,
38
- max_length: T.nilable(Integer)
49
+ max_length: T.nilable(Integer),
50
+ word_separator: T.nilable(String),
51
+ branch_name_case: T.nilable(String),
52
+ template: T.nilable(String)
39
53
  )
40
54
  .void
41
55
  end
@@ -45,7 +59,10 @@ module Dependabot
45
59
  target_branch:,
46
60
  separator: "/",
47
61
  prefix: "dependabot",
48
- max_length: nil
62
+ max_length: nil,
63
+ word_separator: nil,
64
+ branch_name_case: nil,
65
+ template: nil
49
66
  )
50
67
  @dependencies = dependencies
51
68
  @files = files
@@ -53,6 +70,9 @@ module Dependabot
53
70
  @separator = separator
54
71
  @prefix = prefix
55
72
  @max_length = max_length
73
+ @word_separator = word_separator
74
+ @branch_name_case = branch_name_case
75
+ @template = template
56
76
  end
57
77
 
58
78
  sig { overridable.returns(String) }
@@ -62,6 +82,25 @@ module Dependabot
62
82
 
63
83
  private
64
84
 
85
+ sig do
86
+ params(
87
+ vars: T::Hash[String, String],
88
+ strategy: Symbol,
89
+ digest: T.nilable(String)
90
+ ).returns(String)
91
+ end
92
+ def render_from_template(vars:, strategy:, digest: nil)
93
+ rendered = BranchNameTemplate.render(
94
+ T.must(template),
95
+ vars,
96
+ strategy: strategy,
97
+ digest: digest
98
+ )
99
+
100
+ # Apply post-processing (separator, word_separator, case) and max-length
101
+ sanitize_branch_name(rendered)
102
+ end
103
+
65
104
  sig { params(ref_name: String).returns(String) }
66
105
  def sanitize_branch_name(ref_name)
67
106
  # General git ref validation
@@ -70,6 +109,27 @@ module Dependabot
70
109
  # Some users need branch names without slashes
71
110
  sanitized_name = sanitized_name.gsub("/", separator)
72
111
 
112
+ # Apply word_separator and case transformation only to content after the prefix,
113
+ # preserving the user-configured prefix as-is.
114
+ if word_separator || branch_name_case
115
+ prefix_with_sep = "#{prefix}#{separator}"
116
+ prefix_part = sanitized_name.start_with?(prefix_with_sep) ? prefix_with_sep : ""
117
+ content = sanitized_name.delete_prefix(prefix_with_sep)
118
+
119
+ # Replace underscores with word_separator in the content after prefix
120
+ content = content.gsub("_", T.must(word_separator)) if word_separator
121
+
122
+ # Apply case transformation to content after prefix
123
+ case branch_name_case
124
+ when "lower"
125
+ content = content.downcase
126
+ when "upper"
127
+ content = content.upcase
128
+ end
129
+
130
+ sanitized_name = "#{prefix_part}#{content}"
131
+ end
132
+
73
133
  # Shorten the ref in case users refs have length limits
74
134
  branch_name_max_length = max_length
75
135
  if branch_name_max_length && (sanitized_name.length > branch_name_max_length)
@@ -19,7 +19,10 @@ module Dependabot
19
19
  includes_security_fixes: T::Boolean,
20
20
  separator: String,
21
21
  prefix: String,
22
- max_length: T.nilable(Integer)
22
+ max_length: T.nilable(Integer),
23
+ word_separator: T.nilable(String),
24
+ branch_name_case: T.nilable(String),
25
+ template: T.nilable(String)
23
26
  )
24
27
  .void
25
28
  end
@@ -31,7 +34,10 @@ module Dependabot
31
34
  includes_security_fixes:,
32
35
  separator: "/",
33
36
  prefix: "dependabot",
34
- max_length: nil
37
+ max_length: nil,
38
+ word_separator: nil,
39
+ branch_name_case: nil,
40
+ template: nil
35
41
  )
36
42
  super(
37
43
  dependencies: dependencies,
@@ -40,6 +46,9 @@ module Dependabot
40
46
  separator: separator,
41
47
  prefix: prefix,
42
48
  max_length: max_length,
49
+ word_separator: word_separator,
50
+ branch_name_case: branch_name_case,
51
+ template: template,
43
52
  )
44
53
 
45
54
  @dependency_group = dependency_group
@@ -48,6 +57,14 @@ module Dependabot
48
57
 
49
58
  sig { override.returns(String) }
50
59
  def new_branch_name
60
+ if template
61
+ return render_from_template(
62
+ vars: template_vars,
63
+ strategy: :group,
64
+ digest: dependency_digest
65
+ )
66
+ end
67
+
51
68
  sanitize_branch_name(File.join(prefixes, group_name_with_dependency_digest))
52
69
  end
53
70
 
@@ -56,6 +73,24 @@ module Dependabot
56
73
  sig { returns(Dependabot::DependencyGroup) }
57
74
  attr_reader :dependency_group
58
75
 
76
+ sig { returns(T::Hash[String, String]) }
77
+ def template_vars
78
+ directory_part = (directory || "/").sub(%r{^/}, "")
79
+ directory_part = "root" if directory_part.empty?
80
+
81
+ group_name = sanitize_ref(dependency_group.name.tr(" ", "-"))
82
+
83
+ vars = {
84
+ "prefix" => prefix,
85
+ "package_manager" => package_manager,
86
+ "directory" => directory_part,
87
+ "group_name" => group_name,
88
+ "name" => group_name,
89
+ "target_branch" => target_branch || ""
90
+ }
91
+ vars
92
+ end
93
+
59
94
  sig { returns(T::Array[String]) }
60
95
  def prefixes
61
96
  [
@@ -19,7 +19,10 @@ module Dependabot
19
19
  multi_ecosystem_name: String,
20
20
  separator: String,
21
21
  prefix: String,
22
- max_length: T.nilable(Integer)
22
+ max_length: T.nilable(Integer),
23
+ word_separator: T.nilable(String),
24
+ branch_name_case: T.nilable(String),
25
+ template: T.nilable(String)
23
26
  )
24
27
  .void
25
28
  end
@@ -31,7 +34,10 @@ module Dependabot
31
34
  multi_ecosystem_name:,
32
35
  separator: "/",
33
36
  prefix: "dependabot",
34
- max_length: nil
37
+ max_length: nil,
38
+ word_separator: nil,
39
+ branch_name_case: nil,
40
+ template: nil
35
41
  )
36
42
  super(
37
43
  dependencies: dependencies,
@@ -40,6 +46,9 @@ module Dependabot
40
46
  separator: separator,
41
47
  prefix: prefix,
42
48
  max_length: max_length,
49
+ word_separator: word_separator,
50
+ branch_name_case: branch_name_case,
51
+ template: template,
43
52
  )
44
53
 
45
54
  @multi_ecosystem_name = multi_ecosystem_name
@@ -48,6 +57,14 @@ module Dependabot
48
57
 
49
58
  sig { override.returns(String) }
50
59
  def new_branch_name
60
+ if template
61
+ return render_from_template(
62
+ vars: template_vars,
63
+ strategy: :multi_ecosystem,
64
+ digest: dependency_digest
65
+ )
66
+ end
67
+
51
68
  sanitize_branch_name(File.join(prefixes, group_name_with_dependency_digest))
52
69
  end
53
70
 
@@ -56,6 +73,19 @@ module Dependabot
56
73
  sig { returns(String) }
57
74
  attr_reader :multi_ecosystem_name
58
75
 
76
+ sig { returns(T::Hash[String, String]) }
77
+ def template_vars
78
+ multi_name = sanitize_ref(multi_ecosystem_name.tr(" ", "-"))
79
+
80
+ vars = {
81
+ "prefix" => prefix,
82
+ "group_name" => multi_name,
83
+ "name" => multi_name,
84
+ "target_branch" => target_branch || ""
85
+ }
86
+ vars
87
+ end
88
+
59
89
  sig { returns(T::Array[String]) }
60
90
  def prefixes
61
91
  [
@@ -15,26 +15,18 @@ module Dependabot
15
15
 
16
16
  sig { override.returns(String) }
17
17
  def new_branch_name
18
+ if template
19
+ return render_from_template(
20
+ vars: template_vars,
21
+ strategy: :solo
22
+ )
23
+ end
24
+
18
25
  return short_branch_name if branch_name_might_be_long?
19
26
 
20
27
  @name ||=
21
28
  T.let(
22
- begin
23
- dependency_name_part =
24
- if dependencies.count > 1 && updating_a_property?
25
- property_name
26
- elsif dependencies.count > 1 && updating_a_dependency_set?
27
- dependency_set.fetch(:group)
28
- else
29
- dependencies
30
- .map(&:name)
31
- .join("-and-")
32
- .tr(":[]", "-")
33
- .tr("@", "")
34
- end
35
-
36
- "#{dependency_name_part}-#{branch_version_suffix}"
37
- end,
29
+ "#{template_dependency_name}-#{branch_version_suffix}",
38
30
  T.nilable(String)
39
31
  )
40
32
 
@@ -53,6 +45,40 @@ module Dependabot
53
45
  ].compact
54
46
  end
55
47
 
48
+ sig { returns(T::Hash[String, String]) }
49
+ def template_vars
50
+ dep_name = template_dependency_name
51
+ version = branch_version_suffix || ""
52
+
53
+ vars = {
54
+ "prefix" => prefix,
55
+ "package_manager" => package_manager,
56
+ "directory" => sanitized_directory,
57
+ "dependency" => dep_name,
58
+ "version" => version,
59
+ "name" => "#{dep_name}-#{version}",
60
+ "target_branch" => target_branch || ""
61
+ }
62
+ vars
63
+ end
64
+
65
+ sig { returns(String) }
66
+ def template_dependency_name
67
+ if dependencies.count > 1 && updating_a_property?
68
+ property_name
69
+ elsif dependencies.count > 1 && updating_a_dependency_set?
70
+ dependency_set.fetch(:group)
71
+ else
72
+ dependencies.map(&:name).join("-and-").tr(":[]", "-").tr("@", "")
73
+ end
74
+ end
75
+
76
+ sig { returns(String) }
77
+ def sanitized_directory
78
+ dir = (files.first&.directory&.tr(" ", "-") || "/").sub(%r{^/}, "")
79
+ dir.empty? ? "root" : dir
80
+ end
81
+
56
82
  sig { returns(String) }
57
83
  def package_manager
58
84
  T.must(dependencies.first).package_manager
@@ -9,6 +9,7 @@ require "dependabot/pull_request_creator"
9
9
  require "dependabot/pull_request_creator/branch_namer/solo_strategy"
10
10
  require "dependabot/pull_request_creator/branch_namer/dependency_group_strategy"
11
11
  require "dependabot/pull_request_creator/branch_namer/multi_ecosystem_strategy"
12
+ require "dependabot/pull_request_creator/branch_name_template"
12
13
 
13
14
  module Dependabot
14
15
  class PullRequestCreator
@@ -33,6 +34,15 @@ module Dependabot
33
34
  sig { returns(T.nilable(Integer)) }
34
35
  attr_reader :max_length
35
36
 
37
+ sig { returns(T.nilable(String)) }
38
+ attr_reader :word_separator
39
+
40
+ sig { returns(T.nilable(String)) }
41
+ attr_reader :branch_name_case
42
+
43
+ sig { returns(T.nilable(String)) }
44
+ attr_reader :template
45
+
36
46
  sig { returns(T.nilable(Dependabot::DependencyGroup)) }
37
47
  attr_reader :dependency_group
38
48
 
@@ -51,6 +61,9 @@ module Dependabot
51
61
  separator: String,
52
62
  prefix: String,
53
63
  max_length: T.nilable(Integer),
64
+ word_separator: T.nilable(String),
65
+ branch_name_case: T.nilable(String),
66
+ template: T.nilable(String),
54
67
  includes_security_fixes: T::Boolean,
55
68
  multi_ecosystem_name: T.nilable(String)
56
69
  )
@@ -64,6 +77,9 @@ module Dependabot
64
77
  separator: "/",
65
78
  prefix: "dependabot",
66
79
  max_length: nil,
80
+ word_separator: nil,
81
+ branch_name_case: nil,
82
+ template: nil,
67
83
  includes_security_fixes: false,
68
84
  multi_ecosystem_name: nil
69
85
  )
@@ -74,6 +90,9 @@ module Dependabot
74
90
  @separator = separator
75
91
  @prefix = prefix
76
92
  @max_length = max_length
93
+ @word_separator = word_separator
94
+ @branch_name_case = branch_name_case
95
+ @template = template
77
96
  @includes_security_fixes = includes_security_fixes
78
97
  @multi_ecosystem_name = multi_ecosystem_name
79
98
  end
@@ -109,6 +128,9 @@ module Dependabot
109
128
  separator: separator,
110
129
  prefix: prefix,
111
130
  max_length: max_length,
131
+ word_separator: word_separator,
132
+ branch_name_case: branch_name_case,
133
+ template: template,
112
134
  multi_ecosystem_name: T.must(multi_ecosystem_name)
113
135
  )
114
136
  end
@@ -121,7 +143,10 @@ module Dependabot
121
143
  target_branch: target_branch,
122
144
  separator: separator,
123
145
  prefix: prefix,
124
- max_length: max_length
146
+ max_length: max_length,
147
+ word_separator: word_separator,
148
+ branch_name_case: branch_name_case,
149
+ template: template
125
150
  )
126
151
  end
127
152
 
@@ -135,7 +160,10 @@ module Dependabot
135
160
  includes_security_fixes: includes_security_fixes,
136
161
  separator: separator,
137
162
  prefix: prefix,
138
- max_length: max_length
163
+ max_length: max_length,
164
+ word_separator: word_separator,
165
+ branch_name_case: branch_name_case,
166
+ template: template
139
167
  )
140
168
  end
141
169
  end
@@ -126,6 +126,15 @@ module Dependabot
126
126
  sig { returns(T.nilable(Integer)) }
127
127
  attr_reader :branch_name_max_length
128
128
 
129
+ sig { returns(T.nilable(String)) }
130
+ attr_reader :branch_name_word_separator
131
+
132
+ sig { returns(T.nilable(String)) }
133
+ attr_reader :branch_name_case
134
+
135
+ sig { returns(T.nilable(String)) }
136
+ attr_reader :branch_name_template
137
+
129
138
  sig { returns(String) }
130
139
  attr_reader :github_redirection_service
131
140
 
@@ -144,7 +153,7 @@ module Dependabot
144
153
  sig { returns(T.nilable(Encoding)) }
145
154
  attr_reader :pr_message_encoding
146
155
 
147
- sig do
156
+ sig do # rubocop:disable Metrics/BlockLength
148
157
  params(
149
158
  source: Dependabot::Source,
150
159
  base_commit: String,
@@ -164,6 +173,9 @@ module Dependabot
164
173
  branch_name_separator: String,
165
174
  branch_name_prefix: String,
166
175
  branch_name_max_length: T.nilable(Integer),
176
+ branch_name_word_separator: T.nilable(String),
177
+ branch_name_case: T.nilable(String),
178
+ branch_name_template: T.nilable(String),
167
179
  label_language: T::Boolean,
168
180
  automerge_candidate: T::Boolean,
169
181
  github_redirection_service: String,
@@ -198,6 +210,9 @@ module Dependabot
198
210
  branch_name_separator: "/",
199
211
  branch_name_prefix: "dependabot",
200
212
  branch_name_max_length: 100,
213
+ branch_name_word_separator: nil,
214
+ branch_name_case: nil,
215
+ branch_name_template: nil,
201
216
  label_language: false,
202
217
  automerge_candidate: false,
203
218
  github_redirection_service: DEFAULT_GITHUB_REDIRECTION_SERVICE,
@@ -227,6 +242,9 @@ module Dependabot
227
242
  @branch_name_separator = branch_name_separator
228
243
  @branch_name_prefix = branch_name_prefix
229
244
  @branch_name_max_length = branch_name_max_length
245
+ @branch_name_word_separator = branch_name_word_separator
246
+ @branch_name_case = branch_name_case
247
+ @branch_name_template = branch_name_template
230
248
  @label_language = label_language
231
249
  @automerge_candidate = automerge_candidate
232
250
  @github_redirection_service = github_redirection_service
@@ -420,6 +438,9 @@ module Dependabot
420
438
  separator: branch_name_separator,
421
439
  prefix: branch_name_prefix,
422
440
  max_length: branch_name_max_length,
441
+ word_separator: branch_name_word_separator,
442
+ branch_name_case: branch_name_case,
443
+ template: branch_name_template,
423
444
  includes_security_fixes: includes_security_fixes?
424
445
  ),
425
446
  T.nilable(Dependabot::PullRequestCreator::BranchNamer)
@@ -126,16 +126,36 @@ module Dependabot
126
126
  )
127
127
  end
128
128
 
129
+ @github_enterprise_cache = T.let({}, T::Hash[String, T::Boolean])
130
+
131
+ sig { void }
132
+ def self.reset_github_enterprise_cache!
133
+ @github_enterprise_cache.clear
134
+ end
135
+
129
136
  sig { params(base_url: String).returns(T::Boolean) }
130
137
  def self.github_enterprise?(base_url)
138
+ return T.must(@github_enterprise_cache[base_url]) if @github_enterprise_cache.key?(base_url)
139
+
140
+ result = detect_github_enterprise(base_url)
141
+ @github_enterprise_cache[base_url] = result unless result.nil?
142
+ result || false
143
+ end
144
+
145
+ sig { params(base_url: String).returns(T.nilable(T::Boolean)) }
146
+ def self.detect_github_enterprise(base_url)
131
147
  resp = Excon.get(File.join(base_url, "status"))
132
- resp.status == 200 &&
133
- # Alternatively: resp.headers["Server"] == "GitHub.com", but this
134
- # currently doesn't work with development environments
135
- ((resp.headers["X-GitHub-Request-Id"] && !resp.headers["X-GitHub-Request-Id"]&.empty?) || false)
148
+ return false if resp.status == 404
149
+
150
+ return unless resp.status == 200
151
+
152
+ # Alternatively: resp.headers["Server"] == "GitHub.com", but this
153
+ # currently doesn't work with development environments
154
+ (resp.headers["X-GitHub-Request-Id"] && !resp.headers["X-GitHub-Request-Id"]&.empty?) || false
136
155
  rescue StandardError
137
- false
156
+ nil
138
157
  end
158
+ private_class_method :detect_github_enterprise
139
159
 
140
160
  sig do
141
161
  params(
data/lib/dependabot.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
5
- VERSION = "0.385.0"
5
+ VERSION = "0.386.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.385.0
4
+ version: 0.386.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -574,6 +574,7 @@ files:
574
574
  - lib/dependabot/pull_request_creator.rb
575
575
  - lib/dependabot/pull_request_creator/azure.rb
576
576
  - lib/dependabot/pull_request_creator/bitbucket.rb
577
+ - lib/dependabot/pull_request_creator/branch_name_template.rb
577
578
  - lib/dependabot/pull_request_creator/branch_namer.rb
578
579
  - lib/dependabot/pull_request_creator/branch_namer/base.rb
579
580
  - lib/dependabot/pull_request_creator/branch_namer/dependency_group_strategy.rb
@@ -621,7 +622,7 @@ licenses:
621
622
  - MIT
622
623
  metadata:
623
624
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
624
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.385.0
625
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.386.0
625
626
  rdoc_options: []
626
627
  require_paths:
627
628
  - lib