pindo 5.18.17 → 5.19.1
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/base/git_handler.rb +44 -83
- data/lib/pindo/command/appstore/adhocbuild.rb +13 -2
- data/lib/pindo/command/appstore/autobuild.rb +2 -1
- data/lib/pindo/command/appstore/iap.rb +58 -54
- data/lib/pindo/command/repo/create.rb +1 -1
- data/lib/pindo/command/repo/search.rb +1 -1
- data/lib/pindo/command/utils/installskills.rb +2 -2
- data/lib/pindo/command/utils/renewproj.rb +0 -1
- data/lib/pindo/config/build_info_manager.rb +31 -0
- data/lib/pindo/config/ios_config_parser.rb +212 -140
- data/lib/pindo/module/android/android_build_helper.rb +39 -16
- data/lib/pindo/module/android/gradle_helper.rb +8 -14
- data/lib/pindo/module/appstore/appstore_in_app_purchase.rb +80 -56
- data/lib/pindo/module/cert/cert_helper.rb +2 -2
- data/lib/pindo/module/pgyer/pgyerhelper.rb +18 -20
- data/lib/pindo/module/task/model/build/android_build_dev_task.rb +1 -37
- data/lib/pindo/module/task/model/build/android_build_task.rb +40 -0
- data/lib/pindo/module/task/model/build/ios_build_adhoc_task.rb +9 -35
- data/lib/pindo/module/task/model/build/ios_build_appstore_task.rb +5 -5
- data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +14 -4
- data/lib/pindo/module/task/model/build_task.rb +2 -0
- data/lib/pindo/module/task/model/git/git_commit_task.rb +11 -1
- data/lib/pindo/module/task/model/git/git_tag_task.rb +92 -77
- data/lib/pindo/module/task/model/jps/jps_workflow_message_task.rb +8 -10
- data/lib/pindo/module/task/model/unity/unity_export_task.rb +44 -0
- data/lib/pindo/module/task/pindo_task.rb +2 -2
- data/lib/pindo/module/task/task_manager.rb +14 -6
- data/lib/pindo/module/task/task_reporter.rb +45 -0
- data/lib/pindo/module/unity/nuget_helper.rb +5 -20
- data/lib/pindo/module/utils/git_repo_helper.rb +69 -36
- data/lib/pindo/module/xcode/ipa_resign_helper.rb +33 -62
- data/lib/pindo/module/xcode/res/xcode_res_handler.rb +25 -4
- data/lib/pindo/module/xcode/xcode_build_config.rb +7 -1
- data/lib/pindo/module/xcode/xcode_build_helper.rb +36 -4
- data/lib/pindo/module/xcode/xcode_res_helper.rb +8 -0
- data/lib/pindo/options/helpers/bundleid_selector.rb +23 -6
- data/lib/pindo/version.rb +1 -1
- metadata +2 -2
|
@@ -12,7 +12,7 @@ module Pindo
|
|
|
12
12
|
:git_tag
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# 重试配置:失败后重试
|
|
15
|
+
# 重试配置:失败后重试 2 次(共 3 次尝试)
|
|
16
16
|
def self.default_retry_count
|
|
17
17
|
2
|
|
18
18
|
end
|
|
@@ -39,7 +39,7 @@ module Pindo
|
|
|
39
39
|
protected
|
|
40
40
|
|
|
41
41
|
def do_work
|
|
42
|
-
# 检查是否为 Git
|
|
42
|
+
# 检查是否为 Git 仓库,如果不是则直接成功返回(非 Git 仓库目录下跳过打 tag)
|
|
43
43
|
unless valid_git_repository?
|
|
44
44
|
Funlog.instance.fancyinfo_warning("当前目录不是Git仓库,跳过Git打Tag")
|
|
45
45
|
return {
|
|
@@ -51,93 +51,108 @@ module Pindo
|
|
|
51
51
|
|
|
52
52
|
root_dir = @git_root_path
|
|
53
53
|
git_repo_helper = Pindo::GitRepoHelper.share_instance
|
|
54
|
-
stash_saved = false
|
|
55
54
|
stash_name = "pindo_stash_#{Time.now.strftime('%Y%m%d%H%M%S')}_#{rand(1000)}"
|
|
56
55
|
coding_branch = Pindo::GitHandler.git!(%W(-C #{root_dir} rev-parse --abbrev-ref HEAD)).strip
|
|
56
|
+
git_commit_data = get_data_param_by_key(:git_commit)
|
|
57
|
+
stash_saved = false
|
|
57
58
|
|
|
58
59
|
begin
|
|
59
|
-
# 1.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@tag_pre = param[:tag_pre] if param[:tag_pre]
|
|
73
|
-
@tag_type = param[:tag_type] if param[:tag_type]
|
|
74
|
-
@ver_inc = param[:ver_inc] if param[:ver_inc]
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# 3. 合并到发布分支(使用已回填的 @release_branch)
|
|
78
|
-
if coding_branch != @release_branch
|
|
79
|
-
Pindo::GitHandler.merge_branches(
|
|
80
|
-
project_dir: root_dir,
|
|
81
|
-
source_branch: coding_branch,
|
|
82
|
-
target_branch: @release_branch,
|
|
83
|
-
sync_branches: true # 双向合并,确保两分支完全同步
|
|
84
|
-
)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# 4. 获取版本信息
|
|
88
|
-
if git_commit_data && git_commit_data[:task_param] &&
|
|
89
|
-
git_commit_data[:task_param][:build_version] && git_commit_data[:task_param][:build_number]
|
|
90
|
-
@build_version = git_commit_data[:task_param][:build_version]
|
|
91
|
-
@build_number = git_commit_data[:task_param][:build_number]
|
|
92
|
-
Funlog.instance.fancyinfo_success("使用 GitCommitTask 的版本号: #{@build_version}, Build: #{@build_number}, Tag前缀: #{@tag_pre}, Tag类型: #{@tag_type}")
|
|
93
|
-
|
|
94
|
-
# 降级:自己计算(向后兼容或 GitCommitTask 无有效数据)
|
|
95
|
-
else
|
|
96
|
-
@build_number, @commit_hash = git_repo_helper.get_build_number_from_commit(project_dir: root_dir)
|
|
97
|
-
@build_version = git_repo_helper.calculate_build_version(
|
|
98
|
-
project_dir: root_dir,
|
|
99
|
-
tag_prefix: @tag_pre,
|
|
100
|
-
create_tag_type: @tag_type,
|
|
101
|
-
version_increase_type: @ver_inc
|
|
102
|
-
)
|
|
103
|
-
commit_short = @commit_hash ? @commit_hash[0..7] : "unknown"
|
|
104
|
-
Funlog.instance.fancyinfo_warning("未找到 GitCommitTask 依赖,降级为自己计算版本号: #{@build_version}, Build: #{@build_number}, Commit: #{commit_short}")
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# 4. 创建并推送 Tag
|
|
60
|
+
# 1. 工作目录不干净则暂存
|
|
61
|
+
stash_saved = stash_if_dirty(root_dir, stash_name)
|
|
62
|
+
|
|
63
|
+
# 2. 从 GitCommitTask 回填参数(须在合并之前,确保 release_branch 等正确)
|
|
64
|
+
backfill_params_from_commit_task(git_commit_data)
|
|
65
|
+
|
|
66
|
+
# 3. 合并到发布分支
|
|
67
|
+
merge_to_release_branch(root_dir, coding_branch)
|
|
68
|
+
|
|
69
|
+
# 4. 解析版本号
|
|
70
|
+
resolve_build_version(root_dir, git_commit_data, git_repo_helper)
|
|
71
|
+
|
|
72
|
+
# 5. 创建并推送 Tag
|
|
108
73
|
tag_name = "#{@tag_pre}#{@build_version}"
|
|
109
74
|
git_repo_helper.create_and_push_tag(
|
|
110
75
|
project_dir: root_dir,
|
|
111
76
|
tag_name: tag_name,
|
|
112
77
|
tag_type: @tag_type
|
|
113
78
|
)
|
|
114
|
-
|
|
115
79
|
ensure
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
80
|
+
# 6. 还原 stash
|
|
81
|
+
restore_stash(root_dir, stash_name) if stash_saved
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# 工作目录有未提交更改则暂存,返回是否已暂存
|
|
88
|
+
def stash_if_dirty(root_dir, stash_name)
|
|
89
|
+
status = Pindo::GitHandler.git!(%W(-C #{root_dir} status --porcelain)).strip
|
|
90
|
+
return false if status.empty?
|
|
91
|
+
|
|
92
|
+
Funlog.instance.fancyinfo_update("工作目录有未提交更改,暂存到 stash: #{stash_name}")
|
|
93
|
+
Pindo::GitHandler.git!(%W(-C #{root_dir} stash push -u -m #{stash_name}))
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 从 GitCommitTask 回填 release_branch / tag_pre / tag_type / ver_inc
|
|
98
|
+
def backfill_params_from_commit_task(git_commit_data)
|
|
99
|
+
return unless git_commit_data && git_commit_data[:task_param]
|
|
100
|
+
|
|
101
|
+
param = git_commit_data[:task_param]
|
|
102
|
+
@release_branch = param[:release_branch] if param[:release_branch]
|
|
103
|
+
@tag_pre = param[:tag_pre] if param[:tag_pre]
|
|
104
|
+
@tag_type = param[:tag_type] if param[:tag_type]
|
|
105
|
+
@ver_inc = param[:ver_inc] if param[:ver_inc]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# 将编码分支双向合并到发布分支,确保两分支同步到同一节点
|
|
109
|
+
def merge_to_release_branch(root_dir, coding_branch)
|
|
110
|
+
return if coding_branch == @release_branch
|
|
111
|
+
|
|
112
|
+
Pindo::GitHandler.merge_branches(
|
|
113
|
+
project_dir: root_dir,
|
|
114
|
+
source_branch: coding_branch,
|
|
115
|
+
target_branch: @release_branch,
|
|
116
|
+
sync_branches: true # 双向合并,确保两分支完全同步
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# 解析构建版本号:优先用 GitCommitTask 的,否则自己计算(向后兼容)
|
|
121
|
+
def resolve_build_version(root_dir, git_commit_data, git_repo_helper)
|
|
122
|
+
if git_commit_data && git_commit_data[:task_param] &&
|
|
123
|
+
git_commit_data[:task_param][:build_version] && git_commit_data[:task_param][:build_number]
|
|
124
|
+
@build_version = git_commit_data[:task_param][:build_version]
|
|
125
|
+
@build_number = git_commit_data[:task_param][:build_number]
|
|
126
|
+
Funlog.instance.fancyinfo_success("使用 GitCommitTask 的版本号: #{@build_version}, Build: #{@build_number}, Tag前缀: #{@tag_pre}, Tag类型: #{@tag_type}")
|
|
127
|
+
else
|
|
128
|
+
@build_number, @commit_hash = git_repo_helper.get_build_number_from_commit(project_dir: root_dir)
|
|
129
|
+
@build_version = git_repo_helper.calculate_build_version(
|
|
130
|
+
project_dir: root_dir,
|
|
131
|
+
tag_prefix: @tag_pre,
|
|
132
|
+
create_tag_type: @tag_type,
|
|
133
|
+
version_increase_type: @ver_inc
|
|
134
|
+
)
|
|
135
|
+
commit_short = @commit_hash ? @commit_hash[0..7] : "unknown"
|
|
136
|
+
Funlog.instance.fancyinfo_warning("未找到 GitCommitTask 依赖,降级为自己计算版本号: #{@build_version}, Build: #{@build_number}, Commit: #{commit_short}")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# 还原之前暂存的 stash;冲突时保留 stash 并告警,不抛异常(tag 已创建成功)
|
|
141
|
+
def restore_stash(root_dir, stash_name)
|
|
142
|
+
stash_list = Pindo::GitHandler.git!(%W(-C #{root_dir} stash list)).strip
|
|
143
|
+
matched_line = stash_list.lines.find { |l| l.include?(stash_name) }
|
|
144
|
+
|
|
145
|
+
if matched_line && (stash_id = matched_line.match(/(stash@\{\d+\})/)&.[](1))
|
|
146
|
+
# 先应用 stash(不删除),成功后再删除
|
|
147
|
+
Pindo::GitHandler.git!(%W(-C #{root_dir} stash apply #{stash_id}))
|
|
148
|
+
Pindo::GitHandler.git!(%W(-C #{root_dir} stash drop #{stash_id}))
|
|
149
|
+
Funlog.instance.fancyinfo_success("已还原 stash: #{stash_name} (#{stash_id})")
|
|
150
|
+
else
|
|
151
|
+
Funlog.instance.fancyinfo_warning("未找到名为 #{stash_name} 的 stash,请手动检查")
|
|
140
152
|
end
|
|
153
|
+
rescue => e
|
|
154
|
+
Funlog.instance.fancyinfo_warning("stash 还原时发生冲突: #{e.message}")
|
|
155
|
+
Funlog.instance.fancyinfo_warning("stash 已保留,请手动执行 'git stash pop' 处理冲突")
|
|
141
156
|
end
|
|
142
157
|
|
|
143
158
|
end
|
|
@@ -15,6 +15,11 @@ module Pindo
|
|
|
15
15
|
:jps_workflow_message
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# 工作流消息失败重试 1 次(覆盖 JPSTask 默认的重试 3 次)
|
|
19
|
+
def self.default_retry_count
|
|
20
|
+
1
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
# 初始化工作流消息发送任务
|
|
19
24
|
# @param options [Hash] 选项
|
|
20
25
|
# @option options [String] :git_commit_id Git commit SHA(可选,从依赖任务获取)
|
|
@@ -70,17 +75,10 @@ module Pindo
|
|
|
70
75
|
workflow_id: @workflow_id
|
|
71
76
|
}
|
|
72
77
|
rescue => e
|
|
73
|
-
#
|
|
74
|
-
puts "
|
|
78
|
+
# 工作流消息是终端任务,失败需明确报错中断(不再伪造 success)
|
|
79
|
+
puts " ❌ JPS 工作流消息发送失败: #{e.message}"
|
|
75
80
|
puts " ⚠️ 错误堆栈: #{e.backtrace.first(3).join("\n ")}" if ENV['PINDO_DEBUG'] && e.backtrace
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# 消息发送失败不抛出异常,返回成功但带警告
|
|
79
|
-
{
|
|
80
|
-
success: true,
|
|
81
|
-
warning: e.message,
|
|
82
|
-
git_commit_id: @git_commit_id
|
|
83
|
-
}
|
|
81
|
+
raise Informative, "JPS 工作流消息发送失败: #{e.message}"
|
|
84
82
|
end
|
|
85
83
|
end
|
|
86
84
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'pindo/module/task/model/unity_task'
|
|
2
2
|
require 'pindo/base/funlog'
|
|
3
|
+
require 'json'
|
|
3
4
|
|
|
4
5
|
module Pindo
|
|
5
6
|
module TaskSystem
|
|
@@ -18,6 +19,7 @@ module Pindo
|
|
|
18
19
|
# - :project_path [String] Unity 项目路径(默认当前目录)
|
|
19
20
|
# - :export_path [String] 导出路径(nil 表示自动检测)
|
|
20
21
|
# - :deploy_mode [String] 部署模式 ('dev', 'adhoc', 'release'),默认 'dev'
|
|
22
|
+
# - :app_config [Hash] 需写入工程 AppConfig 的键值(传入则在导出前先改写 AppConfig)
|
|
21
23
|
# - :context [Hash] 上下文信息(如 index_count)
|
|
22
24
|
def initialize(platform, options = {})
|
|
23
25
|
@platform = platform
|
|
@@ -26,6 +28,7 @@ module Pindo
|
|
|
26
28
|
@index_count = options[:context] ? options[:context][:index_count] : nil
|
|
27
29
|
@export_path = options[:export_path] # 可由参数传入,nil 表示自动检测
|
|
28
30
|
@deploy_mode = options[:deploy_mode] || 'dev' # 部署模式:dev/adhoc/release,默认 dev
|
|
31
|
+
@app_config = options[:app_config] # 需写入 AppConfig 的键值,nil/空表示跳过
|
|
29
32
|
|
|
30
33
|
name = case platform
|
|
31
34
|
when 'ios', 'ipa'
|
|
@@ -57,6 +60,9 @@ module Pindo
|
|
|
57
60
|
protected
|
|
58
61
|
|
|
59
62
|
def do_work
|
|
63
|
+
# 若传入了配置参数,先改写工程内的 AppConfig
|
|
64
|
+
update_app_config_file if @app_config && !@app_config.empty?
|
|
65
|
+
|
|
60
66
|
# 获取 Unity 版本
|
|
61
67
|
project_unity_version = get_unity_version
|
|
62
68
|
|
|
@@ -106,6 +112,44 @@ module Pindo
|
|
|
106
112
|
|
|
107
113
|
private
|
|
108
114
|
|
|
115
|
+
# 使用传入的配置改写 Unity 工程内的 AppConfig(TextAsset JSON)
|
|
116
|
+
# 仅覆盖 @app_config 中提供的键,其余键保持原值
|
|
117
|
+
def update_app_config_file
|
|
118
|
+
app_config_file = File.join(@unity_root_path, "Assets", "Resources", "AppConfig", "AppConfig.json")
|
|
119
|
+
unless File.exist?(app_config_file)
|
|
120
|
+
Funlog.warning("未找到 AppConfig 文件,跳过 AppConfig 更新:#{app_config_file}")
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
puts "更新 AppConfig: #{app_config_file}"
|
|
125
|
+
|
|
126
|
+
begin
|
|
127
|
+
app_config_json = JSON.parse(File.read(app_config_file))
|
|
128
|
+
rescue JSON::ParserError => e
|
|
129
|
+
Funlog.warning("AppConfig 不是合法 JSON,跳过更新:#{e.message}")
|
|
130
|
+
return
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
changed = false
|
|
134
|
+
@app_config.each do |key, value|
|
|
135
|
+
next if value.nil? || value.to_s.strip.empty?
|
|
136
|
+
new_value = value.to_s.strip
|
|
137
|
+
old_value = app_config_json[key]
|
|
138
|
+
next if old_value.to_s == new_value
|
|
139
|
+
|
|
140
|
+
app_config_json[key] = new_value
|
|
141
|
+
puts " ✓ #{key}: #{old_value} → #{new_value}"
|
|
142
|
+
changed = true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if changed
|
|
146
|
+
File.open(app_config_file, "w") { |f| f.write(JSON.pretty_generate(app_config_json)) }
|
|
147
|
+
puts "✅ AppConfig 更新完成\n"
|
|
148
|
+
else
|
|
149
|
+
puts "AppConfig 无需更新(值一致)\n"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
109
153
|
# 导出 iOS
|
|
110
154
|
def export_ios
|
|
111
155
|
platform = 'iOS'
|
|
@@ -118,12 +118,19 @@ module Pindo
|
|
|
118
118
|
# 输出执行摘要
|
|
119
119
|
@reporter.print_execution_summary
|
|
120
120
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
# 失败闸门:FAILED / CANCELLED / 残留 PENDING(依赖阻塞等导致从未执行)都视为
|
|
122
|
+
# 「未成功完成」,统一抛异常确保进程非零退出码,避免任务没跑成却静默成功
|
|
123
|
+
completed = @queue.completed_snapshot
|
|
124
|
+
failed_tasks = completed.select { |t| t.status == TaskStatus::FAILED }
|
|
125
|
+
cancelled_tasks = completed.select { |t| t.status == TaskStatus::CANCELLED }
|
|
126
|
+
unfinished_tasks = @queue.pending_snapshot
|
|
127
|
+
|
|
128
|
+
if failed_tasks.any? || cancelled_tasks.any? || unfinished_tasks.any?
|
|
129
|
+
parts = []
|
|
130
|
+
parts << "失败: #{failed_tasks.map(&:name).join(', ')}" if failed_tasks.any?
|
|
131
|
+
parts << "取消: #{cancelled_tasks.map(&:name).join(', ')}" if cancelled_tasks.any?
|
|
132
|
+
parts << "未执行: #{unfinished_tasks.map(&:name).join(', ')}" if unfinished_tasks.any?
|
|
133
|
+
raise Informative, "存在未成功完成的任务 —— #{parts.join(';')}"
|
|
127
134
|
end
|
|
128
135
|
end
|
|
129
136
|
|
|
@@ -138,6 +145,7 @@ module Pindo
|
|
|
138
145
|
completed: completed.count,
|
|
139
146
|
success: completed.count { |t| t.status == TaskStatus::SUCCESS },
|
|
140
147
|
failed: completed.count { |t| t.status == TaskStatus::FAILED },
|
|
148
|
+
cancelled: completed.count { |t| t.status == TaskStatus::CANCELLED },
|
|
141
149
|
tasks: (pending + completed).map do |task|
|
|
142
150
|
{
|
|
143
151
|
id: task.id,
|
|
@@ -41,12 +41,57 @@ module Pindo
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# 按依赖拓扑排序展示真实执行顺序
|
|
45
|
+
ordered = topological_execution_order(@queue.pending_snapshot)
|
|
46
|
+
is_serial = strategy.respond_to?(:name) && strategy.name.include?("串行")
|
|
47
|
+
puts "\e[34m"
|
|
48
|
+
if is_serial
|
|
49
|
+
puts "\e[34m" + " 实际执行顺序:"
|
|
50
|
+
else
|
|
51
|
+
puts "\e[34m" + " 执行顺序预览(并发模式下实际顺序受运行时调度影响):"
|
|
52
|
+
end
|
|
53
|
+
ordered.each_with_index do |task, index|
|
|
54
|
+
puts "\e[34m" + " #{index + 1}. #{task.name}"
|
|
55
|
+
end
|
|
56
|
+
|
|
44
57
|
puts "\e[34m"
|
|
45
58
|
puts "\e[34m" + " 总计: #{@queue.pending_count} 个任务"
|
|
46
59
|
puts "\e[34m" + "=" * 60 + "\e[0m"
|
|
47
60
|
puts "\n"
|
|
48
61
|
end
|
|
49
62
|
|
|
63
|
+
# 按依赖关系生成执行顺序(贪心拓扑排序,模拟串行调度:
|
|
64
|
+
# 每轮取「入队顺序中第一个依赖已满足」的任务),与串行实际执行顺序一致
|
|
65
|
+
# @param tasks [Array<PindoTask>] 待排序任务(入队顺序)
|
|
66
|
+
# @return [Array<PindoTask>] 按执行顺序排列的任务
|
|
67
|
+
def topological_execution_order(tasks)
|
|
68
|
+
all_ids = tasks.map(&:id)
|
|
69
|
+
remaining = tasks.dup
|
|
70
|
+
completed_ids = []
|
|
71
|
+
ordered = []
|
|
72
|
+
|
|
73
|
+
until remaining.empty?
|
|
74
|
+
index = remaining.index do |task|
|
|
75
|
+
task.dependencies.all? do |dep_id|
|
|
76
|
+
# 依赖已完成,或依赖不在本批任务中(视为已提前完成)
|
|
77
|
+
completed_ids.include?(dep_id) || !all_ids.include?(dep_id)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 存在循环依赖或依赖缺失,剩余任务按原顺序追加并结束
|
|
82
|
+
if index.nil?
|
|
83
|
+
ordered.concat(remaining)
|
|
84
|
+
break
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
task = remaining.delete_at(index)
|
|
88
|
+
completed_ids << task.id
|
|
89
|
+
ordered << task
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
ordered
|
|
93
|
+
end
|
|
94
|
+
|
|
50
95
|
# 输出任务执行标题
|
|
51
96
|
# @param task [PindoTask] 任务对象
|
|
52
97
|
def print_task_title(task)
|
|
@@ -505,9 +505,8 @@ module Pindo
|
|
|
505
505
|
puts " 使用工具: nuget pack"
|
|
506
506
|
puts
|
|
507
507
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
success = system(cmd)
|
|
508
|
+
# 数组参数 + chdir,避免路径中的 shell 元字符注入
|
|
509
|
+
success = system("nuget", "pack", File.basename(nuspec_file), "-OutputDirectory", output_dir, chdir: package_dir)
|
|
511
510
|
|
|
512
511
|
unless success
|
|
513
512
|
raise Informative, "NuGet 打包失败,请检查 .nuspec 文件格式"
|
|
@@ -573,10 +572,8 @@ module Pindo
|
|
|
573
572
|
begin
|
|
574
573
|
# 查找是否存在与 nuspec 版本匹配的 tag(支持 v1.1.3, V1.1.3, 1.1.3 等格式)
|
|
575
574
|
matching_tag = nil
|
|
576
|
-
temp_dir = Dir.pwd
|
|
577
|
-
Dir.chdir(git_root)
|
|
578
575
|
|
|
579
|
-
# 获取所有 tags
|
|
576
|
+
# 获取所有 tags(命令已带 -C,无需 chdir)
|
|
580
577
|
all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
|
|
581
578
|
|
|
582
579
|
# 查找匹配当前版本的 tag(不区分大小写,支持 v 前缀)
|
|
@@ -585,17 +582,12 @@ module Pindo
|
|
|
585
582
|
tag_version.downcase == nuspec_version.downcase
|
|
586
583
|
end
|
|
587
584
|
|
|
588
|
-
Dir.chdir(temp_dir)
|
|
589
|
-
|
|
590
585
|
# 确定获取哪个范围的 commits
|
|
591
586
|
git_range = if matching_tag
|
|
592
587
|
# 找到匹配的 tag,获取前一个 tag 到这个 tag 的 commits
|
|
593
588
|
puts "📝 找到版本 tag: #{matching_tag},生成该版本的 release notes..."
|
|
594
589
|
|
|
595
|
-
|
|
596
|
-
Dir.chdir(git_root)
|
|
597
|
-
|
|
598
|
-
# 获取所有 tags 并按版本号排序
|
|
590
|
+
# 获取所有 tags 并按版本号排序(命令已带 -C,无需 chdir)
|
|
599
591
|
all_tags = Pindo::GitHandler.git!(%W(-C #{git_root} tag -l)).split("\n")
|
|
600
592
|
sorted_tags = all_tags.sort_by do |tag|
|
|
601
593
|
version_str = tag.gsub(/^(v|V|release[\s_-]*)/i, '')
|
|
@@ -609,8 +601,6 @@ module Pindo
|
|
|
609
601
|
prev_tag = sorted_tags[tag_index - 1]
|
|
610
602
|
end
|
|
611
603
|
|
|
612
|
-
Dir.chdir(temp_dir)
|
|
613
|
-
|
|
614
604
|
if prev_tag
|
|
615
605
|
"#{prev_tag}..#{matching_tag}"
|
|
616
606
|
else
|
|
@@ -635,10 +625,7 @@ module Pindo
|
|
|
635
625
|
end
|
|
636
626
|
end
|
|
637
627
|
|
|
638
|
-
# 获取 commit messages(包含完整的 body)
|
|
639
|
-
temp_dir = Dir.pwd
|
|
640
|
-
Dir.chdir(git_root)
|
|
641
|
-
|
|
628
|
+
# 获取 commit messages(包含完整的 body;命令已带 -C,无需 chdir)
|
|
642
629
|
# 使用特殊分隔符来区分不同的 commits
|
|
643
630
|
separator = "---COMMIT-SEPARATOR---"
|
|
644
631
|
commits_raw = Pindo::GitHandler.git!(%W(-C #{git_root} log #{git_range} --pretty=format:%B#{separator}))
|
|
@@ -646,8 +633,6 @@ module Pindo
|
|
|
646
633
|
# 按分隔符拆分成单个 commits
|
|
647
634
|
commits = commits_raw.split(separator).map(&:strip).reject(&:empty?)
|
|
648
635
|
|
|
649
|
-
Dir.chdir(temp_dir)
|
|
650
|
-
|
|
651
636
|
# 过滤只保留符合规范的 commits(支持带 scope 和不带 scope 格式)
|
|
652
637
|
# 匹配格式: feat:, feat(scope):, fix:, fix(api): 等
|
|
653
638
|
conventional_commit_regex = /^(feat|fix|docs|doc|perf|refactor|style|test|chore|revert)(\([^)]+\))?:/i
|
|
@@ -330,53 +330,86 @@ module Pindo
|
|
|
330
330
|
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
331
331
|
raise ArgumentError, "tag名称不能为空" if tag_name.nil? || tag_name.empty?
|
|
332
332
|
|
|
333
|
+
local_exists = Pindo::GitHandler.local_tag_exists?(local_repo_dir: project_dir, tag_name: tag_name)
|
|
334
|
+
|
|
335
|
+
# 建本地 tag 是纯本地操作,不依赖远程可达,网络故障也能留下本地 tag;
|
|
336
|
+
# 删远程残留与推送一律交给 push_tag_with_verify,推送失败时本地 tag 仍保留,可事后手动补推。
|
|
333
337
|
case tag_type
|
|
334
338
|
when Pindo::CreateTagType::NONE
|
|
339
|
+
# 不创建 tag
|
|
335
340
|
Funlog.instance.fancyinfo_success("跳过创建 Tag")
|
|
336
|
-
|
|
337
|
-
when Pindo::CreateTagType::RECREATE
|
|
338
|
-
#
|
|
339
|
-
if Pindo::GitHandler.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
341
|
+
nil
|
|
342
|
+
when Pindo::CreateTagType::NEW, Pindo::CreateTagType::RECREATE
|
|
343
|
+
# 本地 tag 已在 HEAD:无需重建,仅确保推送到远程(RECREATE 在 HEAD 原地重建也是空转,同样适用)
|
|
344
|
+
if local_exists && Pindo::GitHandler.is_tag_at_head?(git_root_dir: project_dir, tag_name: tag_name)
|
|
345
|
+
push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
|
|
346
|
+
tag_name
|
|
347
|
+
else
|
|
348
|
+
# 本地 tag 不存在或不在 HEAD:删旧重建后推送
|
|
349
|
+
if local_exists
|
|
350
|
+
Funlog.instance.fancyinfo_update("删除本地旧 Tag: #{tag_name}")
|
|
351
|
+
Pindo::GitHandler.git!(%W(-C #{project_dir} tag -d #{tag_name}))
|
|
352
|
+
end
|
|
353
|
+
Pindo::GitHandler.git!(%W(-C #{project_dir} tag #{tag_name}))
|
|
354
|
+
Funlog.instance.fancyinfo_success("已创建本地 Tag: #{tag_name}")
|
|
355
|
+
push_tag_with_verify(project_dir: project_dir, tag_name: tag_name)
|
|
356
|
+
tag_name
|
|
346
357
|
end
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
else
|
|
359
|
+
raise ArgumentError, "未知的 tag_type: #{tag_type}"
|
|
360
|
+
end
|
|
361
|
+
end
|
|
350
362
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
363
|
+
# 推送 tag 到远程并校验是否真正推送成功,失败则重试
|
|
364
|
+
# 校验方式为「远程 tag 指向的 commit == 本地 tag commit」,
|
|
365
|
+
# 一次 ls-remote 同时覆盖「存在性」与「指向正确性」
|
|
366
|
+
# @param project_dir [String] 仓库目录
|
|
367
|
+
# @param tag_name [String] tag 名称
|
|
368
|
+
# @param max_retries [Integer] 最大重试次数(默认 3)
|
|
369
|
+
# @return [Boolean] 是否推送成功(失败到达上限会 raise)
|
|
370
|
+
def push_tag_with_verify(project_dir:, tag_name:, max_retries: 3)
|
|
371
|
+
local_commit = Pindo::GitHandler.git!(%W(-C #{project_dir} rev-parse #{tag_name}^{commit})).strip
|
|
372
|
+
|
|
373
|
+
attempt = 0
|
|
374
|
+
loop do
|
|
375
|
+
attempt += 1
|
|
376
|
+
begin
|
|
377
|
+
# 远程若残留指向不同 commit 的同名 tag,先删除再推送,避免 non-fast-forward 被拒
|
|
378
|
+
remote_commit = Pindo::GitHandler.remote_tag_commit(local_repo_dir: project_dir, tag_name: tag_name)
|
|
379
|
+
if remote_commit == local_commit
|
|
380
|
+
Funlog.instance.fancyinfo_success("已确认 Tag 推送到远程: #{tag_name}")
|
|
381
|
+
return true
|
|
382
|
+
elsif remote_commit
|
|
383
|
+
Funlog.instance.fancyinfo_update("删除远程残留 Tag(远程 #{remote_commit[0..7]} ≠ 本地 #{local_commit[0..7]}): #{tag_name}")
|
|
384
|
+
Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin :refs/tags/#{tag_name}))
|
|
359
385
|
end
|
|
360
|
-
|
|
386
|
+
|
|
387
|
+
Pindo::GitHandler.git_remote!(project_dir, %W(-C #{project_dir} push origin #{tag_name}))
|
|
388
|
+
rescue => e
|
|
389
|
+
first_line = e.message.lines.first&.strip
|
|
390
|
+
Funlog.instance.fancyinfo_warning("推送 Tag 失败(第 #{attempt}/#{max_retries} 次): #{first_line}")
|
|
361
391
|
end
|
|
362
392
|
|
|
363
|
-
# tag
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
393
|
+
# 校验远程 tag 指向的 commit 是否与本地一致,确认推送成功(探测失败按未确认处理,不中断重试)
|
|
394
|
+
remote_commit = begin
|
|
395
|
+
Pindo::GitHandler.remote_tag_commit(local_repo_dir: project_dir, tag_name: tag_name)
|
|
396
|
+
rescue
|
|
397
|
+
nil
|
|
367
398
|
end
|
|
368
|
-
if
|
|
369
|
-
Funlog.instance.
|
|
370
|
-
|
|
399
|
+
if remote_commit && remote_commit == local_commit
|
|
400
|
+
Funlog.instance.fancyinfo_success("已确认 Tag 推送到远程: #{tag_name}")
|
|
401
|
+
return true
|
|
371
402
|
end
|
|
372
|
-
# 继续创建新 tag
|
|
373
|
-
end
|
|
374
403
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
404
|
+
if attempt >= max_retries
|
|
405
|
+
# 本地 tag 已创建,仅远程未确认 —— 保留本地 tag,提示可手动补推
|
|
406
|
+
Funlog.instance.fancyinfo_warning("本地 Tag 已创建并保留,可稍后手动执行: git push origin #{tag_name}")
|
|
407
|
+
raise Informative, "Tag 推送失败,已重试 #{max_retries} 次仍未在远程确认: #{tag_name}(本地 Tag 已保留,可手动补推)"
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
Funlog.instance.fancyinfo_update("远程未确认 Tag,#{attempt}s 后重试推送: #{tag_name}")
|
|
411
|
+
sleep(attempt)
|
|
412
|
+
end
|
|
380
413
|
end
|
|
381
414
|
|
|
382
415
|
end
|