pindo 5.14.4 → 5.14.5
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 +4 -4
- data/lib/pindo/client/httpclient.rb +88 -12
- data/lib/pindo/command/jps/bind.rb +284 -121
- data/lib/pindo/command/jps/media.rb +1 -15
- data/lib/pindo/module/pgyer/pgyerhelper.rb +87 -0
- data/lib/pindo/module/task/model/git/git_commit_task.rb +19 -1
- data/lib/pindo/module/task/model/jps/jps_bind_package_task.rb +217 -0
- data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +20 -21
- data/lib/pindo/module/task/model/jps/jps_workflow_message_task.rb +190 -0
- data/lib/pindo/version.rb +1 -1
- metadata +13 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60289998ea7be5f175400dbd6038a6aebe104095280ee7fe10aba1ea513d20f8
|
|
4
|
+
data.tar.gz: 1a4fddd44fa17a17716a4e55a9c3ad7df41fed6357934e2b179801779dd7a139
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99b491f10a169e0aeac7fef944d969b48bfa8520a220f3705f4762e3643515dd9e978bef2f5d519abc3970dc29ca734d41d5d6532beb61157389a92aee562ff2
|
|
7
|
+
data.tar.gz: b806779564f476de930e152265f2b36a340bd0a1dc82a7d703d8a3789f162910991a2a50d26a5c9b5ae9da719ba9b61032d9b4c58eec6ab5360305213547a700
|
|
@@ -3,13 +3,80 @@ require 'uri'
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'faraday'
|
|
5
5
|
require 'pindo/base/aeshelper'
|
|
6
|
-
require '
|
|
6
|
+
require 'typhoeus'
|
|
7
|
+
require 'typhoeus/adapters/faraday'
|
|
7
8
|
|
|
8
9
|
module Pindo
|
|
9
|
-
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# 简单的 Faraday 重试中间件(替代 faraday-retry)
|
|
12
|
+
class SimpleRetryMiddleware < Faraday::Middleware
|
|
13
|
+
# 默认幂等的 HTTP 方法(可安全重试)
|
|
14
|
+
IDEMPOTENT_METHODS = [:get, :head, :put, :delete, :options, :trace].freeze
|
|
15
|
+
# 可重试的 HTTP 状态码(5xx 服务器错误)
|
|
16
|
+
RETRIABLE_STATUS_CODES = (500..599).freeze
|
|
17
|
+
|
|
18
|
+
def initialize(app, options = {})
|
|
19
|
+
super(app)
|
|
20
|
+
@max = options[:max] || 3 # 最大重试次数(不包括首次尝试)
|
|
21
|
+
@interval = options[:interval] || 0.5
|
|
22
|
+
@backoff_factor = options[:backoff_factor] || 2
|
|
23
|
+
@interval_randomness = options[:interval_randomness] || 0.5
|
|
24
|
+
@exceptions = options[:exceptions] || [Faraday::TimeoutError, Faraday::ConnectionFailed]
|
|
25
|
+
@methods = options[:methods] || IDEMPOTENT_METHODS # 允许重试的 HTTP 方法
|
|
26
|
+
@retry_statuses = options[:retry_statuses] || RETRIABLE_STATUS_CODES
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call(env)
|
|
30
|
+
retries = 0
|
|
31
|
+
|
|
32
|
+
loop do
|
|
33
|
+
begin
|
|
34
|
+
response = @app.call(env)
|
|
35
|
+
|
|
36
|
+
# 检查 HTTP 状态码是否需要重试(5xx 错误)
|
|
37
|
+
# 同时必须满足幂等性要求,避免非幂等请求(如 POST)被意外重试
|
|
38
|
+
if retriable_response?(response) && retriable_request?(env) && retries < @max
|
|
39
|
+
retries += 1
|
|
40
|
+
sleep_with_jitter(retries)
|
|
41
|
+
next # 继续下一次循环(重试)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return response # 成功,返回响应
|
|
45
|
+
|
|
46
|
+
rescue *@exceptions => e
|
|
47
|
+
# 检查是否为幂等方法,非幂等方法不自动重试
|
|
48
|
+
if retriable_request?(env) && retries < @max
|
|
49
|
+
retries += 1
|
|
50
|
+
sleep_with_jitter(retries)
|
|
51
|
+
next # 继续下一次循环(重试)
|
|
52
|
+
else
|
|
53
|
+
raise e # 不能重试,抛出异常
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
12
60
|
|
|
61
|
+
# 检查请求是否可重试(幂等性检查)
|
|
62
|
+
def retriable_request?(env)
|
|
63
|
+
@methods.include?(env[:method])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# 检查响应是否需要重试(5xx 状态码)
|
|
67
|
+
def retriable_response?(response)
|
|
68
|
+
@retry_statuses.include?(response.status)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 指数退避 + 随机抖动
|
|
72
|
+
def sleep_with_jitter(retry_count)
|
|
73
|
+
sleep_time = @interval * (@backoff_factor ** (retry_count - 1))
|
|
74
|
+
jitter = rand * sleep_time * @interval_randomness
|
|
75
|
+
sleep(sleep_time + jitter)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class HttpClient
|
|
13
80
|
|
|
14
81
|
attr_accessor :token
|
|
15
82
|
attr_accessor :baseurl
|
|
@@ -25,23 +92,32 @@ module Pindo
|
|
|
25
92
|
}
|
|
26
93
|
|
|
27
94
|
retry_options = {
|
|
28
|
-
max: 3,
|
|
29
|
-
interval: 0.5,
|
|
30
|
-
backoff_factor: 2,
|
|
31
|
-
interval_randomness: 0.5
|
|
95
|
+
max: 3,
|
|
96
|
+
interval: 0.5,
|
|
97
|
+
backoff_factor: 2,
|
|
98
|
+
interval_randomness: 0.5
|
|
32
99
|
}
|
|
33
100
|
|
|
34
101
|
con = Faraday.new do |config|
|
|
35
|
-
|
|
36
|
-
config.
|
|
102
|
+
# 使用自定义重试中间件
|
|
103
|
+
config.use SimpleRetryMiddleware, retry_options
|
|
104
|
+
# 使用 Typhoeus 适配器(支持代理和连接池)
|
|
105
|
+
config.adapter :typhoeus
|
|
106
|
+
config.proxy = proxy_options.compact if proxy_options[:uri]
|
|
37
107
|
end
|
|
38
|
-
|
|
108
|
+
|
|
39
109
|
con
|
|
40
110
|
end
|
|
41
111
|
|
|
42
112
|
def self.create_instance()
|
|
43
|
-
|
|
44
|
-
con = Faraday.new
|
|
113
|
+
|
|
114
|
+
con = Faraday.new do |config|
|
|
115
|
+
# 使用自定义重试中间件
|
|
116
|
+
config.use SimpleRetryMiddleware, max: 3, interval: 0.5, backoff_factor: 2
|
|
117
|
+
# 使用 Typhoeus 适配器
|
|
118
|
+
config.adapter :typhoeus
|
|
119
|
+
end
|
|
120
|
+
|
|
45
121
|
con
|
|
46
122
|
end
|
|
47
123
|
|
|
@@ -1,191 +1,354 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
3
|
-
require '
|
|
1
|
+
require 'pindo/module/pgyer/pgyerhelper'
|
|
2
|
+
require 'pindo/module/task/task_manager'
|
|
3
|
+
require 'pindo/module/task/model/jps/jps_bind_package_task'
|
|
4
|
+
require 'pindo/options/options'
|
|
5
|
+
require 'pindo/options/groups/task_options'
|
|
4
6
|
|
|
5
7
|
module Pindo
|
|
6
8
|
class Command
|
|
7
9
|
class Jps < Command
|
|
8
10
|
class Bind < Jps
|
|
9
11
|
|
|
10
|
-
self.summary = '
|
|
12
|
+
self.summary = '绑定 Git commit 到已上传的项目包'
|
|
11
13
|
|
|
12
14
|
self.description = <<-DESC
|
|
13
|
-
|
|
15
|
+
绑定 Git commit 到已上传的 JPS 项目包。
|
|
14
16
|
|
|
15
|
-
支持功能:
|
|
17
|
+
支持功能:
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
* 交互式选择包类型(IPA、APK、WebGL、macOS)
|
|
20
|
+
* 支持多选,一次绑定多个包
|
|
21
|
+
* 支持选择历史 commit(使用 --select 参数)
|
|
22
|
+
* 自动关联到当前 HEAD commit 记录
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
使用示例:
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
$ pindo jps bind # 交互式选择包类型和 commit
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
$ pindo jps bind --proj=demo # 指定项目
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
$ pindo jps bind --select # 选择历史 commit
|
|
26
31
|
|
|
27
|
-
$ pindo jps bind # 绑定当前目录的包到默认工作流
|
|
28
|
-
|
|
29
|
-
$ pindo jps bind --proj=demo # 绑定指定项目到工作流
|
|
30
|
-
|
|
31
|
-
$ pindo jps bind --workflow=release # 绑定到指定的工作流
|
|
32
|
-
|
|
33
|
-
$ pindo jps bind --list # 列出所有可用的工作流
|
|
34
32
|
DESC
|
|
35
33
|
|
|
36
34
|
self.arguments = [
|
|
37
|
-
|
|
38
35
|
]
|
|
39
36
|
|
|
37
|
+
# 定义此命令使用的参数项
|
|
38
|
+
def self.option_items
|
|
39
|
+
@option_items ||= Pindo::Options::OptionGroup.merge(
|
|
40
|
+
Pindo::Options::JPSOptions.select(:proj),
|
|
41
|
+
Pindo::Options::TaskOptions.select(:select),
|
|
42
|
+
Pindo::Options::GitOptions.all
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
40
46
|
def self.options
|
|
41
|
-
|
|
42
|
-
['--proj', '指定要绑定的项目名称'],
|
|
43
|
-
['--workflow', '指定要绑定的工作流名称'],
|
|
44
|
-
['--list', '列出所有可用的工作流'],
|
|
45
|
-
['--unbind', '解除包与工作流的绑定'],
|
|
46
|
-
['--status', '查看当前绑定状态']
|
|
47
|
-
].concat(super)
|
|
47
|
+
option_items.map(&:to_claide_option).concat(super)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def initialize(argv)
|
|
51
|
-
|
|
52
|
-
@
|
|
53
|
-
@args_list_flag = argv.flag?('list', false)
|
|
54
|
-
@args_unbind_flag = argv.flag?('unbind', false)
|
|
55
|
-
@args_status_flag = argv.flag?('status', false)
|
|
51
|
+
# 使用 Options 系统解析参数
|
|
52
|
+
@options = initialize_options(argv)
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
@
|
|
54
|
+
# JPS 参数
|
|
55
|
+
@args_proj_name = @options[:proj]
|
|
56
|
+
|
|
57
|
+
# Task 参数
|
|
58
|
+
@args_select_flag = @options[:select] || false
|
|
59
|
+
|
|
60
|
+
super(argv)
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def validate!
|
|
62
64
|
super
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def run
|
|
68
|
+
project_path = Dir.pwd
|
|
69
|
+
|
|
70
|
+
# 1. 如果启用 --select 参数,获取用户选择的 commit
|
|
71
|
+
selected_commit_info = nil
|
|
72
|
+
if @args_select_flag
|
|
73
|
+
selected_commit_info = select_git_commit(project_path)
|
|
74
|
+
|
|
75
|
+
if selected_commit_info.nil?
|
|
76
|
+
puts "未选择任何提交,已取消操作"
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
puts "\n已选择提交:"
|
|
81
|
+
puts " Commit ID: #{selected_commit_info[:commit_id][0..7]}"
|
|
82
|
+
puts " 时间: #{selected_commit_info[:commit_time]}"
|
|
83
|
+
puts " 描述: #{selected_commit_info[:commit_desc]}"
|
|
84
|
+
puts ""
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 2. 获取 JPS 配置(git workflow 信息)
|
|
88
|
+
pgyer_helper = PgyerHelper.share_instace
|
|
89
|
+
pgyer_helper.login
|
|
90
|
+
|
|
91
|
+
app_info_obj, git_workflow_info = pgyer_helper.prepare_upload(
|
|
92
|
+
working_directory: project_path,
|
|
93
|
+
proj_name: @args_proj_name,
|
|
94
|
+
package_type: "",
|
|
95
|
+
manage_type: "git"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if app_info_obj.nil?
|
|
99
|
+
raise Informative, "#{@args_proj_name} 错误,请输入正确的App代号名称"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
project_id = app_info_obj["id"]
|
|
103
|
+
|
|
104
|
+
# 3. 让用户选择要绑定的包类型
|
|
105
|
+
selected_package_types = select_package_types
|
|
106
|
+
if selected_package_types.empty?
|
|
107
|
+
puts "未选择任何包类型,已取消操作"
|
|
65
108
|
return
|
|
66
109
|
end
|
|
67
110
|
|
|
68
|
-
|
|
69
|
-
|
|
111
|
+
# 4. 获取对应类型的最新包
|
|
112
|
+
app_version_list = get_latest_packages_by_types(pgyer_helper, project_id, selected_package_types)
|
|
113
|
+
|
|
114
|
+
if app_version_list.empty?
|
|
115
|
+
raise Informative, "未找到可绑定的项目包,请先上传包"
|
|
70
116
|
end
|
|
71
|
-
end
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
118
|
+
puts "\n找到以下项目包:"
|
|
119
|
+
app_version_list.each do |pkg|
|
|
120
|
+
puts " [#{pkg['nativePackageType']}] ID: #{pkg['id']}, Version: #{pkg['projectVersion']}, Build: #{pkg['build']}"
|
|
121
|
+
end
|
|
122
|
+
puts ""
|
|
123
|
+
|
|
124
|
+
# 5. 创建任务链
|
|
125
|
+
tasks = []
|
|
126
|
+
|
|
127
|
+
# Git 提交任务
|
|
128
|
+
process_type = Pindo::GitHandler.get_uncommitted_files_process_type(
|
|
129
|
+
project_dir: project_path,
|
|
130
|
+
interactive: true
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
git_commit_task = Pindo::TaskSystem::GitCommitTask.new(
|
|
134
|
+
project_path,
|
|
135
|
+
{
|
|
136
|
+
process_type: process_type,
|
|
137
|
+
commit_message: "build: jps bind 绑定前提交"
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
tasks << git_commit_task
|
|
141
|
+
|
|
142
|
+
# 创建绑定任务(一次绑定所有包)
|
|
143
|
+
bind_task = Pindo::TaskSystem::JPSBindPackageTask.new(
|
|
144
|
+
app_version_list, # 传入包列表
|
|
145
|
+
{
|
|
146
|
+
git_commit_id: selected_commit_info&.dig(:commit_id),
|
|
147
|
+
git_commit_time: selected_commit_info&.dig(:commit_time),
|
|
148
|
+
git_commit_desc: selected_commit_info&.dig(:commit_desc),
|
|
149
|
+
project_dir: project_path,
|
|
150
|
+
app_info_obj: app_info_obj,
|
|
151
|
+
workflow_info: git_workflow_info,
|
|
152
|
+
project_name: @args_proj_name
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
bind_task.dependencies << git_commit_task.id
|
|
156
|
+
tasks << bind_task
|
|
157
|
+
|
|
158
|
+
# 6. 执行任务
|
|
159
|
+
task_manager = Pindo::TaskSystem::TaskManager.instance
|
|
160
|
+
task_manager.clear_all
|
|
161
|
+
tasks.each { |task| task_manager.add_task(task) }
|
|
162
|
+
task_manager.start
|
|
163
|
+
|
|
164
|
+
# 7. 输出结果
|
|
165
|
+
report = task_manager.execution_report
|
|
166
|
+
if report[:success] > 0
|
|
167
|
+
puts "\n✅ 绑定完成! 成功绑定 #{app_version_list.size} 个包到 Git Workflow"
|
|
80
168
|
else
|
|
81
|
-
|
|
169
|
+
raise Informative, "绑定失败"
|
|
82
170
|
end
|
|
83
171
|
end
|
|
84
172
|
|
|
85
173
|
private
|
|
86
174
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
{
|
|
96
|
-
{
|
|
175
|
+
# 交互式选择包类型
|
|
176
|
+
# @return [Array<String>] 选择的包类型数组
|
|
177
|
+
def select_package_types
|
|
178
|
+
puts "\n请选择要绑定的包类型(可多选):"
|
|
179
|
+
puts "━" * 60
|
|
180
|
+
|
|
181
|
+
package_types = [
|
|
182
|
+
{ key: 'ipa', name: 'IPA (iOS)' },
|
|
183
|
+
{ key: 'apk', name: 'APK (Android)' },
|
|
184
|
+
{ key: 'webgl', name: 'WebGL (Web)' },
|
|
185
|
+
{ key: 'macos', name: 'MACOS (macOS)' }
|
|
97
186
|
]
|
|
98
187
|
|
|
99
|
-
|
|
100
|
-
puts "
|
|
188
|
+
package_types.each_with_index do |type, index|
|
|
189
|
+
puts " #{index + 1}. #{type[:name]}"
|
|
101
190
|
end
|
|
102
|
-
puts "
|
|
103
|
-
|
|
191
|
+
puts " 0. 取消操作"
|
|
192
|
+
puts "━" * 60
|
|
104
193
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
194
|
+
# 获取用户输入
|
|
195
|
+
print "\n请输入序号(多个用逗号分隔,如 1,2): "
|
|
196
|
+
input = $stdin.gets.strip
|
|
108
197
|
|
|
109
|
-
#
|
|
110
|
-
|
|
198
|
+
# 解析用户输入
|
|
199
|
+
if input == '0' || input.empty?
|
|
200
|
+
return []
|
|
201
|
+
end
|
|
111
202
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
203
|
+
selected = []
|
|
204
|
+
input.split(',').each do |num_str|
|
|
205
|
+
num = num_str.strip.to_i
|
|
206
|
+
if num > 0 && num <= package_types.size
|
|
207
|
+
selected << package_types[num - 1][:key]
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
if selected.empty?
|
|
212
|
+
puts "无效输入,未选择任何包类型"
|
|
118
213
|
else
|
|
119
|
-
puts "
|
|
214
|
+
puts "\n已选择: #{selected.join(', ')}"
|
|
120
215
|
end
|
|
121
|
-
|
|
216
|
+
|
|
217
|
+
selected
|
|
122
218
|
end
|
|
123
219
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
220
|
+
# 根据包类型获取最新的项目包列表
|
|
221
|
+
# @param pgyer_helper [PgyerHelper] JPS 客户端
|
|
222
|
+
# @param project_id [String] 项目 ID
|
|
223
|
+
# @param package_types [Array<String>] 包类型数组
|
|
224
|
+
# @return [Array<Hash>] 项目包信息列表
|
|
225
|
+
def get_latest_packages_by_types(pgyer_helper, project_id, package_types)
|
|
226
|
+
packages_to_bind = []
|
|
227
|
+
|
|
228
|
+
package_types.each do |package_type|
|
|
229
|
+
# 包类型映射:webgl -> zip
|
|
230
|
+
native_package_type = case package_type
|
|
231
|
+
when 'webgl'
|
|
232
|
+
'zip'
|
|
233
|
+
else
|
|
234
|
+
package_type
|
|
235
|
+
end
|
|
127
236
|
|
|
128
|
-
|
|
129
|
-
|
|
237
|
+
begin
|
|
238
|
+
# 获取项目包列表(只获取第一页的第一条)
|
|
239
|
+
result = pgyer_helper.instance_variable_get(:@pgyer_client).get_project_package_list(
|
|
240
|
+
projectId: project_id,
|
|
241
|
+
params: {
|
|
242
|
+
pageNo: 1,
|
|
243
|
+
pageSize: 1,
|
|
244
|
+
nativePackageType: native_package_type
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# 兼容两种响应格式
|
|
249
|
+
packages = result&.dig("data") || result&.dig("data", "packages")
|
|
250
|
+
|
|
251
|
+
if packages && packages.is_a?(Array) && packages.any?
|
|
252
|
+
pkg = packages.first
|
|
253
|
+
# 添加原始类型标记
|
|
254
|
+
pkg['originalType'] = package_type
|
|
255
|
+
packages_to_bind << pkg
|
|
256
|
+
else
|
|
257
|
+
puts " ⚠️ 未找到 #{package_type} 类型的包"
|
|
258
|
+
end
|
|
259
|
+
rescue => e
|
|
260
|
+
Funlog.instance.fancyinfo_error("获取 #{package_type} 类型的项目包失败: #{e.message}")
|
|
261
|
+
end
|
|
130
262
|
end
|
|
131
263
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
puts " 工作流: #{workflow_name}"
|
|
264
|
+
packages_to_bind
|
|
265
|
+
end
|
|
135
266
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
#
|
|
267
|
+
# 交互式选择 git commit
|
|
268
|
+
# @param project_path [String] 项目路径
|
|
269
|
+
# @return [Hash, nil] 包含 :commit_id, :commit_time, :commit_desc,或 nil(取消)
|
|
270
|
+
def select_git_commit(project_path)
|
|
271
|
+
# 1. 查找 git 根目录
|
|
272
|
+
git_root = Pindo::GitHandler.git_root_directory(local_repo_dir: project_path)
|
|
273
|
+
unless git_root
|
|
274
|
+
raise Informative, "当前目录不是 git 仓库"
|
|
275
|
+
end
|
|
141
276
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
277
|
+
# 2. 获取最近 15 条 commit 历史
|
|
278
|
+
all_commits = get_recent_commits(git_root, limit: 15)
|
|
279
|
+
|
|
280
|
+
if all_commits.empty?
|
|
281
|
+
raise Informative, "未找到 git commit 历史"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# 3. 筛选符合规范的提交(以 feat:, fix: 等开头)
|
|
285
|
+
valid_prefixes = ['feat:', 'fix:', 'docs:', 'style:', 'refactor:', 'perf:', 'test:', 'build:', 'ci:', 'chore:', 'revert:']
|
|
286
|
+
commits = all_commits.select do |commit|
|
|
287
|
+
desc = commit[:commit_desc].to_s.strip
|
|
288
|
+
valid_prefixes.any? { |prefix| desc.downcase.start_with?(prefix) }
|
|
289
|
+
end
|
|
146
290
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
291
|
+
# 如果没有符合规范的提交,提示用户并使用全部提交
|
|
292
|
+
if commits.empty?
|
|
293
|
+
puts "\n警告:在最近15个提交中未找到符合规范的提交(feat:, fix: 等开头)"
|
|
294
|
+
puts "显示所有提交供您选择:"
|
|
295
|
+
commits = all_commits
|
|
296
|
+
else
|
|
297
|
+
puts "\n从最近15个提交中筛选出 #{commits.size} 个符合规范的提交:"
|
|
298
|
+
end
|
|
150
299
|
|
|
151
|
-
|
|
152
|
-
|
|
300
|
+
# 4. 显示 commit 列表
|
|
301
|
+
puts "\n请选择要绑定的 git 提交:"
|
|
302
|
+
puts "━" * 80
|
|
303
|
+
commits.each_with_index do |commit, index|
|
|
304
|
+
puts "#{index + 1}. [#{commit[:commit_id][0..7]}] #{commit[:commit_time]} - #{commit[:commit_desc]}"
|
|
153
305
|
end
|
|
306
|
+
puts "0. 取消操作"
|
|
307
|
+
puts "━" * 80
|
|
308
|
+
|
|
309
|
+
# 5. 获取用户选择
|
|
310
|
+
choice = nil
|
|
311
|
+
loop do
|
|
312
|
+
print "\n请输入序号 (0-#{commits.size}): "
|
|
313
|
+
input = $stdin.gets.strip
|
|
154
314
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
puts " 工作流: #{workflow_name}"
|
|
315
|
+
choice = input.to_i
|
|
316
|
+
break if choice >= 0 && choice <= commits.size
|
|
158
317
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# project: proj_name,
|
|
162
|
-
# workflow: workflow_name
|
|
163
|
-
# )
|
|
318
|
+
puts "无效输入,请输入 0-#{commits.size} 之间的数字"
|
|
319
|
+
end
|
|
164
320
|
|
|
165
|
-
|
|
166
|
-
|
|
321
|
+
# 6. 返回选择的 commit(0 表示取消)
|
|
322
|
+
choice == 0 ? nil : commits[choice - 1]
|
|
167
323
|
end
|
|
168
324
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
325
|
+
# 获取最近的 git commits
|
|
326
|
+
# @param git_root [String] git 根目录
|
|
327
|
+
# @param limit [Integer] 获取的 commit 数量
|
|
328
|
+
# @return [Array<Hash>] commit 列表,每个元素包含 :commit_id, :commit_time, :commit_desc
|
|
329
|
+
def get_recent_commits(git_root, limit: 20)
|
|
330
|
+
Dir.chdir(git_root) do
|
|
331
|
+
# 使用 git log 获取 commit 历史
|
|
332
|
+
# 格式:commit_id|commit_time|commit_desc
|
|
333
|
+
output = `git log -#{limit} --format="%H|%ci|%s" 2>/dev/null`.strip
|
|
334
|
+
|
|
335
|
+
return [] if output.empty?
|
|
336
|
+
|
|
337
|
+
output.split("\n").map do |line|
|
|
338
|
+
parts = line.split('|', 3)
|
|
339
|
+
{
|
|
340
|
+
commit_id: parts[0],
|
|
341
|
+
commit_time: parts[1],
|
|
342
|
+
commit_desc: parts[2]
|
|
343
|
+
}
|
|
181
344
|
end
|
|
182
345
|
end
|
|
183
|
-
|
|
184
|
-
#
|
|
185
|
-
|
|
346
|
+
rescue => e
|
|
347
|
+
Funlog.instance.fancyinfo_error("获取 git commit 历史失败: #{e.message}")
|
|
348
|
+
[]
|
|
186
349
|
end
|
|
187
350
|
|
|
188
351
|
end
|
|
189
352
|
end
|
|
190
353
|
end
|
|
191
|
-
end
|
|
354
|
+
end
|
|
@@ -174,7 +174,7 @@ module Pindo
|
|
|
174
174
|
# @return [Hash, nil] 包含 :commit_id, :commit_time, :commit_desc,或 nil(取消)
|
|
175
175
|
def select_git_commit(project_path)
|
|
176
176
|
# 1. 查找 git 根目录
|
|
177
|
-
git_root =
|
|
177
|
+
git_root = Pindo::GitHandler.git_root_directory(local_repo_dir: project_path)
|
|
178
178
|
unless git_root
|
|
179
179
|
raise Informative, "当前目录不是 git 仓库"
|
|
180
180
|
end
|
|
@@ -227,20 +227,6 @@ module Pindo
|
|
|
227
227
|
choice == 0 ? nil : commits[choice - 1]
|
|
228
228
|
end
|
|
229
229
|
|
|
230
|
-
# 查找 git 根目录
|
|
231
|
-
# @param path [String] 起始路径
|
|
232
|
-
# @return [String, nil] git 根目录,未找到返回 nil
|
|
233
|
-
def find_git_root(path)
|
|
234
|
-
current = File.expand_path(path)
|
|
235
|
-
while current != '/'
|
|
236
|
-
if File.exist?(File.join(current, '.git'))
|
|
237
|
-
return current
|
|
238
|
-
end
|
|
239
|
-
current = File.dirname(current)
|
|
240
|
-
end
|
|
241
|
-
nil
|
|
242
|
-
end
|
|
243
|
-
|
|
244
230
|
# 获取最近的 git commits
|
|
245
231
|
# @param git_root [String] git 根目录
|
|
246
232
|
# @param limit [Integer] 获取的 commit 数量
|