pod-binary-pack 1.0.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/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/exe/pod-binary-pack +6 -0
- data/lib/core/auto_build.rb +299 -0
- data/lib/core/build_item.rb +69 -0
- data/lib/core/cli.rb +442 -0
- data/lib/core/commit.rb +19 -0
- data/lib/core/config.rb +56 -0
- data/lib/core/cost.rb +68 -0
- data/lib/core/git_helper.rb +222 -0
- data/lib/core/github_fork.rb +425 -0
- data/lib/core/github_helper.rb +334 -0
- data/lib/core/hook_manager.rb +32 -0
- data/lib/core/hosting/gitee_client.rb +114 -0
- data/lib/core/hosting/gitee_provider.rb +312 -0
- data/lib/core/hosting/gitlab_provider.rb +342 -0
- data/lib/core/hosting/provider.rb +96 -0
- data/lib/core/hosting.rb +43 -0
- data/lib/core/logger.rb +66 -0
- data/lib/core/podspec.rb +126 -0
- data/lib/core/push.rb +80 -0
- data/lib/core/repo.rb +284 -0
- data/lib/core/repo_cocoapods.rb +64 -0
- data/lib/core/repo_module.rb +281 -0
- data/lib/core/repo_module_binary.rb +439 -0
- data/lib/core/repo_module_source.rb +506 -0
- data/lib/core/repo_xcode.rb +41 -0
- data/lib/core/repo_xcode_package.rb +713 -0
- data/lib/core/repo_xcode_shell.rb +170 -0
- data/lib/core/util/file_helper.rb +100 -0
- data/lib/core/util/string.rb +35 -0
- data/lib/pod/binary_pack/version.rb +7 -0
- data/lib/pod/binary_pack.rb +9 -0
- metadata +144 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require_relative 'provider'
|
|
5
|
+
require_relative 'gitee_client'
|
|
6
|
+
require_relative '../push'
|
|
7
|
+
require_relative '../git_helper'
|
|
8
|
+
|
|
9
|
+
module Build
|
|
10
|
+
# Gitee 平台实现(适用于 gitee.com 个人标准版)
|
|
11
|
+
# 仓库标识统一使用 owner/repo(full_name),不使用数字ID
|
|
12
|
+
class GiteeProvider < Hosting::Provider
|
|
13
|
+
include Singleton
|
|
14
|
+
|
|
15
|
+
DEFAULT_ENDPOINT = 'https://gitee.com/api/v5'
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
super
|
|
19
|
+
@api = nil
|
|
20
|
+
@user_login = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def available?
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 创建gitee仓库 如果不存在的话
|
|
28
|
+
def create_project_if_need(project_name, http_url, private: true, description: nil)
|
|
29
|
+
unless gitee_url?(http_url)
|
|
30
|
+
Build.log.info('不使用gitee接口检查和创建项目')
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Build.log.detail("检查gitee仓库(#{http_url})是否存在")
|
|
35
|
+
|
|
36
|
+
info = find_project(project_name, http_url, false)
|
|
37
|
+
if info
|
|
38
|
+
Build.log.info("已存在 仓库: #{info.full_name} 命名空间: #{info.namespace_path} 无需新建")
|
|
39
|
+
info.full_name
|
|
40
|
+
else
|
|
41
|
+
Build.log.info("不存在 需要创建仓库,仓库描述: #{description}")
|
|
42
|
+
do_create_project(project_name, http_url, description, private)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def find_project(pod_name, http_url, can_raise = true)
|
|
47
|
+
unless gitee_url?(http_url)
|
|
48
|
+
Build.log.info('不使用gitee接口查询仓库')
|
|
49
|
+
return nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
_host, owner, repo = split_url(http_url)
|
|
53
|
+
|
|
54
|
+
data = api.get("/repos/#{escape(owner)}/#{escape(repo)}")
|
|
55
|
+
build_info(data)
|
|
56
|
+
rescue GiteeClient::NotFound
|
|
57
|
+
if can_raise
|
|
58
|
+
error = "当前配置access_token检索不到#{owner}/#{repo},可能是如下原因:\n"
|
|
59
|
+
error += "1、该组件在gitee仓库中的名字和#{pod_name}不一致\n"
|
|
60
|
+
error += "2、当前令牌账号对 #{owner}/#{repo} 没有权限,请用仓库管理员账号生成令牌"
|
|
61
|
+
pusher.push('❌ 打包失败', error)
|
|
62
|
+
Process.exit(-1)
|
|
63
|
+
end
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 标准版保护分支: 保护后仅仓库管理员可推送/合并
|
|
68
|
+
def lock_branch(repo_info, branch, page_url)
|
|
69
|
+
owner, repo = split_full_name(repo_info.full_name)
|
|
70
|
+
branch_path = "#{escape(owner)}/#{escape(repo)}/branches/#{escape(branch)}/protection"
|
|
71
|
+
|
|
72
|
+
# 先取消已有的保护(404说明本来就没保护,忽略)
|
|
73
|
+
begin
|
|
74
|
+
api.delete("/repos/#{branch_path}")
|
|
75
|
+
rescue GiteeClient::NotFound
|
|
76
|
+
# 未保护,无需先取消
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# 重新保护
|
|
80
|
+
api.put("/repos/#{branch_path}", {})
|
|
81
|
+
|
|
82
|
+
"#{page_url}/branches"
|
|
83
|
+
rescue GiteeClient::Error => e
|
|
84
|
+
pusher.push('❌ 打包失败', "锁死分支#{branch}失败: #{e.message}")
|
|
85
|
+
Process.exit(-1)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# 判断分支是否已经包含某次提交(分页拉取分支提交列表)
|
|
89
|
+
def branch_contain_commit?(repo_info, branch, commit_sha)
|
|
90
|
+
owner, repo = split_full_name(repo_info.full_name)
|
|
91
|
+
current_page = 1
|
|
92
|
+
per_page = 100
|
|
93
|
+
|
|
94
|
+
loop do
|
|
95
|
+
commits = api.get(
|
|
96
|
+
"/repos/#{escape(owner)}/#{escape(repo)}/commits",
|
|
97
|
+
sha: branch,
|
|
98
|
+
page: current_page,
|
|
99
|
+
per_page: per_page
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
commits.each do |obj|
|
|
103
|
+
return true if obj['sha'] == commit_sha
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
break if commits.size < per_page
|
|
107
|
+
|
|
108
|
+
current_page += 1
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
false
|
|
112
|
+
rescue GiteeClient::Error => e
|
|
113
|
+
pusher.push('❌ 打包失败', "查询gitee提交记录失败: #{e.message}")
|
|
114
|
+
Process.exit(-1)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# 本地 fast-forward 方式合并分支到目标分支并推送
|
|
118
|
+
# 等价于GitLab端 ff 合并: 目标分支直接快进到源分支HEAD,不产生merge commit
|
|
119
|
+
def merge_branch(repo_module, target_branch)
|
|
120
|
+
Build.log.step("开始合并分支到#{target_branch}(#{File.basename(repo_module.path)})")
|
|
121
|
+
|
|
122
|
+
return unless check_merge_branch(repo_module, target_branch)
|
|
123
|
+
|
|
124
|
+
path = repo_module.path
|
|
125
|
+
source_branch = repo_module.branch
|
|
126
|
+
|
|
127
|
+
# 丢弃保护可能的残留,拉取所有远端最新引用
|
|
128
|
+
GitHelper.fetch(path)
|
|
129
|
+
|
|
130
|
+
if GitHelper.remote_branch?(path, target_branch)
|
|
131
|
+
# 基于远端目标分支创建/重置本地目标分支
|
|
132
|
+
GitHelper.checkout_tracking(path, target_branch)
|
|
133
|
+
|
|
134
|
+
# 快进到源分支,非ff情况check_merge_branch已拦截,这里失败直接退出
|
|
135
|
+
GitHelper.merge_ff_only(path, "origin/#{source_branch}")
|
|
136
|
+
|
|
137
|
+
# 推送目标分支
|
|
138
|
+
GitHelper.push(path, target_branch)
|
|
139
|
+
else
|
|
140
|
+
# 远端目标分支不存在,直接把源分支推送为目标分支
|
|
141
|
+
Build.log.info("远端不存在#{target_branch}分支,将当前源分支推送创建该分支")
|
|
142
|
+
GitHelper.push_head_as(path, target_branch)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# 切回源分支,后续打tag等流程依赖当前分支
|
|
146
|
+
GitHelper.checkout(path, source_branch)
|
|
147
|
+
|
|
148
|
+
Build.log.detail("自动合并完毕 #{source_branch} => #{target_branch}")
|
|
149
|
+
pusher.push("【✅组件合并到#{target_branch}成功】")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
# 合并前置校验
|
|
155
|
+
def check_merge_branch(repo_module, target_branch)
|
|
156
|
+
unless repo_module.build_item.merge_master
|
|
157
|
+
Build.log.info('本次构建 merge_master = false 不执行自动合并')
|
|
158
|
+
return false
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
source_branch = repo_module.branch
|
|
162
|
+
if target_branch == source_branch
|
|
163
|
+
Build.log.warn("源分支#{source_branch}和目标分支#{target_branch}指定名字一样, 无需执行合并")
|
|
164
|
+
return false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
path = repo_module.path
|
|
168
|
+
GitHelper.fetch(path)
|
|
169
|
+
|
|
170
|
+
# 目标分支远端不存在,直接允许(后续推送创建)
|
|
171
|
+
unless GitHelper.remote_branch?(path, target_branch)
|
|
172
|
+
return true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
target_ref = "origin/#{target_branch}"
|
|
176
|
+
source_ref = "origin/#{source_branch}"
|
|
177
|
+
|
|
178
|
+
target_last_commit = GitHelper.revision(path, target_ref)
|
|
179
|
+
source_last_commit = GitHelper.revision(path, source_ref)
|
|
180
|
+
|
|
181
|
+
unless target_last_commit
|
|
182
|
+
return true
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# 源分支和目标分支最新提交一致
|
|
186
|
+
if target_last_commit == source_last_commit
|
|
187
|
+
Build.log.warn("源分支#{source_branch}和目标分支#{target_branch}最新提交一致, 无需执行合并")
|
|
188
|
+
return false
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# 目标分支最新提交是源分支的祖先 => 可以ff快进合并
|
|
192
|
+
if GitHelper.ancestor?(path, target_ref, source_ref)
|
|
193
|
+
return true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
error = "当前分支#{source_branch}落后于(或分叉于)#{target_branch}, 无法fast-forward合并, 请先手动合并分支后再打包并执行自动合并分支"
|
|
197
|
+
pusher.push('❌ 打包失败', error)
|
|
198
|
+
Process.exit(-1)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def do_create_project(pod_name, http_url, description, is_private)
|
|
202
|
+
_host, owner, _repo = split_url(http_url)
|
|
203
|
+
|
|
204
|
+
params = {
|
|
205
|
+
name: pod_name,
|
|
206
|
+
path: pod_name,
|
|
207
|
+
description: description,
|
|
208
|
+
# gitee无私有/公开之外的internal级别: true私有 false公开
|
|
209
|
+
private: is_private ? true : false,
|
|
210
|
+
auto_init: true
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
data =
|
|
214
|
+
if owner == user_login
|
|
215
|
+
# 个人空间建仓
|
|
216
|
+
api.post('/user/repos', params)
|
|
217
|
+
else
|
|
218
|
+
# 组织空间建仓,先校验令牌对组织有权限
|
|
219
|
+
begin
|
|
220
|
+
api.get("/orgs/#{escape(owner)}")
|
|
221
|
+
rescue GiteeClient::Error
|
|
222
|
+
error = "命名空间#{owner}不存在,或当前令牌账号无权在其下创建仓库。请先在Gitee创建组织/空间#{owner},并使用该组织管理员的令牌"
|
|
223
|
+
pusher.push('❌ 打包失败', error)
|
|
224
|
+
Process.exit(-1)
|
|
225
|
+
end
|
|
226
|
+
api.post("/orgs/#{escape(owner)}/repos", params)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
full_name = data.is_a?(Hash) ? data['full_name'] : nil
|
|
230
|
+
unless full_name
|
|
231
|
+
pusher.push('❌ 打包失败', '创建gitee仓库失败,接口未返回仓库信息')
|
|
232
|
+
Process.exit(-1)
|
|
233
|
+
end
|
|
234
|
+
full_name
|
|
235
|
+
rescue GiteeClient::Error => e
|
|
236
|
+
pusher.push('❌ 打包失败', "创建gitee仓库失败: #{e.message}")
|
|
237
|
+
Process.exit(-1)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# 令牌所属用户的login(用于区分个人空间建仓还是组织建仓)
|
|
241
|
+
def user_login
|
|
242
|
+
@user_login ||= api.get('/user')['login']
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def build_info(data)
|
|
246
|
+
namespace = data['namespace'] || {}
|
|
247
|
+
Hosting::RepoInfo.new(
|
|
248
|
+
id: data['id'],
|
|
249
|
+
full_name: data['full_name'],
|
|
250
|
+
namespace_path: namespace['path']
|
|
251
|
+
)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def split_full_name(full_name)
|
|
255
|
+
full_name.to_s.split('/', 2)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# 从ssh/http地址解析 host/owner/repo
|
|
259
|
+
# git@gitee.com:owner/repo.git -> [gitee.com, owner, repo]
|
|
260
|
+
# https://gitee.com/owner/repo(.git) -> [gitee.com, owner, repo]
|
|
261
|
+
def split_url(url)
|
|
262
|
+
url = url.to_s
|
|
263
|
+
if url.start_with?('git@')
|
|
264
|
+
tail = url.split('@', 2).last
|
|
265
|
+
host, path = tail.split(':', 2)
|
|
266
|
+
else
|
|
267
|
+
u = URI(url)
|
|
268
|
+
host = u.host
|
|
269
|
+
path = u.path
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
path = path.to_s.sub(/\A\//, '').sub(/\.git\z/, '')
|
|
273
|
+
parts = path.split('/')
|
|
274
|
+
[host.to_s, parts[0], parts[1]]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def gitee_url?(url)
|
|
278
|
+
Hosting.host_of(url).to_s.downcase.include?('gitee')
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def escape(value)
|
|
282
|
+
GiteeClient.escape_segment(value)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def api
|
|
286
|
+
@api ||= GiteeClient.new(endpoint, access_token)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def endpoint
|
|
290
|
+
value = ENV['GITEE_API_URL'].to_s.strip
|
|
291
|
+
value = read_git_config('gitee.apiurl') if value.empty?
|
|
292
|
+
value.empty? ? DEFAULT_ENDPOINT : value
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def access_token
|
|
296
|
+
value = ENV['GITEE_ACCESS_TOKEN'].to_s.strip
|
|
297
|
+
value = read_git_config('gitee.accesstoken') if value.empty?
|
|
298
|
+
if value.empty?
|
|
299
|
+
error = "未读取到gitee令牌,请通过以下任一方式配置(优先级从高到低):\n"
|
|
300
|
+
error += "1、Jenkins环境变量 GITEE_ACCESS_TOKEN(推荐)\n"
|
|
301
|
+
error += "2、git config --global gitee.accesstoken \"xxx\""
|
|
302
|
+
pusher.push('❌ 打包失败', error)
|
|
303
|
+
Process.exit(-1)
|
|
304
|
+
end
|
|
305
|
+
value
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def read_git_config(key)
|
|
309
|
+
%x(git config --global #{key}).to_s.strip
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require_relative 'provider'
|
|
5
|
+
|
|
6
|
+
module Build
|
|
7
|
+
# GitLab 平台实现,保留原有基于 Merge Request API 的行为
|
|
8
|
+
# gitlab gem 为懒加载:未安装/未配置时,只有真正操作GitLab仓库才会报错,不影响Gitee流程
|
|
9
|
+
class GitlabProvider < Hosting::Provider
|
|
10
|
+
include Singleton
|
|
11
|
+
|
|
12
|
+
attr_accessor :endpoint
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
super
|
|
16
|
+
@endpoint = nil
|
|
17
|
+
@private_token = nil
|
|
18
|
+
@ready = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def available?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 创建gitlab项目 如果不存在的话(固定创建为私有项目)
|
|
26
|
+
def create_project_if_need(project_name, http_url, private: true, description: nil)
|
|
27
|
+
unless gitlab_url?(http_url)
|
|
28
|
+
Build.log.info('不使用gitlab接口检查和创建项目')
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Build.log.detail("检查gitlab项目(#{http_url})是否存在")
|
|
33
|
+
|
|
34
|
+
# 检查是否已经存在
|
|
35
|
+
project_obj = find_project(project_name, http_url, false)
|
|
36
|
+
if project_obj
|
|
37
|
+
Build.log.info("已存在 项目ID: #{project_obj.id} 群组:#{project_obj.namespace_path} 无需新建")
|
|
38
|
+
project_obj.full_name
|
|
39
|
+
else
|
|
40
|
+
Build.log.info("不存在 需要创建项目,项目描述: #{description}")
|
|
41
|
+
do_create_project(project_name, http_url, description, private)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def find_project(pod_name, http_url, can_raise: true)
|
|
46
|
+
ensure_client
|
|
47
|
+
|
|
48
|
+
unless gitlab_url?(http_url)
|
|
49
|
+
Build.log.info('不使用gitlab接口查询项目ID')
|
|
50
|
+
return nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 搜索
|
|
54
|
+
project_obj = nil
|
|
55
|
+
projects = Gitlab.project_search(pod_name)
|
|
56
|
+
projects.each do |project|
|
|
57
|
+
project_hash = project.to_hash
|
|
58
|
+
if project.name == pod_name
|
|
59
|
+
if http_url == project_hash['http_url_to_repo'] ||
|
|
60
|
+
http_url == project_hash['ssh_url_to_repo'] ||
|
|
61
|
+
http_url == project_hash['web_url']
|
|
62
|
+
project_obj = project
|
|
63
|
+
break
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if project_obj.nil? && can_raise
|
|
69
|
+
error = "当前配置private_token检索不到#{pod_name},可能是如下原因:\n"
|
|
70
|
+
error += "1、该组件在git仓库中的名字和#{pod_name}不一致\n"
|
|
71
|
+
error += "2、当前账号在该链接下#{http_url}没有权限,注:需要Maintainer权限"
|
|
72
|
+
pusher.push('❌ 打包失败', error)
|
|
73
|
+
Process.exit(-1)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return nil unless project_obj
|
|
77
|
+
|
|
78
|
+
Hosting::RepoInfo.new(
|
|
79
|
+
id: project_obj.id,
|
|
80
|
+
full_name: project_obj.to_hash['path_with_namespace'],
|
|
81
|
+
namespace_path: project_obj.namespace.id.to_s
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# 锁死对应的分支
|
|
86
|
+
def lock_branch(repo_info, branch, page_url)
|
|
87
|
+
ensure_client
|
|
88
|
+
project_id = repo_info.id
|
|
89
|
+
|
|
90
|
+
# 先取消锁死
|
|
91
|
+
res = Gitlab.protected_branches(project_id)
|
|
92
|
+
res.each do |obj|
|
|
93
|
+
if obj.name == branch
|
|
94
|
+
Gitlab.unprotect_branch(project_id, branch)
|
|
95
|
+
break
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 锁死
|
|
100
|
+
Gitlab.protect_branch(project_id, branch, { allow_force_push: false, push_access_level: 0, merge_access_level: 0 })
|
|
101
|
+
|
|
102
|
+
"#{page_url}/-/settings/repository#js-protected-branches-settings"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 判断分支是否已经包含某次提交
|
|
106
|
+
def branch_contain_commit?(repo_info, branch, commit_sha)
|
|
107
|
+
ensure_client
|
|
108
|
+
project_id = repo_info.id
|
|
109
|
+
current_page = 1
|
|
110
|
+
per_page = 20
|
|
111
|
+
|
|
112
|
+
# 获取某个分支的提交
|
|
113
|
+
commits = Gitlab.commits(project_id, { ref_name: branch, page: current_page, per_page: per_page })
|
|
114
|
+
commits.each do |obj|
|
|
115
|
+
return true if obj.to_hash['id'] == commit_sha
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
while commits.size == per_page
|
|
119
|
+
current_page += 1
|
|
120
|
+
commits = Gitlab.repo_commits(project_id, { ref_name: branch, page: current_page, per_page: per_page })
|
|
121
|
+
commits.each do |obj|
|
|
122
|
+
return true if obj.to_hash['id'] == commit_sha
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# 通过Merge Request API合并分支(保留原GitLab实现)
|
|
130
|
+
def merge_branch(repo_module, target_branch)
|
|
131
|
+
ensure_client
|
|
132
|
+
|
|
133
|
+
Build.log.step("开始合并分支到#{target_branch}(#{File.basename(repo_module.path)})")
|
|
134
|
+
|
|
135
|
+
unless gitlab_url?(repo_module.ssh_url)
|
|
136
|
+
Build.log.info('非gitlab项目, 不支持此功能')
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
return unless check_merge_branch(repo_module, target_branch)
|
|
141
|
+
|
|
142
|
+
# 合并前需要修改合并方法为快进式 防止拿到的提交者是错误的
|
|
143
|
+
project_id = repo_module.repo_info.id
|
|
144
|
+
|
|
145
|
+
Gitlab.edit_project(project_id, { merge_method: 'ff' })
|
|
146
|
+
sleep 1
|
|
147
|
+
|
|
148
|
+
message = "[merge] #{repo_module.branch} => #{target_branch}"
|
|
149
|
+
merge_request = Gitlab.create_merge_request(project_id, message, { source_branch: repo_module.branch, target_branch: target_branch })
|
|
150
|
+
merge_url = "#{endpoint}/projects/#{project_id}/merge_requests/#{merge_request.iid}/merge"
|
|
151
|
+
Build.log.detail("合并请求的ID: #{merge_request.iid} 链接: #{merge_url}")
|
|
152
|
+
|
|
153
|
+
merge_request = Gitlab.merge_request(project_id, merge_request.iid)
|
|
154
|
+
merge_status = merge_request.to_hash['merge_status']
|
|
155
|
+
state = merge_request.to_hash['state']
|
|
156
|
+
can_be_merged = (merge_status == 'can_be_merged')
|
|
157
|
+
retry_count = 0
|
|
158
|
+
while retry_count <= 60 && !can_be_merged
|
|
159
|
+
Build.log.detail("合并请求检查中...(当前状态: #{state} #{merge_status})")
|
|
160
|
+
merge_request = Gitlab.merge_request(project_id, merge_request.iid)
|
|
161
|
+
merge_status = merge_request.to_hash['merge_status']
|
|
162
|
+
can_be_merged = (merge_status == 'can_be_merged')
|
|
163
|
+
retry_count += 1
|
|
164
|
+
sleep 2
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# 请求状态错误
|
|
168
|
+
if state != 'opened'
|
|
169
|
+
error = "自动合并失败, 合并请求状态错误:#{state} #{merge_url}"
|
|
170
|
+
pusher.push("【❌ 组件分支合并到#{target_branch}失败】", error)
|
|
171
|
+
Process.exit(-1)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# 超时
|
|
175
|
+
unless can_be_merged
|
|
176
|
+
error = "自动合并失败, 检查状态超时: #{merge_status} #{merge_url}"
|
|
177
|
+
pusher.push("【❌ 组件分支合并到#{target_branch}失败】", error)
|
|
178
|
+
Process.exit(-1)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
Build.log.detail("merge_request检查完毕,当前状态:#{state} #{merge_status}")
|
|
182
|
+
res = Gitlab.accept_merge_request(project_id, merge_request.iid, { merge_commit_message: message })
|
|
183
|
+
|
|
184
|
+
if res.state == 'merged'
|
|
185
|
+
Build.log.detail("自动合并完毕 #{repo_module.branch} => #{target_branch}")
|
|
186
|
+
pusher.push("【✅组件合并到#{target_branch}成功】")
|
|
187
|
+
else
|
|
188
|
+
error = "自动合并失败, 请检查:#{merge_url}"
|
|
189
|
+
pusher.push("【❌ 组件分支合并到#{target_branch}失败】", error)
|
|
190
|
+
Process.exit(-1)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def check_merge_branch(repo_module, target_branch)
|
|
197
|
+
unless repo_module.build_item.merge_master
|
|
198
|
+
Build.log.info('本次构建 merge_master = false 不执行自动合并')
|
|
199
|
+
return false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if target_branch == repo_module.branch
|
|
203
|
+
Build.log.warn("源分支#{repo_module.branch}和目标分支#{target_branch}指定名字一样, 无需执行合并")
|
|
204
|
+
return false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
unless repo_module.repo_info
|
|
208
|
+
error = '仓库信息为空,无法执行自动合并'
|
|
209
|
+
pusher.push('❌ 打包失败', error)
|
|
210
|
+
Process.exit(-1)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
project_id = repo_module.repo_info.id
|
|
214
|
+
current_page = 1
|
|
215
|
+
per_page = 20
|
|
216
|
+
|
|
217
|
+
# 获取目标分支的最新提交
|
|
218
|
+
target_commits = Gitlab.commits(project_id, { ref_name: target_branch, page: current_page, per_page: per_page })
|
|
219
|
+
target_last_commit = target_commits.size > 0 ? target_commits[0].to_hash['id'] : nil
|
|
220
|
+
|
|
221
|
+
return true unless target_last_commit
|
|
222
|
+
|
|
223
|
+
# 获取源分支的提交
|
|
224
|
+
source_commits = Gitlab.commits(project_id, { ref_name: repo_module.branch, page: current_page, per_page: per_page })
|
|
225
|
+
source_last_commit = source_commits.size > 0 ? source_commits[0].to_hash['id'] : nil
|
|
226
|
+
|
|
227
|
+
if target_last_commit == source_last_commit
|
|
228
|
+
Build.log.warn("源分支#{repo_module.branch}和目标分支#{target_branch}最新提交一致, 无需执行合并")
|
|
229
|
+
return false
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# 包含目标分支的提交
|
|
233
|
+
source_commits.each do |obj|
|
|
234
|
+
return true if target_last_commit == obj.to_hash['id']
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# 往前翻页 包含目标分支的提交
|
|
238
|
+
while source_commits.size == per_page
|
|
239
|
+
current_page += 1
|
|
240
|
+
source_commits = Gitlab.repo_commits(project_id, { ref_name: repo_module.branch, page: current_page, per_page: per_page })
|
|
241
|
+
source_commits.each do |obj|
|
|
242
|
+
return true if target_last_commit == obj.to_hash['id']
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
error = "当前分支#{repo_module.branch}落后于#{target_branch}, 请先手动合并分支后再打包并执行自动合并分支"
|
|
247
|
+
pusher.push('❌ 打包失败', error)
|
|
248
|
+
Process.exit(-1)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def do_create_project(pod_name, http_url, description, is_private)
|
|
252
|
+
ensure_client
|
|
253
|
+
|
|
254
|
+
# 读取路径
|
|
255
|
+
url = URI(http_url)
|
|
256
|
+
repo_path = url.path.gsub("/#{pod_name}.git", '')
|
|
257
|
+
repo_path = repo_path.gsub(pod_name.to_s, '')
|
|
258
|
+
|
|
259
|
+
# 获取群组ID
|
|
260
|
+
namespace_id = get_project_namespace_id(repo_path)
|
|
261
|
+
if namespace_id.nil?
|
|
262
|
+
error = '获取namespace_id失败, 无法创建项目'
|
|
263
|
+
pusher.push('❌ 打包失败', error)
|
|
264
|
+
Process.exit(-1)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# 可见性 private/internal(仅自托管实例支持)
|
|
268
|
+
visibility = is_private ? 'private' : 'internal'
|
|
269
|
+
Build.log.debug("创建的项目在群组ID:#{namespace_id}")
|
|
270
|
+
res = Gitlab.create_project(pod_name, {
|
|
271
|
+
description: description,
|
|
272
|
+
initialize_with_readme: true,
|
|
273
|
+
default_branch: 'master',
|
|
274
|
+
namespace_id: namespace_id,
|
|
275
|
+
'merge_method' => 'ff',
|
|
276
|
+
visibility: visibility
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
unless res.id
|
|
280
|
+
error = '创建项目失败'
|
|
281
|
+
pusher.push('❌ 打包失败', error)
|
|
282
|
+
Process.exit(-1)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
res.id
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# 根据仓库路径获取命名空间ID
|
|
289
|
+
def get_project_namespace_id(repo_path)
|
|
290
|
+
Build.log.debug("仓库命名空间路径:#{repo_path}")
|
|
291
|
+
group_name = repo_path.split('/').last
|
|
292
|
+
Build.log.debug("组名:#{group_name}")
|
|
293
|
+
groups = Gitlab.group_search(group_name)
|
|
294
|
+
groups.each do |group|
|
|
295
|
+
return group.id if repo_path.include? group.full_path
|
|
296
|
+
end
|
|
297
|
+
nil
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def gitlab_url?(http_url)
|
|
301
|
+
Hosting.host_of(http_url).to_s.downcase.include?('gitlab')
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def ensure_client
|
|
305
|
+
return if @ready
|
|
306
|
+
|
|
307
|
+
begin
|
|
308
|
+
require 'gitlab'
|
|
309
|
+
rescue LoadError
|
|
310
|
+
error = '操作GitLab仓库需要 gitlab gem,请在Gemfile中添加 gem "gitlab" 后 bundle install'
|
|
311
|
+
pusher.push('❌ 打包失败', error)
|
|
312
|
+
Process.exit(-1)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
@endpoint = read_required_config(
|
|
316
|
+
'gitlab.apiurl',
|
|
317
|
+
'未读取到配置的gitlabAPI, 请先执行命令配置:git config --global gitlab.apiurl "xxx" 示例:https://gitlab.xxx.com/api/v4'
|
|
318
|
+
)
|
|
319
|
+
@private_token = read_required_config(
|
|
320
|
+
'gitlab.privatetoken',
|
|
321
|
+
'未读取到配置的private_token, 请执行命令配置:git config --global gitlab.privatetoken "xxx"'
|
|
322
|
+
)
|
|
323
|
+
Build.log.debug("读取配置的gitlabAPI地址是:#{@endpoint}")
|
|
324
|
+
|
|
325
|
+
Gitlab.configure do |config|
|
|
326
|
+
config.endpoint = @endpoint
|
|
327
|
+
config.private_token = @private_token
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
@ready = true
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def read_required_config(key, error_message)
|
|
334
|
+
res = %x(git config --global #{key}).to_s.strip
|
|
335
|
+
if res.empty?
|
|
336
|
+
pusher.push('❌ 打包失败', error_message)
|
|
337
|
+
Process.exit(-1)
|
|
338
|
+
end
|
|
339
|
+
res
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|