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,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require_relative '../push'
|
|
5
|
+
|
|
6
|
+
module Build
|
|
7
|
+
module Hosting
|
|
8
|
+
# 代码托管平台的仓库描述
|
|
9
|
+
# @!attribute id Gitee无数字ID概念,该字段仅GitLab使用
|
|
10
|
+
# @!attribute full_name 平台上的完整仓库路径,如 xxx/YYModel
|
|
11
|
+
# @!attribute namespace_path 命名空间路径,如 xxx
|
|
12
|
+
RepoInfo = Struct.new(:id, :full_name, :namespace_path, keyword_init: true)
|
|
13
|
+
|
|
14
|
+
# 代码托管平台抽象层
|
|
15
|
+
# GitLab/Gitee/不支持平台各自实现该接口,业务层不感知具体平台
|
|
16
|
+
class Provider
|
|
17
|
+
# @return [Push] 推送能力
|
|
18
|
+
def pusher
|
|
19
|
+
@pusher || Push.instance
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_writer :pusher
|
|
23
|
+
|
|
24
|
+
# @return [Boolean] 当前平台的API能力是否可用
|
|
25
|
+
def available?
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 检查仓库是否存在,不存在则自动创建
|
|
30
|
+
# @param project_name [String] 组件/仓库名
|
|
31
|
+
# @param http_url [String] 仓库http地址
|
|
32
|
+
# @param private [Boolean] 是否私有仓库,固定默认私有
|
|
33
|
+
# @param description [String] 仓库描述
|
|
34
|
+
def create_project_if_need(project_name, http_url, private: true, description: nil)
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 查询仓库信息
|
|
39
|
+
# @return [Build::Hosting::RepoInfo, nil]
|
|
40
|
+
def find_project(pod_name, http_url, can_raise: true)
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 锁死(保护)发布分支
|
|
45
|
+
# @param repo_info [Build::Hosting::RepoInfo]
|
|
46
|
+
# @param branch [String] 分支名
|
|
47
|
+
# @param page_url [String] 仓库网页地址
|
|
48
|
+
# @return [String] 分支保护设置页地址
|
|
49
|
+
def lock_branch(repo_info, branch, page_url)
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 判断分支是否包含某次提交
|
|
54
|
+
# @return [Boolean]
|
|
55
|
+
def branch_contain_commit?(repo_info, branch, commit_sha)
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 合并组件分支到目标分支
|
|
60
|
+
def merge_branch(repo_module, target_branch)
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 不支持的平台(非Gitee/GitLab),所有平台能力静默跳过
|
|
66
|
+
class NullProvider < Provider
|
|
67
|
+
include Singleton
|
|
68
|
+
|
|
69
|
+
def available?
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create_project_if_need(project_name, http_url, private: true, description: nil)
|
|
74
|
+
Build.log.info("非受支持的代码托管平台(#{http_url}),跳过仓库检查和创建")
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def find_project(pod_name, http_url, can_raise: true)
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def lock_branch(repo_info, branch, page_url)
|
|
83
|
+
Build.log.info("非受支持的代码托管平台,跳过分支锁死: #{branch}")
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def branch_contain_commit?(repo_info, branch, commit_sha)
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def merge_branch(repo_module, target_branch)
|
|
92
|
+
Build.log.info("非受支持的代码托管平台,不支持自动合并分支到#{target_branch}")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/core/hosting.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require_relative 'hosting/provider'
|
|
5
|
+
require_relative 'hosting/gitee_client'
|
|
6
|
+
require_relative 'hosting/gitee_provider'
|
|
7
|
+
require_relative 'hosting/gitlab_provider'
|
|
8
|
+
|
|
9
|
+
module Build
|
|
10
|
+
# 代码托管平台工厂:根据仓库地址选择对应的平台Provider
|
|
11
|
+
module Hosting
|
|
12
|
+
# 从ssh(scp-like)或http地址中解析host
|
|
13
|
+
# git@gitee.com:owner/repo.git -> gitee.com
|
|
14
|
+
# https://gitee.com/owner/repo -> gitee.com
|
|
15
|
+
def self.host_of(url)
|
|
16
|
+
url = url.to_s
|
|
17
|
+
host =
|
|
18
|
+
if url.start_with?('git@')
|
|
19
|
+
url.split('@', 2).last.split(':', 2).first
|
|
20
|
+
else
|
|
21
|
+
begin
|
|
22
|
+
URI(url).host
|
|
23
|
+
rescue URI::Error
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
host.to_s.downcase
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param url [String] 仓库ssh或http地址
|
|
31
|
+
# @return [Build::Hosting::Provider]
|
|
32
|
+
def self.provider_for(url)
|
|
33
|
+
host = host_of(url)
|
|
34
|
+
if host.include?('gitee')
|
|
35
|
+
GiteeProvider.instance
|
|
36
|
+
elsif host.include?('gitlab')
|
|
37
|
+
GitlabProvider.instance
|
|
38
|
+
else
|
|
39
|
+
NullProvider.instance
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/core/logger.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Build
|
|
4
|
+
# 统一构建日志,替代散落各处的 puts。
|
|
5
|
+
#
|
|
6
|
+
# 级别:
|
|
7
|
+
# step 阶段标题(⏩,前置空行),如 clone仓库、pod update、开始编译
|
|
8
|
+
# info 普通信息
|
|
9
|
+
# detail 细节说明/阶段性完成(-> 前缀);流程内各步骤完成统一用此级别
|
|
10
|
+
# success 成功提示(✅);仅用于整条命令/流程的最终关键结果,阶段步骤不要用,避免满屏✅
|
|
11
|
+
# warn 警告(⚠️,输出到 stderr,不中断流程)
|
|
12
|
+
# error 错误(❌,输出到 stderr;是否退出由调用方决定)
|
|
13
|
+
# debug 调试细节(命令回显/响应体等),仅 --verbose 或环境变量开启时输出
|
|
14
|
+
class Logger
|
|
15
|
+
DEBUG_ENV = 'POD_BINARY_PACK_VERBOSE'
|
|
16
|
+
|
|
17
|
+
attr_writer :debug_enabled
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@debug_enabled = !ENV.fetch(DEBUG_ENV, '').strip.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def debug_enabled?
|
|
24
|
+
!!@debug_enabled
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def step(message)
|
|
28
|
+
write($stdout, "\n⏩ #{message}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def info(message = '')
|
|
32
|
+
write($stdout, message.to_s)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def detail(message)
|
|
36
|
+
write($stdout, "-> #{message}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def success(message)
|
|
40
|
+
write($stdout, "✅ #{message}")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def warn(message)
|
|
44
|
+
write($stderr, "⚠️ #{message}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def error(message)
|
|
48
|
+
write($stderr, "❌ #{message}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def debug(message)
|
|
52
|
+
write($stdout, "· #{message}") if debug_enabled?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def write(io, message)
|
|
58
|
+
io.puts(message)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Build::Logger] 全局构建日志实例
|
|
63
|
+
def self.log
|
|
64
|
+
@log ||= Logger.new
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/core/podspec.rb
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Build
|
|
6
|
+
class Podspec
|
|
7
|
+
# @return [String] podspec文件路径
|
|
8
|
+
attr_accessor :path
|
|
9
|
+
|
|
10
|
+
# @return [JSON] 更新后的内容
|
|
11
|
+
attr_accessor :podspec_content
|
|
12
|
+
|
|
13
|
+
def initialize(path)
|
|
14
|
+
@path = path
|
|
15
|
+
@podspec_content = update_need_framework
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
# 添加必要的framework
|
|
20
|
+
def update_need_framework
|
|
21
|
+
code_path = File.dirname(self.path)
|
|
22
|
+
|
|
23
|
+
json = File.read(self.path)
|
|
24
|
+
podspec_content = JSON.parse(json)
|
|
25
|
+
|
|
26
|
+
need_adds = []
|
|
27
|
+
|
|
28
|
+
framework1 = "CoreServices"
|
|
29
|
+
contain1 = code_contain_framework(code_path, framework1)
|
|
30
|
+
if contain1
|
|
31
|
+
need_adds << framework1
|
|
32
|
+
Build.log.warn("源码podspec缺少依赖:s.frameworks '#{framework1}', 已为自动添加")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# 遍历添加
|
|
36
|
+
need_adds.each do |framework|
|
|
37
|
+
unless podspec_content["frameworks"]
|
|
38
|
+
podspec_content["frameworks"] = []
|
|
39
|
+
podspec_content["frameworks"] << framework
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if podspec_content["frameworks"].class == String
|
|
43
|
+
origin = podspec_content["frameworks"]
|
|
44
|
+
if origin != framework
|
|
45
|
+
podspec_content["frameworks"] = [origin]
|
|
46
|
+
podspec_content["frameworks"] << framework
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if podspec_content["frameworks"].class == Array
|
|
51
|
+
origin = podspec_content["frameworks"]
|
|
52
|
+
unless origin.include?(framework)
|
|
53
|
+
podspec_content["frameworks"] << framework
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
podspec_content
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def code_contain_framework(code_path, framework)
|
|
63
|
+
files = Dir["#{code_path}/**/*"]
|
|
64
|
+
|
|
65
|
+
# 组件里所有依赖的其他组件
|
|
66
|
+
all_reference_pods = []
|
|
67
|
+
files.each do |file_path_str|
|
|
68
|
+
file_path = Pathname(file_path_str)
|
|
69
|
+
extend_name = file_path.extname
|
|
70
|
+
if not extend_name == ".h" and
|
|
71
|
+
not extend_name == ".m" and
|
|
72
|
+
not extend_name == ".mm" and
|
|
73
|
+
not extend_name == ".pch" and
|
|
74
|
+
not extend_name == ".c" and
|
|
75
|
+
not extend_name == ".cc" and
|
|
76
|
+
not extend_name == ".hpp" and
|
|
77
|
+
not extend_name == ".cpp"
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
reference_pods = pod_in_file(file_path)
|
|
81
|
+
all_reference_pods = all_reference_pods | reference_pods
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
all_reference_pods.include?(framework)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 获取某个文件里引用的依赖库名字
|
|
88
|
+
# @param file_path 文件路径
|
|
89
|
+
# @return reference_pods
|
|
90
|
+
def pod_in_file(file_path)
|
|
91
|
+
reference_pods = []
|
|
92
|
+
if File.directory?(file_path)
|
|
93
|
+
return reference_pods
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
safe_readlines(file_path).each { |line|
|
|
97
|
+
# 兼容 #import <A/B.h> 与 #import<A/B.h>(无空格)两种写法,
|
|
98
|
+
# 无空格写法漏识别会导致framework依赖检测遗漏
|
|
99
|
+
match = line.match(/\A#\s*import\s*<([^>]+)>/)
|
|
100
|
+
next unless match
|
|
101
|
+
|
|
102
|
+
import_path = match[1]
|
|
103
|
+
next unless import_path.include?("/")
|
|
104
|
+
|
|
105
|
+
# 取第一段作为组件名,处理 #import <A/B/C.h> 多级子模块的情况
|
|
106
|
+
real_module_name = import_path.split("/").first
|
|
107
|
+
|
|
108
|
+
unless reference_pods.include?(real_module_name)
|
|
109
|
+
reference_pods << real_module_name
|
|
110
|
+
end
|
|
111
|
+
}
|
|
112
|
+
reference_pods
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# 容错读取源码行:历史组件源码可能存在GBK等非UTF-8文件。
|
|
116
|
+
# 直接IO.readlines按默认编码读,后续start_with?/match遇到非法字节会抛
|
|
117
|
+
# invalid byte sequence中断整个打包流程;先按二进制读取再显式做一次
|
|
118
|
+
# 同编码转换替换非法字节
|
|
119
|
+
def safe_readlines(file_path)
|
|
120
|
+
content = File.read(file_path, mode: 'rb')
|
|
121
|
+
content.encode!('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
122
|
+
content.lines
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/core/push.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'config'
|
|
4
|
+
require_relative 'repo_module'
|
|
5
|
+
require_relative 'repo_xcode_package'
|
|
6
|
+
require_relative 'repo_xcode_shell'
|
|
7
|
+
require 'singleton'
|
|
8
|
+
|
|
9
|
+
module Build
|
|
10
|
+
class Push
|
|
11
|
+
include Singleton
|
|
12
|
+
|
|
13
|
+
# @return [Object] 消息发送器
|
|
14
|
+
attr_accessor :sender
|
|
15
|
+
|
|
16
|
+
# @return [RepoModule] 打包的组件
|
|
17
|
+
attr_accessor :repo_module
|
|
18
|
+
|
|
19
|
+
# @return [RepoXCodeShell] 壳工程(即打包工程)
|
|
20
|
+
attr_accessor :repo_xcode_shell
|
|
21
|
+
|
|
22
|
+
def setup(sender = nil, repo_module = nil, repo_xcode_shell = nil)
|
|
23
|
+
@sender = sender
|
|
24
|
+
@repo_module = repo_module
|
|
25
|
+
@repo_xcode_shell = repo_xcode_shell
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 发送推送消息
|
|
30
|
+
# @param title [String] 推送标题
|
|
31
|
+
# @param summary [String] 推送简要描述
|
|
32
|
+
def push(title, summary = nil)
|
|
33
|
+
|
|
34
|
+
# 未初始化时 拿不到推送给谁 就不推 只打印
|
|
35
|
+
if not repo_module or not repo_module.last_commit.committer
|
|
36
|
+
Build.log.warn("未获取到仓库信息或提交者信息,不推送以下内容:")
|
|
37
|
+
Build.log.info(title)
|
|
38
|
+
Build.log.info(summary.to_s)
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
to = repo_module.last_commit.committer
|
|
43
|
+
content = create_push_content(summary)
|
|
44
|
+
|
|
45
|
+
Build.log.step("开始推送通知流程")
|
|
46
|
+
log_content = "#{title}\n#{content}\n"
|
|
47
|
+
|
|
48
|
+
# 发送渠道
|
|
49
|
+
Build.log.debug("即将推送给#{to}如下内容:\n#{log_content}")
|
|
50
|
+
if @sender
|
|
51
|
+
@sender.send_msg(to.upcase, title, content.to_s)
|
|
52
|
+
Build.log.detail("推送通知完毕")
|
|
53
|
+
else
|
|
54
|
+
Build.log.warn("未提供推送配置,不进行推送")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_push_content(summary = nil)
|
|
60
|
+
|
|
61
|
+
shell_http_url = repo_xcode_shell ? repo_xcode_shell.http_url : "null"
|
|
62
|
+
shell_branch = repo_xcode_shell ? repo_xcode_shell.branch : "null"
|
|
63
|
+
build_url = Build::Config.instance.build_url
|
|
64
|
+
|
|
65
|
+
content = ""
|
|
66
|
+
content += "🔹组件: #{repo_module.pod_name}\n"
|
|
67
|
+
content += "🔹版本: #{repo_module.pod_version_final}\n"
|
|
68
|
+
content += "🔹分支: #{repo_module.branch}\n"
|
|
69
|
+
content += "🔹提交: #{repo_module.last_commit.commit_msg}\n"
|
|
70
|
+
content += "🔹组件 地址: #{repo_module.http_url}\n"
|
|
71
|
+
content += "🔹壳工程地址: #{shell_http_url}\n"
|
|
72
|
+
content += "🔹壳工程分支: #{shell_branch}\n"
|
|
73
|
+
content += "🔹构建地址: #{build_url}\n"
|
|
74
|
+
content += summary ? "🔸摘要: #{summary}\n\n" : "\n"
|
|
75
|
+
content += "🔹#{Time.new.strftime("%Y-%m-%d %H:%M:%S")}\n"
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/core/repo.rb
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'git'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require_relative 'hosting'
|
|
5
|
+
require_relative 'commit'
|
|
6
|
+
require_relative 'push'
|
|
7
|
+
require_relative 'git_helper'
|
|
8
|
+
require_relative 'util/file_helper'
|
|
9
|
+
|
|
10
|
+
module Build
|
|
11
|
+
class Repo
|
|
12
|
+
attr_reader :page_url
|
|
13
|
+
attr_reader :http_url
|
|
14
|
+
attr_reader :ssh_url
|
|
15
|
+
attr_reader :branch
|
|
16
|
+
|
|
17
|
+
# @return [Build::Hosting::Provider] 当前仓库对应的代码托管平台能力
|
|
18
|
+
def hosting
|
|
19
|
+
@hosting ||= Hosting.provider_for(ssh_url)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_writer :hosting
|
|
23
|
+
|
|
24
|
+
# @return [Push] 推送能力
|
|
25
|
+
attr_accessor :pusher
|
|
26
|
+
|
|
27
|
+
# @return [String] 仓库提交时的用户名
|
|
28
|
+
attr_accessor :user_name
|
|
29
|
+
|
|
30
|
+
# @return [String] 仓库提交时的邮箱
|
|
31
|
+
attr_accessor :user_email
|
|
32
|
+
|
|
33
|
+
# @return [Pathname] 仓库所在本机的路径
|
|
34
|
+
attr_accessor :repo_path
|
|
35
|
+
|
|
36
|
+
# @return [String] 仓库所在本机的路径
|
|
37
|
+
attr_accessor :path
|
|
38
|
+
|
|
39
|
+
# @return [Commit] 最新的提交信息
|
|
40
|
+
attr_accessor :last_commit
|
|
41
|
+
|
|
42
|
+
# @return [Integer] 当前仓库的大小 KB
|
|
43
|
+
attr_accessor :size
|
|
44
|
+
|
|
45
|
+
# @return [String] 当前仓库的大小
|
|
46
|
+
attr_accessor :size_format
|
|
47
|
+
|
|
48
|
+
def initialize(path, ssh_url, branch = nil)
|
|
49
|
+
@ssh_url = ssh_url.to_s
|
|
50
|
+
@branch = branch.to_s
|
|
51
|
+
@path = path.to_s
|
|
52
|
+
@repo_path = Pathname.new(path.to_s)
|
|
53
|
+
@user_name = "jenkins"
|
|
54
|
+
@user_email = "jenkins@jenkins.com"
|
|
55
|
+
@last_commit = Commit.new
|
|
56
|
+
@pusher = Push.instance
|
|
57
|
+
|
|
58
|
+
__validate
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
public
|
|
62
|
+
# 根据url去clone
|
|
63
|
+
# @param only_head: only_head
|
|
64
|
+
def git_clone(only_head)
|
|
65
|
+
Build.log.info("(#{File.basename(self.path)})下载[#{self.ssh_url}] [#{self.branch}] [#{only_head ? "深度1" : "全量"}]")
|
|
66
|
+
|
|
67
|
+
# clone(git gem 5.x: depth/branch 均为 clone 的直接选项,不再支持嵌套 options:)
|
|
68
|
+
clone_options = {}
|
|
69
|
+
clone_options[:branch] = self.branch unless self.branch.to_s.empty?
|
|
70
|
+
clone_options[:depth] = 1 if only_head
|
|
71
|
+
begin
|
|
72
|
+
Git.clone(self.ssh_url, self.repo_path.to_s, **clone_options)
|
|
73
|
+
rescue Git::FailedError => e
|
|
74
|
+
# 常见克隆失败转成可读提示,避免直接抛git原始堆栈;
|
|
75
|
+
# git gem 5.x: e.result 为 CommandLine::Result,stderr 取不到时回退错误消息全文
|
|
76
|
+
stderr = if e.respond_to?(:result) && e.result.respond_to?(:stderr)
|
|
77
|
+
e.result.stderr.to_s
|
|
78
|
+
else
|
|
79
|
+
e.message.to_s
|
|
80
|
+
end
|
|
81
|
+
friendly =
|
|
82
|
+
if stderr.include?('not found in upstream origin')
|
|
83
|
+
"分支/tag '#{self.branch}' 在远端仓库中不存在,请检查分支名是否正确(区分大小写),或先在远端创建该分支/tag"
|
|
84
|
+
elsif stderr.include?('Repository not found') || stderr.include?('Permission denied') ||
|
|
85
|
+
stderr.include?('Could not read from remote')
|
|
86
|
+
"仓库不存在或当前账号无访问权限,请检查仓库地址与SSH公钥配置"
|
|
87
|
+
end
|
|
88
|
+
fatal_line = stderr.lines.find { |line| line.strip.start_with?('fatal:') }.to_s.strip
|
|
89
|
+
error = +"克隆仓库失败: #{self.ssh_url} (分支/tag: #{self.branch.to_s.empty? ? '默认' : self.branch})"
|
|
90
|
+
error << "\n-> #{friendly}" if friendly
|
|
91
|
+
error << "\n-> #{fatal_line}" unless fatal_line.empty?
|
|
92
|
+
pusher.push("❌ 打包失败", error)
|
|
93
|
+
Build.log.error(error)
|
|
94
|
+
Process.exit(-1)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
repo = Git.open(self.repo_path)
|
|
98
|
+
repo.config_set('user.name', user_name)
|
|
99
|
+
repo.config_set('user.email', user_email)
|
|
100
|
+
|
|
101
|
+
Build.log.detail("下载完成")
|
|
102
|
+
|
|
103
|
+
# 打印最近提交记录(git gem 5.x: repo.log.execute)
|
|
104
|
+
log = repo.log(30).execute.first
|
|
105
|
+
|
|
106
|
+
if log
|
|
107
|
+
committer = log.committer
|
|
108
|
+
Build.log.detail("最近提交记录: [#{log.sha}] [#{log.message}] [#{committer.date}] [#{committer.name}] [#{committer.email}]")
|
|
109
|
+
|
|
110
|
+
last_commit.commit_id = log.sha
|
|
111
|
+
last_commit.commit_msg = log.message
|
|
112
|
+
last_commit.committer = committer.name
|
|
113
|
+
last_commit.commit_mail = committer.email
|
|
114
|
+
last_commit.commit_time = committer.date.strftime("%Y-%m-%d %H:%M:%S")
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 获取是否有变更
|
|
120
|
+
def git_status
|
|
121
|
+
GitHelper.status(self.path)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 拉代码
|
|
125
|
+
def git_pull
|
|
126
|
+
GitHelper.pull(self.path)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# 添加到暂存区
|
|
130
|
+
def git_add_stag
|
|
131
|
+
GitHelper.add_stag(self.path)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# 仅暂存指定文件
|
|
135
|
+
def git_add_files(files)
|
|
136
|
+
GitHelper.add_files(self.path, files)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# 指定文件是否有改动
|
|
140
|
+
def git_file_changed?(file)
|
|
141
|
+
GitHelper.file_changed?(self.path, file)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# 提交代码
|
|
145
|
+
def git_commit(commit_msg = nil)
|
|
146
|
+
GitHelper.commit(self.path, commit_msg)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def git_push
|
|
150
|
+
GitHelper.push(self.path, self.branch)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def git_push_f
|
|
154
|
+
GitHelper.push_f(self.path, self.branch)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def git_push_tags
|
|
158
|
+
GitHelper.push_tags(self.path)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# 重置仓库
|
|
162
|
+
def git_reset_hard
|
|
163
|
+
GitHelper.reset_hard(self.path)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# 查询变更
|
|
167
|
+
def git_fetch
|
|
168
|
+
GitHelper.fetch(self.path)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# 检出分支
|
|
172
|
+
def git_checkout(checkout_branch)
|
|
173
|
+
GitHelper.checkout(self.path, checkout_branch)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# 删除本地标签
|
|
177
|
+
def git_tag_delete_local(tag)
|
|
178
|
+
GitHelper.tag_delete_local(self.path, tag)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# 删除远端标签
|
|
182
|
+
def git_tag_delete_origin(tag)
|
|
183
|
+
GitHelper.tag_delete_origin(path, tag)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# 本地仓库打标签
|
|
187
|
+
def git_tag_local(tag)
|
|
188
|
+
GitHelper.tag_local(self.path, tag)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# 远程仓库打标签
|
|
192
|
+
def git_tag_origin(tag)
|
|
193
|
+
GitHelper.tag_origin(self.path, tag)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# 丢弃修改并拉取代码
|
|
197
|
+
def discard_pull
|
|
198
|
+
current = Dir.pwd
|
|
199
|
+
Dir.chdir(self.path)
|
|
200
|
+
Build.log.info("丢弃修改并拉取代码")
|
|
201
|
+
git_add_stag
|
|
202
|
+
git_reset_hard
|
|
203
|
+
git_fetch
|
|
204
|
+
git_checkout(branch)
|
|
205
|
+
git_pull
|
|
206
|
+
Dir.chdir(current)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# 一条龙服务
|
|
210
|
+
# @return [Boolean] 本次是否真的产生并推送了新提交(无变更返回false)
|
|
211
|
+
def push_code
|
|
212
|
+
Build.log.step("推送产物(#{File.basename(self.path)})")
|
|
213
|
+
|
|
214
|
+
git_pull
|
|
215
|
+
if git_status
|
|
216
|
+
git_add_stag
|
|
217
|
+
git_commit(nil)
|
|
218
|
+
git_push
|
|
219
|
+
true
|
|
220
|
+
else
|
|
221
|
+
false
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# 清空仓库(保留.git及隐藏文件,与原ls行为一致;Ruby原生实现避免空格文件名被shell切分)
|
|
226
|
+
def clear
|
|
227
|
+
current = Dir.pwd
|
|
228
|
+
Dir.chdir(self.path)
|
|
229
|
+
Build.log.debug("清空仓库(#{File.basename(self.path)})")
|
|
230
|
+
Dir.children('.').each do |entry|
|
|
231
|
+
next if entry.start_with?('.')
|
|
232
|
+
FileUtils.rm_rf(entry)
|
|
233
|
+
end
|
|
234
|
+
Dir.chdir(current)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def page_url
|
|
238
|
+
http_url.gsub(".git","")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def http_url
|
|
242
|
+
if ssh_url.start_with?"git@"
|
|
243
|
+
repo_url = ssh_url.gsub(":","/")
|
|
244
|
+
repo_url = repo_url.gsub("git@","http://")
|
|
245
|
+
return repo_url
|
|
246
|
+
end
|
|
247
|
+
ssh_url
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def size
|
|
251
|
+
file_array = Array.new
|
|
252
|
+
FileHelper.recursion_files(self.path, file_array)
|
|
253
|
+
|
|
254
|
+
size = 0
|
|
255
|
+
file_array.each { |file|
|
|
256
|
+
size += File.size(file)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@size = size*0.001
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def size_format
|
|
263
|
+
self.size > 1024 ? "#{(self.size * 0.001).round(1).to_s}MB": "#{self.size.round(0).to_s}KB"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
# 参数校验
|
|
268
|
+
def __validate
|
|
269
|
+
if ssh_url.to_s.empty? or not ssh_url.to_s.start_with?("git@")
|
|
270
|
+
error = "repo初始化参数ssh_url必须不能为空, 且是ssh地址"
|
|
271
|
+
pusher.push("❌ 打包失败", error)
|
|
272
|
+
Process.exit(-1)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if path.to_s.empty?
|
|
276
|
+
error = "目标路径为空, 请先设置路径"
|
|
277
|
+
pusher.push("❌ 打包失败", error)
|
|
278
|
+
Process.exit(-1)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
end
|
|
284
|
+
end
|