m-git 2.5.4

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +85 -0
  4. data/lib/m-git.rb +66 -0
  5. data/lib/m-git/argv.rb +170 -0
  6. data/lib/m-git/argv/opt.rb +38 -0
  7. data/lib/m-git/argv/opt_list.rb +71 -0
  8. data/lib/m-git/argv/parser.rb +66 -0
  9. data/lib/m-git/base_command.rb +271 -0
  10. data/lib/m-git/command/add.rb +41 -0
  11. data/lib/m-git/command/branch.rb +90 -0
  12. data/lib/m-git/command/checkout.rb +106 -0
  13. data/lib/m-git/command/clean.rb +64 -0
  14. data/lib/m-git/command/commit.rb +84 -0
  15. data/lib/m-git/command/config.rb +202 -0
  16. data/lib/m-git/command/delete.rb +99 -0
  17. data/lib/m-git/command/fetch.rb +32 -0
  18. data/lib/m-git/command/forall.rb +81 -0
  19. data/lib/m-git/command/info.rb +74 -0
  20. data/lib/m-git/command/init.rb +324 -0
  21. data/lib/m-git/command/log.rb +73 -0
  22. data/lib/m-git/command/merge.rb +381 -0
  23. data/lib/m-git/command/pull.rb +364 -0
  24. data/lib/m-git/command/push.rb +311 -0
  25. data/lib/m-git/command/rebase.rb +348 -0
  26. data/lib/m-git/command/reset.rb +31 -0
  27. data/lib/m-git/command/self.rb +223 -0
  28. data/lib/m-git/command/stash.rb +189 -0
  29. data/lib/m-git/command/status.rb +135 -0
  30. data/lib/m-git/command/sync.rb +327 -0
  31. data/lib/m-git/command/tag.rb +67 -0
  32. data/lib/m-git/command_manager.rb +24 -0
  33. data/lib/m-git/error.rb +20 -0
  34. data/lib/m-git/foundation.rb +25 -0
  35. data/lib/m-git/foundation/constants.rb +107 -0
  36. data/lib/m-git/foundation/dir.rb +25 -0
  37. data/lib/m-git/foundation/duration_recorder.rb +92 -0
  38. data/lib/m-git/foundation/git_message_parser.rb +50 -0
  39. data/lib/m-git/foundation/lock.rb +32 -0
  40. data/lib/m-git/foundation/loger.rb +129 -0
  41. data/lib/m-git/foundation/mgit_config.rb +222 -0
  42. data/lib/m-git/foundation/operation_progress_manager.rb +139 -0
  43. data/lib/m-git/foundation/timer.rb +74 -0
  44. data/lib/m-git/foundation/utils.rb +361 -0
  45. data/lib/m-git/hooks_manager.rb +96 -0
  46. data/lib/m-git/manifest.rb +181 -0
  47. data/lib/m-git/manifest/cache_manager.rb +44 -0
  48. data/lib/m-git/manifest/internal.rb +182 -0
  49. data/lib/m-git/manifest/light_repo.rb +108 -0
  50. data/lib/m-git/manifest/light_repo_generator.rb +87 -0
  51. data/lib/m-git/manifest/linter.rb +153 -0
  52. data/lib/m-git/open_api.rb +427 -0
  53. data/lib/m-git/open_api/script_download_info.rb +37 -0
  54. data/lib/m-git/output/output.rb +461 -0
  55. data/lib/m-git/plugin_manager.rb +112 -0
  56. data/lib/m-git/repo.rb +133 -0
  57. data/lib/m-git/repo/status.rb +481 -0
  58. data/lib/m-git/repo/sync_helper.rb +254 -0
  59. data/lib/m-git/template.rb +9 -0
  60. data/lib/m-git/template/local_manifest.rb +27 -0
  61. data/lib/m-git/template/manifest_hook.rb +28 -0
  62. data/lib/m-git/template/post_download_hook.rb +29 -0
  63. data/lib/m-git/template/post_hook.rb +31 -0
  64. data/lib/m-git/template/pre_exec_hook.rb +31 -0
  65. data/lib/m-git/template/pre_hook.rb +29 -0
  66. data/lib/m-git/template/pre_push_hook.rb +32 -0
  67. data/lib/m-git/version.rb +6 -0
  68. data/lib/m-git/workspace.rb +648 -0
  69. data/lib/m-git/workspace/path_helper.rb +56 -0
  70. data/lib/m-git/workspace/workspace_helper.rb +159 -0
  71. data/m-git +1 -0
  72. data/mgit +19 -0
  73. metadata +218 -0
