sashimi_tanpopo 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/Rakefile +23 -0
- data/Steepfile +32 -0
- data/docs/RECIPE.md +63 -0
- data/exe/sashimi_tanpopo +5 -0
- data/lib/sashimi_tanpopo/cli.rb +169 -0
- data/lib/sashimi_tanpopo/dsl.rb +214 -0
- data/lib/sashimi_tanpopo/logger.rb +34 -0
- data/lib/sashimi_tanpopo/provider/base.rb +54 -0
- data/lib/sashimi_tanpopo/provider/github.rb +228 -0
- data/lib/sashimi_tanpopo/provider/gitlab.rb +288 -0
- data/lib/sashimi_tanpopo/provider/local.rb +28 -0
- data/lib/sashimi_tanpopo/provider.rb +6 -0
- data/lib/sashimi_tanpopo/version.rb +5 -0
- data/lib/sashimi_tanpopo.rb +18 -0
- data/rbs_collection.lock.yaml +196 -0
- data/rbs_collection.yaml +23 -0
- data/sig/sashimi_tanpopo/cli.rbs +28 -0
- data/sig/sashimi_tanpopo/dsl.rbs +68 -0
- data/sig/sashimi_tanpopo/logger.rbs +11 -0
- data/sig/sashimi_tanpopo/provider/base.rbs +25 -0
- data/sig/sashimi_tanpopo/provider/github.rbs +66 -0
- data/sig/sashimi_tanpopo/provider/gitlab.rbs +72 -0
- data/sig/sashimi_tanpopo/provider/local.rbs +15 -0
- data/sig/sashimi_tanpopo.rbs +13 -0
- metadata +307 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SashimiTanpopo
|
|
4
|
+
class DSL
|
|
5
|
+
# Apply recipe file
|
|
6
|
+
#
|
|
7
|
+
# @param recipe_path [String]
|
|
8
|
+
# @param target_dir [String]
|
|
9
|
+
# @param params [Hash<Symbol, String>]
|
|
10
|
+
# @param dry_run [Boolean]
|
|
11
|
+
# @param is_colored [Boolean] Whether show color diff
|
|
12
|
+
# @param is_update_local [Boolean] Whether update local file in `update_file`
|
|
13
|
+
#
|
|
14
|
+
# @return [Hash<String, { before_content: String, after_content: String, mode: String }>] changed files (key: file path, value: Hash)
|
|
15
|
+
#
|
|
16
|
+
# @example Response format
|
|
17
|
+
# {
|
|
18
|
+
# "path/to/changed-file.txt" => {
|
|
19
|
+
# before_content: "foo",
|
|
20
|
+
# after_content: "bar",
|
|
21
|
+
# mode: "100644",
|
|
22
|
+
# }
|
|
23
|
+
# }
|
|
24
|
+
def perform(recipe_path:, target_dir:, params:, dry_run:, is_colored:, is_update_local:)
|
|
25
|
+
evaluate(
|
|
26
|
+
recipe_body: File.read(recipe_path),
|
|
27
|
+
recipe_path: recipe_path,
|
|
28
|
+
target_dir: target_dir,
|
|
29
|
+
params: params,
|
|
30
|
+
dry_run: dry_run,
|
|
31
|
+
is_colored: is_colored,
|
|
32
|
+
is_update_local: is_update_local,
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Apply recipe file for unit test
|
|
37
|
+
#
|
|
38
|
+
# @param recipe_body [String]
|
|
39
|
+
# @param recipe_path [String]
|
|
40
|
+
# @param target_dir [String]
|
|
41
|
+
# @param params [Hash<Symbol, String>]
|
|
42
|
+
# @param dry_run [Boolean]
|
|
43
|
+
# @param is_colored [Boolean] Whether show color diff
|
|
44
|
+
# @param is_update_local [Boolean] Whether update local file in `update_file`
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash<String, { before_content: String, after_content: String, mode: String }>] changed files (key: file path, value: Hash)
|
|
47
|
+
#
|
|
48
|
+
# @example Response format
|
|
49
|
+
# {
|
|
50
|
+
# "path/to/changed-file.txt" => {
|
|
51
|
+
# before_content: "foo",
|
|
52
|
+
# after_content: "bar",
|
|
53
|
+
# mode: "100644",
|
|
54
|
+
# }
|
|
55
|
+
# }
|
|
56
|
+
def evaluate(recipe_body:, recipe_path:, target_dir:, params:, dry_run:, is_colored:, is_update_local:)
|
|
57
|
+
context = EvalContext.new(params: params, dry_run: dry_run, is_colored: is_colored, target_dir: target_dir, is_update_local: is_update_local)
|
|
58
|
+
InstanceEval.new(recipe_body: recipe_body, recipe_path: recipe_path, target_dir: target_dir, context: context).call
|
|
59
|
+
context.changed_files
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class EvalContext
|
|
63
|
+
# @param params [Hash<Symbol, String>]
|
|
64
|
+
# @param dry_run [Boolean]
|
|
65
|
+
# @param is_colored [Boolean] Whether show color diff
|
|
66
|
+
# @param target_dir [String]
|
|
67
|
+
# @param is_update_local [Boolean] Whether update local file in `update_file`
|
|
68
|
+
def initialize(params:, dry_run:, is_colored:, target_dir:, is_update_local:)
|
|
69
|
+
@__params__ = params
|
|
70
|
+
@__dry_run__ = dry_run
|
|
71
|
+
@__target_dir__ = target_dir
|
|
72
|
+
@__is_update_local__ = is_update_local
|
|
73
|
+
|
|
74
|
+
@__diffy_format__ = is_colored ? :color : :text
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# passed from `--params`
|
|
78
|
+
#
|
|
79
|
+
# @return [Hash<Symbol, String>]
|
|
80
|
+
#
|
|
81
|
+
# @example Pass params via `--params`
|
|
82
|
+
# sashimi_tanpopo local --params name:sue445 --params lang:ja recipe.rb
|
|
83
|
+
#
|
|
84
|
+
# @example within `recipe.rb`
|
|
85
|
+
# # recipe.rb
|
|
86
|
+
#
|
|
87
|
+
# params
|
|
88
|
+
# #=> {name: "sue445", lang: "ja"}
|
|
89
|
+
def params
|
|
90
|
+
@__params__
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return [Hash<String, { before_content: String, after_content: String, mode: String }>] key: file path, value: Hash
|
|
94
|
+
def changed_files
|
|
95
|
+
@__changed_files__ ||= {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @return [Boolean] Whether dry run
|
|
99
|
+
#
|
|
100
|
+
# @example
|
|
101
|
+
# unless dry_run?
|
|
102
|
+
# puts "This will be called when apply mode"
|
|
103
|
+
# end
|
|
104
|
+
def dry_run?
|
|
105
|
+
@__dry_run__
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Update files if exists
|
|
109
|
+
#
|
|
110
|
+
# @param pattern [String] Path to target file (relative path from `--target-dir`). This supports [`Dir.glob`](https://ruby-doc.org/current/Dir.html#method-c-glob) pattern. (e.g. `.github/workflows/*.yml`)
|
|
111
|
+
#
|
|
112
|
+
# @yieldparam content [String] Content of file. If `content` is changed in block, file will be changed.
|
|
113
|
+
#
|
|
114
|
+
# @example Update single file
|
|
115
|
+
# update_file "test.txt" do |content|
|
|
116
|
+
# content.gsub!("name", params[:name])
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# @example Update multiple files
|
|
120
|
+
# update_file ".github/workflows/*.yml" do |content|
|
|
121
|
+
# content.gsub!(/ruby-version: "(.+)"/, %Q{ruby-version: "#{params[:ruby_version]}"})
|
|
122
|
+
# end
|
|
123
|
+
def update_file(pattern, &block)
|
|
124
|
+
Dir.glob(pattern).each do |path|
|
|
125
|
+
full_file_path = File.join(@__target_dir__, path)
|
|
126
|
+
before_content = File.read(full_file_path)
|
|
127
|
+
|
|
128
|
+
SashimiTanpopo.logger.info "Checking #{full_file_path}"
|
|
129
|
+
|
|
130
|
+
after_content = update_single_file(path, &block)
|
|
131
|
+
|
|
132
|
+
unless after_content
|
|
133
|
+
SashimiTanpopo.logger.info "#{full_file_path} isn't changed"
|
|
134
|
+
next
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
changed_files[path] = {
|
|
138
|
+
before_content: before_content,
|
|
139
|
+
after_content: after_content,
|
|
140
|
+
mode: File.stat(full_file_path).mode.to_s(8)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if dry_run?
|
|
144
|
+
SashimiTanpopo.logger.info "#{full_file_path} will be changed (dryrun)"
|
|
145
|
+
else
|
|
146
|
+
SashimiTanpopo.logger.info "#{full_file_path} is changed"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
# @param path [String]
|
|
154
|
+
#
|
|
155
|
+
# @yieldparam content [String] content of file
|
|
156
|
+
#
|
|
157
|
+
# @return [String] Content of changed file if file is changed
|
|
158
|
+
# @return [nil] file isn't changed
|
|
159
|
+
def update_single_file(path)
|
|
160
|
+
return nil unless File.exist?(path)
|
|
161
|
+
|
|
162
|
+
content = File.read(path)
|
|
163
|
+
before_content = content.dup
|
|
164
|
+
|
|
165
|
+
yield content
|
|
166
|
+
|
|
167
|
+
# File isn't changed
|
|
168
|
+
return nil if content == before_content
|
|
169
|
+
|
|
170
|
+
show_diff(before_content, content)
|
|
171
|
+
|
|
172
|
+
File.write(path, content) if !dry_run? && @__is_update_local__
|
|
173
|
+
|
|
174
|
+
content
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @param str1 [String]
|
|
178
|
+
# @param str2 [String]
|
|
179
|
+
def show_diff(str1, str2)
|
|
180
|
+
diff_text = Diffy::Diff.new(str1, str2, context: 3).to_s(@__diffy_format__) # steep:ignore
|
|
181
|
+
|
|
182
|
+
SashimiTanpopo.logger.info "diff:"
|
|
183
|
+
|
|
184
|
+
diff_text.each_line do |line|
|
|
185
|
+
SashimiTanpopo.logger.info line
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
class InstanceEval
|
|
191
|
+
# @param recipe_body [String]
|
|
192
|
+
# @param recipe_path [String]
|
|
193
|
+
# @param target_dir [String]
|
|
194
|
+
# @param context [EvalContext]
|
|
195
|
+
def initialize(recipe_body:, recipe_path:, target_dir:, context:)
|
|
196
|
+
@code = <<~RUBY
|
|
197
|
+
Dir.chdir(@target_dir) do
|
|
198
|
+
@context.instance_eval do
|
|
199
|
+
eval(#{recipe_body.dump}, nil, #{recipe_path.dump}, 1)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
RUBY
|
|
203
|
+
|
|
204
|
+
@target_dir = target_dir
|
|
205
|
+
@context = context
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def call
|
|
209
|
+
eval(@code)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
private_constant :InstanceEval
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SashimiTanpopo
|
|
4
|
+
module Logger
|
|
5
|
+
class Formatter
|
|
6
|
+
# @param severity [String]
|
|
7
|
+
# @param datetime [Time]
|
|
8
|
+
# @param progname [String]
|
|
9
|
+
# @param msg [String]
|
|
10
|
+
def call(severity, datetime, progname, msg)
|
|
11
|
+
log = "%s : %s" % ["%5s" % severity, msg.strip]
|
|
12
|
+
|
|
13
|
+
log + "\n"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@logger = ::Logger.new($stdout).tap do |l|
|
|
19
|
+
l.formatter = SashimiTanpopo::Logger::Formatter.new
|
|
20
|
+
end
|
|
21
|
+
$stdout.sync = true
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# @return [::Logger]
|
|
25
|
+
def logger
|
|
26
|
+
@logger
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param l [::Logger]
|
|
30
|
+
def logger=(l)
|
|
31
|
+
@logger = l
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SashimiTanpopo
|
|
4
|
+
module Provider
|
|
5
|
+
class Base
|
|
6
|
+
# @param recipe_paths [Array<String>]
|
|
7
|
+
# @param target_dir [String,nil]
|
|
8
|
+
# @param params [Hash<Symbol, String>]
|
|
9
|
+
# @param dry_run [Boolean]
|
|
10
|
+
# @param is_colored [Boolean] Whether show color diff
|
|
11
|
+
# @param is_update_local [Boolean] Whether update local file in `update_file`
|
|
12
|
+
def initialize(recipe_paths:, target_dir:, params:, dry_run:, is_colored:, is_update_local:)
|
|
13
|
+
@recipe_paths = recipe_paths
|
|
14
|
+
@target_dir = target_dir || Dir.pwd
|
|
15
|
+
@params = params
|
|
16
|
+
@dry_run = dry_run
|
|
17
|
+
@is_colored = is_colored
|
|
18
|
+
@is_update_local = is_update_local
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Apply recipe files
|
|
22
|
+
#
|
|
23
|
+
# @return [Hash<String, { before_content: String, after_content: String, mode: String }>] changed files (key: file path, value: Hash)
|
|
24
|
+
#
|
|
25
|
+
# @example Response format
|
|
26
|
+
# {
|
|
27
|
+
# "path/to/changed-file.txt" => {
|
|
28
|
+
# before_content: "foo",
|
|
29
|
+
# after_content: "bar",
|
|
30
|
+
# mode: "100644",
|
|
31
|
+
# }
|
|
32
|
+
# }
|
|
33
|
+
def apply_recipe_files
|
|
34
|
+
all_changed_files = {} # : changed_files
|
|
35
|
+
|
|
36
|
+
@recipe_paths.each do |recipe_path|
|
|
37
|
+
changed_files =
|
|
38
|
+
DSL.new.perform(
|
|
39
|
+
recipe_path: recipe_path,
|
|
40
|
+
target_dir: @target_dir,
|
|
41
|
+
params: @params,
|
|
42
|
+
dry_run: @dry_run,
|
|
43
|
+
is_colored: @is_colored,
|
|
44
|
+
is_update_local: @is_update_local,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
all_changed_files.merge!(changed_files)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
all_changed_files
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SashimiTanpopo
|
|
4
|
+
module Provider
|
|
5
|
+
# Apply recipe files and create Pull Request
|
|
6
|
+
class GitHub < Base
|
|
7
|
+
DEFAULT_API_ENDPOINT = "https://api.github.com/"
|
|
8
|
+
|
|
9
|
+
DEFAULT_GITHUB_HOST = "github.com"
|
|
10
|
+
|
|
11
|
+
# @param recipe_paths [Array<String>]
|
|
12
|
+
# @param target_dir [String,nil]
|
|
13
|
+
# @param params [Hash<Symbol, String>]
|
|
14
|
+
# @param dry_run [Boolean]
|
|
15
|
+
# @param is_colored [Boolean] Whether show color diff
|
|
16
|
+
# @param git_username [String,nil]
|
|
17
|
+
# @param git_email [String,nil]
|
|
18
|
+
# @param commit_message [String]
|
|
19
|
+
# @param repository [String]
|
|
20
|
+
# @param access_token [String]
|
|
21
|
+
# @param api_endpoint [String]
|
|
22
|
+
# @param pr_title [String]
|
|
23
|
+
# @param pr_body [String]
|
|
24
|
+
# @param pr_source_branch [String] Pull Request source branch (a.k.a. head branch)
|
|
25
|
+
# @param pr_target_branch [String] Pull Request target branch (a.k.a. base branch)
|
|
26
|
+
# @param pr_assignees [Array<String>]
|
|
27
|
+
# @param pr_reviewers [Array<String>]
|
|
28
|
+
# @param pr_labels [Array<String>]
|
|
29
|
+
# @param is_draft_pr [Boolean] Whether create draft Pull Request
|
|
30
|
+
def initialize(recipe_paths:, target_dir:, params:, dry_run:, is_colored:,
|
|
31
|
+
git_username:, git_email:, commit_message:,
|
|
32
|
+
repository:, access_token:, api_endpoint: DEFAULT_API_ENDPOINT,
|
|
33
|
+
pr_title:, pr_body:, pr_source_branch:, pr_target_branch:,
|
|
34
|
+
pr_assignees: [], pr_reviewers: [], pr_labels: [], is_draft_pr:)
|
|
35
|
+
super(
|
|
36
|
+
recipe_paths: recipe_paths,
|
|
37
|
+
target_dir: target_dir,
|
|
38
|
+
params: params,
|
|
39
|
+
dry_run: dry_run,
|
|
40
|
+
is_colored: is_colored,
|
|
41
|
+
is_update_local: false,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@commit_message = commit_message
|
|
45
|
+
@repository = repository
|
|
46
|
+
@pr_title = pr_title
|
|
47
|
+
@pr_body = pr_body
|
|
48
|
+
@pr_source_branch = pr_source_branch
|
|
49
|
+
@pr_target_branch = pr_target_branch
|
|
50
|
+
@pr_assignees = pr_assignees
|
|
51
|
+
@pr_reviewers = pr_reviewers
|
|
52
|
+
@pr_labels = pr_labels
|
|
53
|
+
@is_draft_pr = is_draft_pr
|
|
54
|
+
|
|
55
|
+
@client = Octokit::Client.new(api_endpoint: api_endpoint, access_token: access_token)
|
|
56
|
+
|
|
57
|
+
@git_username =
|
|
58
|
+
if git_username
|
|
59
|
+
git_username
|
|
60
|
+
else
|
|
61
|
+
current_user_name
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@git_email =
|
|
65
|
+
if git_email
|
|
66
|
+
git_email
|
|
67
|
+
else
|
|
68
|
+
"#{@git_username}@users.noreply.#{self.class.github_host(api_endpoint)}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Apply recipe files
|
|
73
|
+
#
|
|
74
|
+
# @return [String] Created Pull Request URL
|
|
75
|
+
# @return [nil] Pull Request isn't created
|
|
76
|
+
def perform
|
|
77
|
+
changed_files = apply_recipe_files
|
|
78
|
+
|
|
79
|
+
return nil if changed_files.empty? || @dry_run
|
|
80
|
+
|
|
81
|
+
if exists_branch?(@pr_source_branch)
|
|
82
|
+
SashimiTanpopo.logger.info "Skipped because branch #{@pr_source_branch} already exists on #{@repository}"
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
create_branch_and_push_changes(changed_files)
|
|
87
|
+
|
|
88
|
+
pr = create_pull_request
|
|
89
|
+
|
|
90
|
+
add_pr_labels(pr[:number])
|
|
91
|
+
add_pr_assignees(pr[:number])
|
|
92
|
+
add_pr_reviewers(pr[:number])
|
|
93
|
+
|
|
94
|
+
pr[:html_url]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get GitHub host from api_endpoint
|
|
98
|
+
#
|
|
99
|
+
# @param api_endpoint [String]
|
|
100
|
+
#
|
|
101
|
+
# @return [String]
|
|
102
|
+
def self.github_host(api_endpoint)
|
|
103
|
+
return DEFAULT_GITHUB_HOST if api_endpoint == DEFAULT_API_ENDPOINT
|
|
104
|
+
|
|
105
|
+
matched = %r{^https?://(.+)/api}.match(api_endpoint)
|
|
106
|
+
return matched[1] if matched # steep:ignore
|
|
107
|
+
|
|
108
|
+
DEFAULT_GITHUB_HOST
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# @return [String]
|
|
114
|
+
#
|
|
115
|
+
# @see https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user
|
|
116
|
+
def current_user_name
|
|
117
|
+
@client.user[:login]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Whether exists branch on repository
|
|
121
|
+
#
|
|
122
|
+
# @param branch [String]
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def exists_branch?(branch)
|
|
126
|
+
@client.branch(@repository, branch)
|
|
127
|
+
true
|
|
128
|
+
rescue Octokit::NotFound
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Create branch on repository and push changes
|
|
133
|
+
#
|
|
134
|
+
# @param changed_files [Hash<String, { before_content: String, after_content: String, mode: String }>] key: file path, value: Hash
|
|
135
|
+
#
|
|
136
|
+
# @see https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#get-a-reference
|
|
137
|
+
# @see https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#create-a-reference
|
|
138
|
+
# @see https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#get-a-commit
|
|
139
|
+
# @see https://docs.github.com/en/rest/git/trees#create-a-tree
|
|
140
|
+
# @see https://docs.github.com/en/rest/git/commits?apiVersion=2022-11-28#create-a-commit
|
|
141
|
+
# @see https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#update-a-reference
|
|
142
|
+
def create_branch_and_push_changes(changed_files)
|
|
143
|
+
current_ref = @client.ref(@repository, "heads/#{@pr_target_branch}")
|
|
144
|
+
branch_ref = @client.create_ref(@repository, "heads/#{@pr_source_branch}", current_ref.object.sha) # steep:ignore
|
|
145
|
+
|
|
146
|
+
branch_commit = @client.commit(@repository, branch_ref.object.sha) # steep:ignore
|
|
147
|
+
|
|
148
|
+
tree_metas =
|
|
149
|
+
changed_files.map do |path, data|
|
|
150
|
+
create_tree_meta(path: path, body: data[:after_content], mode: data[:mode])
|
|
151
|
+
end
|
|
152
|
+
tree = @client.create_tree(@repository, tree_metas, base_tree: branch_commit.commit.tree.sha) # steep:ignore
|
|
153
|
+
|
|
154
|
+
commit = @client.create_commit(
|
|
155
|
+
@repository,
|
|
156
|
+
@commit_message,
|
|
157
|
+
tree.sha, # steep:ignore
|
|
158
|
+
branch_ref.object.sha, # steep:ignore
|
|
159
|
+
author: {
|
|
160
|
+
name: @git_username,
|
|
161
|
+
email: @git_email,
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@client.update_ref(@repository, "heads/#{@pr_source_branch}", commit.sha) # steep:ignore
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @param path [String]
|
|
169
|
+
# @param body [String]
|
|
170
|
+
# @param mode [String]
|
|
171
|
+
#
|
|
172
|
+
# @return [Hash<{ path: String, mode: String, type: String, sha: String }>]
|
|
173
|
+
#
|
|
174
|
+
# @see https://docs.github.com/en/rest/git/blobs#create-a-blob
|
|
175
|
+
def create_tree_meta(path:, body:, mode:)
|
|
176
|
+
file_body_sha = @client.create_blob(@repository, body)
|
|
177
|
+
|
|
178
|
+
{
|
|
179
|
+
path: path,
|
|
180
|
+
mode: mode,
|
|
181
|
+
type: "blob",
|
|
182
|
+
sha: file_body_sha,
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @return [Hash{pr_number: Integer, html_url: String}] Created Pull Request info
|
|
187
|
+
#
|
|
188
|
+
# @see https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request
|
|
189
|
+
def create_pull_request
|
|
190
|
+
pr = @client.create_pull_request(@repository, @pr_target_branch, @pr_source_branch, @pr_title, @pr_body, draft: @is_draft_pr)
|
|
191
|
+
|
|
192
|
+
SashimiTanpopo.logger.info "Pull Request is created: #{pr[:html_url]}"
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
number: pr[:number],
|
|
196
|
+
html_url: pr[:html_url],
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @param pr_number [Integer]
|
|
201
|
+
#
|
|
202
|
+
# @see https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#add-labels-to-an-issue
|
|
203
|
+
def add_pr_labels(pr_number)
|
|
204
|
+
return if @pr_labels.empty?
|
|
205
|
+
|
|
206
|
+
@client.add_labels_to_an_issue(@repository, pr_number, @pr_labels)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @param pr_number [Integer]
|
|
210
|
+
#
|
|
211
|
+
# @see https://docs.github.com/en/rest/issues/assignees?apiVersion=2022-11-28#add-assignees-to-an-issue
|
|
212
|
+
def add_pr_assignees(pr_number)
|
|
213
|
+
return if @pr_assignees.empty?
|
|
214
|
+
|
|
215
|
+
@client.add_assignees(@repository, pr_number, @pr_assignees)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# @param pr_number [Integer]
|
|
219
|
+
#
|
|
220
|
+
# @see https://docs.github.com/en/rest/pulls/review-requests?apiVersion=2022-11-28#request-reviewers-for-a-pull-request
|
|
221
|
+
def add_pr_reviewers(pr_number)
|
|
222
|
+
return if @pr_reviewers.empty?
|
|
223
|
+
|
|
224
|
+
@client.request_pull_request_review(@repository, pr_number, reviewers: @pr_reviewers)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|