@@ -0,0 +1,364 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope 类似 git pull
6
+ #
7
+ class Pull < BaseCommand
8
+
9
+ OPT_LIST = {
10
+ :no_check => '--no-check',
11
+ }.freeze
12
+
13
+ def options
14
+ [
15
+ ARGV::Opt.new(OPT_LIST[:no_check], info:'指定该参数意味着执行前跳过仓库的状态检查,直接对指定或所有仓库执行pull操作,有一定风险,请慎重执行。', type: :boolean),
16
+ ].concat(super)
17
+ end
18
+
19
+ def __progress_type
20
+ OperationProgressManager::PROGRESS_TYPE[:pull]
21
+ end
22
+
23
+ def execute(argv)
24
+ if argv.opt(OPT_LIST[:no_check])
25
+ simple_pull(argv)
26
+ return
27
+ end
28
+
29
+ verbose_pull(argv)
30
+ end
31
+
32
+ def verbose_pull(argv)
33
+ return if do_abort(argv)
34
+
35
+ Output.puts_start_cmd
36
+
37
+ # 获取远程仓库当前分支信息
38
+ Workspace.pre_fetch
39
+
40
+ config_repo = generate_config_repo
41
+
42
+ if mgit_try_to_continue?
43
+ # 不处于中间态禁止执行
44
+ Foundation.help!("当前并不处于操作中间态,无法进行continue操作!") if !OperationProgressManager.is_in_progress?(Workspace.root, __progress_type)
45
+
46
+ # 读取指令缓存失败禁止执行
47
+ context, _ = OperationProgressManager.load_context(Workspace.root, __progress_type)
48
+ Foundation.help!("缓存指令读取失败,continue无法继续进行,请重新执行完整指令。") if context.nil? || !context.validate?
49
+
50
+ # 分支不匹配禁止执行
51
+ Foundation.help!("当前主仓库所在分支跟上次操作时所在分支(#{context.branch})不一致,请切换后重试。") if config_repo.status_checker.current_branch(use_cache:true) != context.branch
52
+
53
+ if !context.repos.nil?
54
+ Output.puts_processing_message("加载上次即将操作的子仓库...")
55
+ Workspace.update_all_repos(context.repos)
56
+ end
57
+
58
+ Output.puts_success_message("已跳过主仓库。")
59
+
60
+ else
61
+
62
+ # 处于中间态则提示
63
+ if OperationProgressManager.is_in_progress?(Workspace.root, __progress_type)
64
+ if Output.continue_with_user_remind?("当前处于操作中间态,建议取消操作并执行\"mgit pull --continue\"继续操作子仓库。\n 继续执行将清除中间态并重新操作所有仓库,是否取消?")
65
+ Output.puts_cancel_message
66
+ return
67
+ end
68
+ end
69
+
70
+ # 优先pull配置仓库
71
+ config_error = pull_config_repo(argv.cmd, argv.git_opts, config_repo)
72
+ if config_error
73
+ Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
74
+ return
75
+ end
76
+ end
77
+
78
+ do_repos = []
79
+ diverged_repos = []
80
+ no_remote_repos = []
81
+ no_tracking_repos = []
82
+ dirty_repos = []
83
+ detached_repos = []
84
+ remote_inconsist_repos = []
85
+ do_nothing_repos = []
86
+
87
+ Output.puts_processing_message("检查各仓库状态...")
88
+ Workspace.serial_enumerate_with_progress(all_repos) { |repo|
89
+ next if !config_repo.nil? && repo.name == config_repo.name
90
+
91
+ Timer.start(repo.name)
92
+ status = repo.status_checker.status
93
+ branch_status = repo.status_checker.branch_status
94
+
95
+ if branch_status == Repo::Status::GIT_BRANCH_STATUS[:up_to_date] || branch_status == Repo::Status::GIT_BRANCH_STATUS[:ahead]
96
+ # 领先和最新的仓库均不操作
97
+ do_nothing_repos.push(repo)
98
+ else
99
+
100
+ url_consist = repo.url_consist?
101
+ is_dirty = status == Repo::Status::GIT_REPO_STATUS[:dirty]
102
+ dirty_repos.push(repo) if is_dirty
103
+ remote_inconsist_repos.push(repo) if !url_consist
104
+
105
+ # 仅有分叉或落后,且工作区干净的仓库直接加入到操作集
106
+ if branch_status == Repo::Status::GIT_BRANCH_STATUS[:diverged] && !is_dirty && url_consist
107
+ do_repos.push(repo)
108
+ diverged_repos.push(repo.name)
109
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:behind] && !is_dirty && url_consist
110
+ do_repos.push(repo)
111
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
112
+ no_remote_repos.push(repo)
113
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
114
+ no_tracking_repos.push(repo)
115
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
116
+ detached_repos.push(repo)
117
+ end
118
+
119
+ end
120
+ Timer.stop(repo.name)
121
+ }
122
+ Output.puts_success_message("检查完成!\n")
123
+
124
+ if Workspace.filter_config.auto_exec
125
+ do_repos += dirty_repos
126
+ do_repos.uniq! { |repo| repo.name }
127
+ elsif no_remote_repos.length > 0 ||
128
+ dirty_repos.length > 0 ||
129
+ detached_repos.length > 0 ||
130
+ no_tracking_repos.length > 0 ||
131
+ remote_inconsist_repos.length > 0
132
+ remind_repos = []
133
+ remind_repos.push(['远程分支不存在', no_remote_repos.map { |e| e.name }]) if no_remote_repos.length > 0
134
+ remind_repos.push(['未追踪远程分支(建议:mgit branch -u origin/<branch>)', no_tracking_repos.map { |e| e.name }]) if no_tracking_repos.length > 0
135
+ remind_repos.push(['有本地改动', dirty_repos.map { |e| e.name }]) if dirty_repos.length > 0
136
+ remind_repos.push(['HEAD游离,当前不在任何分支上', detached_repos.map { |e| e.name }]) if detached_repos.length > 0
137
+ remind_repos.push(['实际url与配置不一致', remote_inconsist_repos.map { |e| e.name }]) if remote_inconsist_repos.length > 0
138
+ Output.interact_with_multi_selection_combined_repos(remind_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 强制执行', 'c: 终止']) { |input|
139
+ if input == 'b'
140
+ do_repos += dirty_repos
141
+ do_repos += detached_repos
142
+ do_repos += no_remote_repos
143
+ do_repos += no_tracking_repos
144
+ do_repos += remote_inconsist_repos
145
+ do_repos.uniq! { |repo| repo.name }
146
+ elsif input == 'c' || input != 'a'
147
+ Output.puts_cancel_message
148
+ return
149
+ end
150
+ }
151
+ end
152
+
153
+ if do_repos.length != 0
154
+ error_repos = []
155
+ # 如果不带任何参数,则将pull分解为fetch+merge执行, fetch已经执行,此处执行merge。带参数则透传。
156
+ if argv.git_opts.length == 0
157
+ # 排除HEAD游离,无远程分支,未追踪远程分支的仓库,这三种仓库是无法强制执行git pull的(但是可以执行如:git pull origin master,因此透传不做此校验)
158
+ skip_repos = do_repos.select { |repo|
159
+ branch_status = repo.status_checker.branch_status
160
+ branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote] ||
161
+ branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking] ||
162
+ branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
163
+ }
164
+ if skip_repos.length > 0
165
+ Output.puts_remind_block(skip_repos.map { |e| e.name }, "以上仓库无法强制执行,已跳过。")
166
+ do_repos -= skip_repos
167
+ if do_repos.length == 0
168
+ Output.puts_success_message("仓库均为最新,无须执行!")
169
+ return
170
+ end
171
+ end
172
+
173
+ count_msg = ",另有#{do_nothing_repos.length}个仓库无须执行" if do_nothing_repos.length > 0
174
+ Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始为以上仓库合并远程分支#{count_msg}...")
175
+ _, error_repos = Workspace.execute_git_cmd_with_repos('', '', do_repos) { |repo|
176
+ msg = nil
177
+ branch = repo.status_checker.current_branch(strict_mode:false, use_cache:true)
178
+ tracking_branch = repo.status_checker.tracking_branch(branch, use_cache:true)
179
+ # 如果产生分叉,为生成的新节点提供log
180
+ msg = "-m \"【Merge】【0.0.0】【#{branch}】合并远程分支'#{tracking_branch}'。\"" if diverged_repos.include?(repo.name)
181
+ ["merge", "#{tracking_branch} #{msg}"]
182
+ }
183
+ else
184
+ count_msg = ",另有#{do_nothing_repos.length}个仓库无须执行" if do_nothing_repos.length > 0
185
+ Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始pull以上仓库#{count_msg}...")
186
+ _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, do_repos)
187
+ end
188
+
189
+ if config_error.nil? && error_repos.length == 0
190
+ Output.puts_succeed_cmd(argv.absolute_cmd)
191
+ Timer.show_time_consuming_repos
192
+ end
193
+
194
+ else
195
+ Output.puts_success_message("仓库均为最新,无须执行!")
196
+ end
197
+
198
+ # 清除中间态
199
+ OperationProgressManager.remove_progress(Workspace.root, __progress_type)
200
+ end
201
+
202
+ def simple_pull(argv)
203
+ Output.puts_start_cmd
204
+
205
+ # 优先pull配置仓库
206
+ config_repo = generate_config_repo
207
+ config_error = pull_config_repo(argv.cmd, argv.git_opts, config_repo)
208
+
209
+ Output.puts_processing_message("开始pull子仓库...")
210
+ _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, all_repos)
211
+
212
+ if config_error.nil? && error_repos.length == 0
213
+ Output.puts_succeed_cmd(argv.absolute_cmd)
214
+ elsif !config_error.nil?
215
+ Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
216
+ end
217
+
218
+ # 情况中间态
219
+ OperationProgressManager.remove_progress(Workspace.root, __progress_type)
220
+ end
221
+
222
+ def pull_config_repo(cmd, opts, repo)
223
+ if !repo.nil?
224
+ branch_status = repo.status_checker.branch_status
225
+ if branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
226
+ remind_config_repo_fail("主仓库\"#{repo.name}\"HEAD游离,当前不在任何分支上,无法执行!")
227
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
228
+ remind_config_repo_fail("主仓库\"#{repo.name}\"未跟踪对应远程分支,无法执行!(需要执行'mgit branch -u origin/<branch>')")
229
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
230
+ remind_config_repo_fail("主仓库\"#{repo.name}\"远程分支不存在,无法执行!")
231
+ # elsif repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
232
+ # remind_config_repo_fail("主仓库\"#{repo.name}\"有改动,无法执行!")
233
+ else
234
+
235
+ Output.puts_processing_message("开始操作主仓库...")
236
+
237
+ # 现场信息
238
+ exec_subrepos = all_repos(except_config:true)
239
+ is_all = Workspace.is_all_exec_sub_repos?(exec_subrepos)
240
+ context = OperationProgressContext.new(__progress_type)
241
+ context.cmd = cmd
242
+ context.opts = opts
243
+ context.repos = is_all ? nil : exec_subrepos.map { |e| e.name } # nil表示操作所有子仓库
244
+ context.branch = repo.status_checker.current_branch(use_cache:true)
245
+
246
+ # 如果不带任何参数,则将pull分解为fetch+merge执行, fetch已经执行,此处执行merge。带参数则透传。
247
+ if opts.length == 0
248
+ branch = repo.status_checker.current_branch(strict_mode:false, use_cache:true)
249
+ tracking_branch = repo.status_checker.tracking_branch(branch, use_cache:true)
250
+ msg = "-m \"【Merge】【0.0.0】【#{branch}】合并远程分支'#{tracking_branch}'。\"" if branch_status == Repo::Status::GIT_BRANCH_STATUS[:diverged]
251
+ cmd, opts = "merge", "#{tracking_branch} #{msg}"
252
+ end
253
+
254
+ success, output = repo.execute_git_cmd(cmd, opts)
255
+ if success
256
+ Output.puts_success_message("主仓库操作成功!\n")
257
+ else
258
+ Output.puts_fail_message("主仓库操作失败!\n")
259
+ config_error = output
260
+ end
261
+
262
+ # 刷新配置表
263
+ begin
264
+ Workspace.update_config(strict_mode:false) { |missing_repos|
265
+ if missing_repos.length > 0
266
+ success_missing_repos = Workspace.guide_to_checkout_branch(missing_repos, all_repos, append_message:"拒绝该操作本次执行将忽略以上仓库")
267
+ all_repos.concat(success_missing_repos)
268
+ # success_missing_repos包含新下载的和当前分支已有的新仓库,其中已有仓库包含在@all_repos内,需要去重
269
+ all_repos.uniq! { |repo| repo.name }
270
+ end
271
+ }
272
+ refresh_context(context)
273
+ rescue Error => e
274
+ if e.type == MGIT_ERROR_TYPE[:config_generate_error]
275
+ OperationProgressManager.trap_into_progress(Workspace.root, context)
276
+ show_progress_error("配置表生成失败", "#{e.msg}")
277
+ end
278
+ end
279
+
280
+ return config_error
281
+ end
282
+ end
283
+ end
284
+
285
+ def remind_config_repo_fail(msg)
286
+ Output.puts_fail_message(msg)
287
+ if Workspace.filter_config.auto_exec || Output.continue_with_user_remind?("是否继续操作其余仓库?")
288
+ return
289
+ else
290
+ Output.puts_cancel_message
291
+ exit
292
+ end
293
+ end
294
+
295
+ def do_abort(argv)
296
+ if mgit_try_to_abort?
297
+ Output.puts_start_cmd
298
+ OperationProgressManager.remove_progress(Workspace.root, __progress_type)
299
+ do_repos = all_repos.select { |repo| repo.status_checker.is_in_merge_progress? }
300
+
301
+ if do_repos.length > 0
302
+ append_message = ",另有#{all_repos.length - do_repos.length}个仓库无须操作" if do_repos.length < all_repos.length
303
+ Output.puts_processing_block(do_repos.map { |e| e.name }, "开始操作以上仓库#{append_message}...")
304
+ _, error_repos = Workspace.execute_git_cmd_with_repos('merge', '--abort', do_repos)
305
+ Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
306
+ else
307
+ Output.puts_success_message("没有仓库需要操作!")
308
+ end
309
+
310
+ return true
311
+ else
312
+ return false
313
+ end
314
+ end
315
+
316
+ def refresh_context(context)
317
+ exec_subrepos = all_repos(except_config:true)
318
+ is_all = Workspace.is_all_exec_sub_repos?(exec_subrepos)
319
+ context.repos = is_all ? nil : exec_subrepos.map { |e| e.name } # nil表示操作所有子仓库
320
+ end
321
+
322
+ def show_progress_error(summary, detail)
323
+ error = "#{summary} 已进入操作中间态。
324
+
325
+ 原因:
326
+ #{detail}
327
+
328
+ 可选:
329
+ - 使用\"mgit pull --continue\"继续拉取。
330
+ - 使用\"mgit pull --abort\"取消拉取。"
331
+ Foundation.help!(error, title:'暂停')
332
+ end
333
+
334
+ def enable_repo_selection
335
+ return true
336
+ end
337
+
338
+ def enable_auto_execution
339
+ return true
340
+ end
341
+
342
+ def include_lock_by_default
343
+ return true
344
+ end
345
+
346
+ def enable_continue_operation
347
+ return true
348
+ end
349
+
350
+ def enable_abort_operation
351
+ return true
352
+ end
353
+
354
+ def self.description
355
+ return "从仓库或本地分支获取数据并合并。"
356
+ end
357
+
358
+ def self.usage
359
+ return "mgit pull [<git-pull-option>] [(--mrepo|--el-mrepo) <repo>...] [--auto-exec] [--no-check] [--include-lock] [--help]\nmgit pull --continue\nmgit pull --abort"
360
+ end
361
+
362
+ end
363
+
364
+ end
@@ -0,0 +1,311 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope 类似 git push
6
+ # 可自动生成 git gerrit 评审分支
7
+ # mgit push --gerrit
8
+ #
9
+ class Push < BaseCommand
10
+
11
+ # 默认是否开启gerrit相关功能
12
+ #
13
+ MGIT_PUSH_GERRIT_ENABLED = false
14
+
15
+ # 默认是否开启topic相关功能,开启后强制开启gerrit
16
+ MGIT_PUSH_TOPIC_ENABLED = false
17
+
18
+ OPT_LIST = {
19
+ :gerrit => '--gerrit',
20
+ :topic_id => '--topic'
21
+ }.freeze
22
+
23
+ def options
24
+ [
25
+ ARGV::Opt.new(OPT_LIST[:gerrit],
26
+ info:"开启gerrit功能,如果没有对应远程分支则推送新分支,否则推送到审查分支(refs/for/**),默认未开启",
27
+ type: :boolean),
28
+ ARGV::Opt.new(OPT_LIST[:topic_id],
29
+ info:"指定一组变更的分类topic,若未指定则自动生成,默认未开启,开启后强制开启Gerrit功能。mgit push --topic 12345 = git push origin HEAD:refs/for/<branch>%topic=12345",
30
+ type: :string),
31
+ ].concat(super)
32
+ end
33
+
34
+ attr_reader :topic_id
35
+ attr_reader :gerrit_enabled
36
+ def __setup_option_value(argv)
37
+ group_id_opt = argv.opt(OPT_LIST[:topic_id])
38
+ if group_id_opt
39
+ @topic_id = group_id_opt.value
40
+ elsif MGIT_PUSH_TOPIC_ENABLED
41
+ @topic_id = SecureRandom.uuid
42
+ end
43
+
44
+ @gerrit_enabled = !@topic_id.nil? || argv.opt_list.did_set_opt?(OPT_LIST[:gerrit]) || MGIT_PUSH_GERRIT_ENABLED
45
+ end
46
+
47
+ def execute(argv)
48
+ __setup_option_value(argv)
49
+ if argv.git_opts&.length > 0
50
+ raws_string = ''
51
+ argv.raw_opts.each do |raws|
52
+ raws_string += ' '
53
+ raws_string += raws.join(' ')
54
+ end
55
+ Foundation.help!("禁止使用参数 #{argv.git_opts}\n" + Output.remind_message("建议直接使用mgit #{argv.cmd}#{raws_string}"))
56
+ end
57
+ Workspace.check_branch_consistency
58
+
59
+ Output.puts_start_cmd
60
+
61
+ # 获取远程仓库当前分支信息
62
+ Workspace.pre_fetch
63
+
64
+ do_repos = []
65
+ diverged_repos = []
66
+ do_nothing_repos = []
67
+ detached_repos = []
68
+ no_remote_repos = []
69
+ no_tracking_repos = []
70
+ remote_inconsist_repos = []
71
+ dirty_repos = []
72
+
73
+ Output.puts_processing_message("检查各仓库状态...")
74
+ Workspace.serial_enumerate_with_progress(all_repos) { |repo|
75
+ Timer.start(repo.name)
76
+
77
+ url_consist = repo.url_consist?
78
+ branch_status = repo.status_checker.branch_status
79
+ remote_inconsist_repos.push(repo) if !url_consist
80
+
81
+ if branch_status == Repo::Status::GIT_BRANCH_STATUS[:diverged]
82
+ diverged_repos.push(repo)
83
+ # 仅超前且url一致的仓库直接加入到操作集
84
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:ahead] && url_consist
85
+ do_repos.push(repo)
86
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
87
+ no_remote_repos.push(repo)
88
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
89
+ no_tracking_repos.push(repo)
90
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
91
+ detached_repos.push(repo)
92
+ else
93
+ do_nothing_repos.push(repo.name)
94
+ end
95
+
96
+ if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
97
+ dirty_repos.push(repo)
98
+ end
99
+
100
+ Timer.stop(repo.name)
101
+ }
102
+ Output.puts_success_message("检查完成!\n")
103
+
104
+ # 将没有远程分支的仓库纳入到本次操作的仓库中
105
+ do_repos += no_remote_repos
106
+ no_remote_repos = []
107
+
108
+ if diverged_repos.length > 0 ||
109
+ detached_repos.length > 0 ||
110
+ no_remote_repos.length > 0 ||
111
+ no_tracking_repos.length > 0 ||
112
+ remote_inconsist_repos.length > 0 ||
113
+ dirty_repos.length > 0
114
+ remind_repos = []
115
+ remind_repos.push(['远程分支不存在', no_remote_repos.map { |e| e.name }]) if no_remote_repos.length > 0
116
+ remind_repos.push(['未追踪远程分支(建议:mgit branch -u origin/<branch>)', no_tracking_repos.map { |e| e.name }]) if no_tracking_repos.length > 0
117
+ remind_repos.push(['HEAD游离,当前不在任何分支上', detached_repos.map { |e| e.name }]) if detached_repos.length > 0
118
+ remind_repos.push(['当前分支与远程分支分叉,需先pull本地合并', diverged_repos.map { |e| e.name }]) if diverged_repos.length > 0
119
+ remind_repos.push(['实际url与配置不一致', remote_inconsist_repos.map { |e| e.name }]) if remote_inconsist_repos.length > 0
120
+ remind_repos.push(['有本地改动', dirty_repos.map { |e| e.name }]) if dirty_repos.length > 0
121
+ Output.interact_with_multi_selection_combined_repos(remind_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 强制执行', 'c: 终止']) { |input|
122
+ if input == 'b'
123
+ do_repos += diverged_repos
124
+ do_repos += detached_repos
125
+ do_repos += no_remote_repos
126
+ do_repos += no_tracking_repos
127
+ do_repos += remote_inconsist_repos
128
+ do_repos.uniq! { |repo| repo.name }
129
+ elsif input == 'c' || input != 'a'
130
+ Output.puts_cancel_message
131
+ return
132
+ end
133
+ }
134
+ end
135
+ if do_repos.length == 0
136
+ Output.puts_remind_message("仓库均无新提交,无须执行!")
137
+ return
138
+ end
139
+ HooksManager.execute_mgit_pre_push_hook(argv.cmd, argv.pure_opts, do_repos.map { |e| e.config })
140
+
141
+ # 跳过无法处理的异常状态仓库
142
+ skip_repos = do_repos.select { |repo|
143
+ branch_status = repo.status_checker.branch_status
144
+ branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached] ||
145
+ branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
146
+ }
147
+ Output.puts_remind_block(skip_repos.map { |e| e.name }, "以上仓库无法强制执行,已跳过。") if skip_repos.length > 0
148
+ do_repos -= skip_repos
149
+ if do_repos.length == 0
150
+ Output.puts_success_message("仓库均无新提交,无须执行!")
151
+ return
152
+ end
153
+
154
+ # 找到本次操作仓库中推新分支的仓库
155
+ no_remote_repo_names = do_repos.select { |repo|
156
+ branch_status = repo.status_checker.branch_status
157
+ branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
158
+ }.map { |repo| repo.name }
159
+
160
+ count_msg = ",另有#{do_nothing_repos.length}个仓库无须执行" if do_nothing_repos.length > 0
161
+ Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始push以上仓库#{count_msg}...")
162
+
163
+ # ------ 执行push ------
164
+ total_task = do_repos.length
165
+ Output.update_progress(total_task, 0)
166
+
167
+ config_repo_arr = do_repos.select { |repo| repo.config.is_config_repo }
168
+ do_repos_without_config_repo = do_repos - config_repo_arr
169
+
170
+ sub_error_repos, sub_cr_repos = __execute_push(argv, do_repos_without_config_repo) { |progress|
171
+ Output.update_progress(total_task, progress)
172
+ }
173
+
174
+ # 保证最后操作主仓库,便于流水线进行模块注册
175
+ sub_task_count = do_repos_without_config_repo.length
176
+ config_error_repos, config_cr_repos = __execute_push(argv, config_repo_arr) { |progress|
177
+ Output.update_progress(total_task, progress + sub_task_count)
178
+ }
179
+ puts "\n"
180
+ # ----------------------
181
+ # 显示错误仓库信息
182
+ error_repos = sub_error_repos.merge(config_error_repos)
183
+ Workspace.show_error(error_repos) if error_repos.length > 0
184
+
185
+ # 显示成功推了新分支的仓库
186
+ success_push_branch_repo_names = no_remote_repo_names - error_repos.keys
187
+ if success_push_branch_repo_names.length > 0
188
+ Output.puts_remind_block(success_push_branch_repo_names, "为以上仓库推送了新分支。")
189
+ puts "\n"
190
+ end
191
+
192
+ # 显示成功仓库的评审链接
193
+ if gerrit_enabled
194
+ success_output = ''
195
+ all_cr_repos = sub_cr_repos.keys + config_cr_repos.keys
196
+ all_cr_repos.uniq!
197
+ all_cr_repos.each do |repo_name|
198
+ cr_url = sub_cr_repos[repo_name] || config_cr_repos[repo_name]
199
+ success_output += Output.generate_title_block(repo_name, has_separator: false) { cr_url } + "\n\n"
200
+ end
201
+
202
+ if success_output.length > 0
203
+ Output.puts_remind_message("以下本地提交代码评审链接,请联系仓库负责人评审后合入:")
204
+ puts success_output
205
+ end
206
+
207
+ # 显示topic id
208
+ if topic_id
209
+ success_push_code_repo_names = do_repos.map { |e| e.name } - error_repos.keys - success_push_branch_repo_names
210
+ if success_push_code_repo_names.length > 0
211
+ Output.puts_remind_message("本次push的topic id:#{topic_id}\n") if !topic_id.nil? && topic_id.length > 0
212
+ end
213
+ end
214
+ end
215
+
216
+ # 显示成功信息
217
+ if error_repos.empty?
218
+ Output.puts_succeed_cmd(argv.absolute_cmd)
219
+ Timer.show_time_consuming_repos
220
+ elsif topic_id
221
+ # 显示失败后的操作提示,若全部成功则不显示
222
+ group_repo_names = error_repos.keys
223
+ group_repo_name_str = group_repo_names.join(' ')
224
+ is_all = Workspace.is_all_exec_sub_repos_by_name?(group_repo_names)
225
+ mrepo_str = is_all ? '' : " --mrepo #{group_repo_name_str}"
226
+ Output.puts_processing_block(group_repo_names, "以上仓库组推送失败,请处理后用以下指令再次推送:\n\n mgit push --topic #{topic_id}#{mrepo_str}\n")
227
+ end
228
+
229
+ end
230
+
231
+ private
232
+
233
+ def __execute_push(argv, do_repos)
234
+ mutex = Mutex.new
235
+ error_repos = {}
236
+ cr_repos = {}
237
+ task_count = 0
238
+
239
+ Workspace.concurrent_enumerate(do_repos) { |repo|
240
+ cmd, opt = __parse_cmd_and_opt(repo)
241
+ git_cmd = repo.git_cmd(cmd, opt)
242
+
243
+ Utils.execute_shell_cmd(git_cmd) { |stdout, stderr, status|
244
+ mutex.lock
245
+ error_msg, cr_url = __process_push_result(repo, stdout, stderr, status)
246
+ error_repos[repo.name] = error_msg if error_msg
247
+ cr_repos[repo.name] = cr_url if cr_url
248
+
249
+ task_count += 1
250
+ yield(task_count) if block_given?
251
+ mutex.unlock
252
+ }
253
+ }
254
+
255
+ [error_repos, cr_repos]
256
+ end
257
+
258
+ #
259
+ # @return [String, String] error_message, code_review_url
260
+ #
261
+ #
262
+ def __process_push_result(repo, stdout, stderr, status)
263
+ # 标记状态更新
264
+ repo.status_checker.refresh
265
+
266
+ # 本地成功但远程失败此时status.success? == true,解析以检测这个情况
267
+ repo_msg_parser = GitMessageParser.new(repo.config.url)
268
+ check_msg = repo_msg_parser.parse_push_msg(stderr)
269
+
270
+ # 本地和远程同时成功
271
+ if status.success? && check_msg.nil?
272
+ cr_url = repo_msg_parser.parse_code_review_url(stdout) || repo_msg_parser.parse_code_review_url(stderr) if gerrit_enabled
273
+ elsif status.success? && !check_msg.nil?
274
+ # 本地失败
275
+ # check_msg = error_msg
276
+ else
277
+ check_msg = stderr
278
+ check_msg += stdout if stdout.length > 0
279
+ end
280
+ [check_msg, cr_url]
281
+ end
282
+
283
+ def __parse_cmd_and_opt(repo)
284
+ cmd = 'push'
285
+ if repo.status_checker.branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
286
+ opt = "-u origin #{repo.status_checker.current_branch}"
287
+ else
288
+ opt = "origin HEAD:#{repo.status_checker.current_branch}"
289
+ if gerrit_enabled
290
+ opt = "origin HEAD:refs/for/#{repo.status_checker.current_branch}"
291
+ opt += "%topic=" + topic_id if topic_id
292
+ end
293
+ end
294
+ [cmd, opt]
295
+ end
296
+
297
+ def enable_repo_selection
298
+ return true
299
+ end
300
+
301
+ def self.description
302
+ return "更新远程分支和对应的数据对象。"
303
+ end
304
+
305
+ def self.usage
306
+ return "mgit push [<git-push-option>|--gerrit] [(--mrepo|--el-mrepo) <repo>...] [--topic] [--help]"
307
+ end
308
+
309
+ end
310
+
311
+ end