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,32 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+ module Template
5
+
6
+ PRE_CUSTOMIZED_PUSH_HOOK_TEMPLATE = '
7
+ #coding=utf-8
8
+
9
+ module MGitTemplate
10
+
11
+ class PrePushHook
12
+
13
+ # hook接口,用于接受push指令执行后的数据
14
+ #
15
+ # @param cmd [String] 本次执行指令
16
+ #
17
+ # @param opts [String] 本次执行指令参数
18
+ #
19
+ # @param mgit_root [String] mgit根目录
20
+ #
21
+ # @param exec_repos [Array<Manifest::LightRepo>] 本次执行指令的LightRepo数组
22
+ #
23
+ def self.run(cmd, opts, mgit_root, exec_repos)
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ '
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module MGit
3
+ VERSION = "2.5.4".freeze
4
+ end
5
+
6
+
@@ -0,0 +1,648 @@
1
+
2
+ require 'm-git/workspace/workspace_helper'
3
+ require 'm-git/workspace/path_helper'
4
+
5
+ module MGit
6
+ class Workspace
7
+ # @!attribute 仓库过滤器,过滤执行的仓库
8
+ #
9
+ RepoFilterConfig = Struct.new(:auto_exec, :include_lock, :select_repos, :exclude_repos)
10
+
11
+ class << self
12
+
13
+ include WorkspaceHelper
14
+ include PathHelper
15
+
16
+ attr_reader :root
17
+
18
+ attr_reader :config
19
+
20
+ def filter_config
21
+ @filter_config ||= RepoFilterConfig.new
22
+ end
23
+
24
+ # 配置mgit根目录
25
+ def setup_multi_repo_root(initial_root=nil)
26
+ if initial_root
27
+ @root = initial_root
28
+ return
29
+ end
30
+ @root = multi_repo_root_path
31
+ if @root.nil?
32
+ Foundation.help!('该目录不是多仓库目录!!!')
33
+ end
34
+ end
35
+
36
+ # 解析配置文件(完成后可使用@config获取配置对象)
37
+ #
38
+ # @param strict_mode [Boolean] default: true 严格模式下,出错直接终止,非严格模式下,出错则抛出异常
39
+ #
40
+ def setup_config(strict_mode:true)
41
+ # 记录旧config哈希
42
+ hash_sha1 = config.hash_sha1 if !config.nil?
43
+
44
+ # 调用manifest_hook
45
+ HooksManager.execute_manifest_hook(strict_mode:strict_mode)
46
+
47
+ # 解析config
48
+ @config = Manifest.parse(source_config_dir)
49
+
50
+ # 是否更新了配置表
51
+ did_update = hash_sha1.nil? || hash_sha1 != config.hash_sha1
52
+
53
+ # --- 同步工作区 ---
54
+ begin
55
+ # 从配置中读取
56
+ should_sync_workspace = MGitConfig.query_with_key(root, :syncworkspace)
57
+ if should_sync_workspace
58
+ # 同步工作区仓库(缓存或弹出)
59
+ Utils.sync_workspace(root, config)
60
+ else
61
+ # 若禁止同步的话,则将缓存弹出(若有的话)
62
+ config.light_repos.each { |light_repo|
63
+ if !Dir.exist?(light_repo.abs_dest(root))
64
+ pop(root, light_repo)
65
+ end
66
+ }
67
+ end
68
+ rescue Error => _
69
+ Output.puts_fail_message("MGit配置读取失败,跳过工作区同步!")
70
+ end
71
+
72
+ # ------------------
73
+
74
+ # --- 同步.git实体 ---
75
+ begin
76
+ # 从配置中读取
77
+ manage_git = MGitConfig.query_with_key(root, :managegit)
78
+ if !manage_git
79
+ Utils.pop_git_entity(root, config)
80
+ else
81
+ # 当前逻辑是如果配置了托管.git的话,此时不压入.git,而是只把新下载仓库的.git压入
82
+ # 后续如果有需要的话可以打开下面这个注释,这样在每次执行mgit指令时都会根据配置压入.git,起到同步的作用
83
+ # Workspace.push_git_entity(@root, @config)
84
+ end
85
+ rescue Error => _
86
+ Output.puts_fail_message("MGit配置读取失败,跳过.git同步!")
87
+ end
88
+ # ------------------
89
+ did_update
90
+ end
91
+
92
+ # 更新配置解析结果,并同步缺失仓库
93
+ def update_config(strict_mode:true)
94
+ Output.puts_processing_message("检查多仓库配置信息...")
95
+ if setup_config(strict_mode:strict_mode)
96
+ Output.puts_success_message("配置信息已更新!\n")
97
+ else
98
+ # 配置表未更新,直接返回
99
+ Output.puts_success_message("配置信息已为最新!\n")
100
+ return
101
+ end
102
+
103
+ origin_all_repo_names = all_repos.map { |e| e.name }
104
+ @all_repos, @exec_light_repos = nil, nil
105
+ missing_repos = []
106
+ missing_light_repos = setup_all_repos(strict_mode:false)
107
+ if missing_light_repos.length > 0
108
+
109
+ Utils.show_clone_info(root, missing_light_repos)
110
+ mutex = Mutex.new
111
+ error_repos = {}
112
+ task_count = 0
113
+ Output.update_progress(missing_light_repos.length, task_count)
114
+ concurrent_enumerate(missing_light_repos) { |light_repo|
115
+ error_message, _ = Repo::SyncHelper.sync_new_repo(light_repo, root)
116
+ mutex.lock
117
+ if error_message.nil?
118
+ missing_repos.push(Repo.generate_strictly(root, light_repo))
119
+ else
120
+ error_repos[light_repo.name] = error_message
121
+ end
122
+ task_count += 1
123
+ Output.update_progress(missing_light_repos.length, task_count)
124
+ mutex.unlock
125
+ }
126
+ if error_repos.length > 0
127
+ show_error(error_repos, action:'下载操作')
128
+ #(注意,如果不希望被下载仓库的.git实体被mgit管理,请执行\"mgit sync -n -o\",该方式将不会把.git实体放置到.mgit/souce-git中,更适合开发中途接入mgit的用户)
129
+ Foundation.help!("请检查原因并执行\"mgit sync -n\"重新下载。")
130
+ else
131
+ Output.puts_success_message("下载成功!\n")
132
+ end
133
+ end
134
+
135
+ # 加入将当前分支上本地已有的新仓库
136
+ current_branch_exist_new_repos = all_repos.select { |repo| !origin_all_repo_names.include?(repo.name) }
137
+ missing_repos += current_branch_exist_new_repos
138
+
139
+ # 新仓库当前分支可能并不是所需分支,可以再进一步操作
140
+ yield(missing_repos) if block_given? && missing_repos.length > 0
141
+ end
142
+
143
+ # 配置实体仓库对象(完成后可通过all_repos方法或@all_repos属性获取所有可执行仓库对象)
144
+ #
145
+ # @param strict_mode [Boolean] default: true 严格模式下,出错直接终止,非严格模式下,出错则抛出异常
146
+ #
147
+ def setup_all_repos(strict_mode: true)
148
+ repos = []
149
+ locked_repos = []
150
+ missing_light_repos = []
151
+
152
+ need_sync_repos = []
153
+ exec_light_repos.each do |light_repo|
154
+ need_sync_repos << light_repo unless Repo.check_git_dest(root, light_repo)
155
+ end
156
+
157
+ if need_sync_repos.length > 0
158
+ sync_new_repos(need_sync_repos)
159
+ end
160
+
161
+ exec_light_repos.each { |light_repo|
162
+
163
+ if strict_mode
164
+ repo = Repo.generate_strictly(root, light_repo)
165
+ else
166
+ repo, _ = Repo.generate_softly(root, light_repo)
167
+ end
168
+
169
+ if !repo.nil?
170
+ # 同步被锁仓库,不加入到本次执行中
171
+ if repo.config.lock
172
+ locked_repos.push(repo)
173
+ else
174
+ repos.push(repo)
175
+ end
176
+ else
177
+ missing_light_repos.push(light_repo)
178
+ end
179
+ }
180
+
181
+ # 同步锁定仓库
182
+ sync_locked_repos(locked_repos)
183
+
184
+ @locked_repos = locked_repos
185
+ @all_repos = repos
186
+ @all_repos += locked_repos if filter_config.include_lock
187
+
188
+ missing_light_repos
189
+ end
190
+
191
+ # 获取所有仓库
192
+ def all_repos(except_config:false)
193
+ setup_all_repos if @all_repos.nil?
194
+
195
+ if except_config
196
+ @all_repos.select { |e| !e.config.is_config_repo }
197
+ else
198
+ @all_repos
199
+ end
200
+ end
201
+
202
+ def locked_repos
203
+ setup_all_repos if @locked_repos.nil?
204
+ @locked_repos
205
+ end
206
+
207
+ # 提供一组light repo,更新repo对象
208
+ def update_all_repos(update_repos_names)
209
+ if update_repos_names.is_a?(Array)
210
+ update_light_repos = config.repo_list(selection:update_repos_names)
211
+ @exec_light_repos = update_light_repos
212
+ @all_repos = nil
213
+ setup_all_repos
214
+ end
215
+ end
216
+
217
+ # 抽取本次需要执行指令的仓库对应的LightRepo
218
+ #
219
+ # @return [Array<LightRepo>] 本次需要执行指令的LightRepo数组
220
+ #
221
+ def exec_light_repos
222
+ if @exec_light_repos.nil?
223
+ mrepo_opt = filter_config.select_repos
224
+ exclude_mrepo_opt = filter_config.exclude_repos
225
+
226
+ selected_repos = mrepo_opt.value if !mrepo_opt.nil?
227
+ excluded_repos = exclude_mrepo_opt.value if !exclude_mrepo_opt.nil?
228
+
229
+ # 校验参数是否正确
230
+ check_repo_names = []
231
+ check_repo_names.concat(selected_repos) if selected_repos
232
+ check_repo_names.concat(excluded_repos) if excluded_repos
233
+ unless check_repo_names.empty?
234
+ light_repo_names = config.light_repos.map(&:name)
235
+ extra_names = check_repo_names - light_repo_names
236
+ Foundation.help!("指定的仓库名称#{extra_names}不存在,请检查命令指定的参数") unless extra_names.empty?
237
+ end
238
+
239
+ @exec_light_repos = config.repo_list(selection:selected_repos, exclusion:excluded_repos)
240
+ end
241
+ @exec_light_repos
242
+ end
243
+
244
+ # -----------------------------------------------
245
+ # 校验mgit根目录
246
+ def multi_repo_root_path
247
+ dir = Dir.pwd
248
+ while File.dirname(dir) != dir do
249
+ Dir.foreach(dir) do |filename|
250
+ next unless File.directory?(File.join(dir, filename))
251
+ return dir if filename == '.mgit'
252
+ end
253
+ dir = File.dirname(dir)
254
+ end
255
+ nil
256
+ end
257
+
258
+ # 生成配置仓库的Repo对象
259
+ #
260
+ # @return [Repo] 配置仓库的Repo对象
261
+ #
262
+ def generate_config_repo
263
+ config_light_repo = exec_light_repos.find { |light_repo| light_repo.is_config_repo == true }
264
+ if !config_light_repo.nil?
265
+ repo, _ = Repo.generate_softly(root, config_light_repo)
266
+ return repo
267
+ else
268
+ return nil
269
+ end
270
+ end
271
+
272
+
273
+ def concurrent_enumerate_with_progress_bar(light_repos, message, &exec_handler)
274
+ Output.puts_processing_block(light_repos.map { |e| e.name }, message)
275
+ concurrent_enumerate_with_progress_bar_pure(light_repos, &exec_handler)
276
+ end
277
+
278
+ def concurrent_enumerate_with_progress_bar_pure(light_repos, &exec_handler)
279
+ task_count = 0
280
+ Output.update_progress(light_repos.length, task_count)
281
+ concurrent_enumerate(light_repos) { |light_repo|
282
+ exec_handler.call(light_repo) if exec_handler
283
+ Lock.mutex_puts {
284
+ task_count += 1
285
+ Output.update_progress(light_repos.length, task_count)
286
+ }
287
+ }
288
+ end
289
+
290
+ def sync_new_repos(repos)
291
+ return if repos.length == 0
292
+
293
+ error_repos = {}
294
+
295
+ Utils.show_clone_info(root, repos)
296
+ concurrent_enumerate_with_progress_bar_pure(repos) { |light_repo|
297
+ error_message, _ = Repo::SyncHelper.sync_new_repo(light_repo, root)
298
+ if !error_message.nil?
299
+ Lock.mutex_exec { error_repos[light_repo.name] = error_message }
300
+ end
301
+ }
302
+
303
+ # 执行下载后hook
304
+ repos_need_to_guide = []
305
+ concurrent_enumerate(repos) { |light_repo|
306
+ if error_repos[light_repo.name].nil? && # 下载未出错
307
+ !light_repo.lock && # 不是锁定仓库
308
+ !HooksManager.execute_post_download_hook(light_repo.name, light_repo.abs_dest(root)) # hook没有修改HEAD
309
+ repos_need_to_guide.push(light_repo)
310
+ end
311
+ }
312
+
313
+ # 引导分支切换
314
+ if repos_need_to_guide.length > 0
315
+ existing_repos = []
316
+ missing_repos = []
317
+ config.repo_list.each { |light_repo|
318
+ repo, _ = Repo.generate_softly(root, light_repo)
319
+ if !repo.nil?
320
+ if repos_need_to_guide.include?(light_repo)
321
+ missing_repos.push(repo)
322
+ else
323
+ existing_repos.push(repo)
324
+ end
325
+ end
326
+ }
327
+
328
+ repo_combo = missing_repos + existing_repos
329
+ if has_diff_branch?(repo_combo)
330
+ # 提示切换新下载仓库
331
+ guide_to_checkout_branch(missing_repos, existing_repos)
332
+ # 切换完成后如果所出分支不一致,给出提示
333
+ Output.puts_remind_message("注意,当前所有仓库并不处于统一分支,可通过\"mgit branch --compact\"查看。") if has_diff_branch?(repo_combo)
334
+ end
335
+ end
336
+
337
+ if error_repos.length > 0
338
+ show_error(error_repos, action:'锁定')
339
+ end
340
+ end
341
+
342
+ # 同步锁定仓库
343
+ def sync_locked_repos(repos)
344
+ return if repos.length == 0
345
+
346
+ Output.puts_processing_message("正在锁定#{repos.length}个仓库...")
347
+ mutex = Mutex.new
348
+ error_repos = {}
349
+ concurrent_enumerate(repos) { |repo|
350
+ error_message = Repo::SyncHelper.sync_exist_repo(repo, repo.config)
351
+ if !error_message.nil?
352
+ mutex.lock
353
+ error_repos[repo.name] = error_message
354
+ mutex.unlock
355
+ end
356
+ }
357
+ if error_repos.length > 0
358
+ show_error(error_repos, action:'锁定')
359
+ end
360
+ end
361
+
362
+ # 校验分支统一性
363
+ def check_branch_consistency
364
+ if has_diff_branch?(all_repos)
365
+ if filter_config.auto_exec || Output.continue_with_user_remind?("当前所有仓库并不处于同一分支(可通过\"mgit branch --compact\"查看),是否继续?")
366
+ return
367
+ else
368
+ Output.puts_cancel_message
369
+ exit
370
+ end
371
+ end
372
+ end
373
+
374
+ # 检查是否存在不一致的分支
375
+ #
376
+ # @return [Boolean] 是否存在不一致分支
377
+ #
378
+ def has_diff_branch?(repos)
379
+ return false if repos.length == 0
380
+
381
+ branch = nil
382
+ repos.each { |repo|
383
+ current_branch = repo.status_checker.current_branch(strict_mode:false)
384
+ # current_branch为空值意味着HEAD游离
385
+ if current_branch.nil? || (!branch.nil? && branch != current_branch)
386
+ return true
387
+ elsif branch.nil?
388
+ branch = current_branch
389
+ end
390
+ }
391
+ return false
392
+ end
393
+
394
+ # 引导切换新仓库的分支
395
+ #
396
+ # @param missing_repos [Array<Repo>] 缺失仓库
397
+ #
398
+ # @param exist_repos [Array<Repo>] 已有仓库
399
+ #
400
+ # @return [Array<Repo>] 切换成功的仓库
401
+ #
402
+ def guide_to_checkout_branch(new_repos, exist_repos, append_message:nil)
403
+ return [] if new_repos.length == 0 || exist_repos.length == 0
404
+
405
+ # 寻找最多仓库所在分支作为推荐
406
+ branch_count = {}
407
+ exist_repos.each { |repo|
408
+ branch = repo.status_checker.current_branch(strict_mode:false, use_cache:true)
409
+ if !branch.nil?
410
+ branch_count[branch] = 0 if branch_count[branch].nil?
411
+ branch_count[branch] += 1
412
+ end
413
+ }
414
+ # 若已有仓库都游离,无法推荐切换,则直接返回
415
+ return [] if branch_count.length == 0
416
+ max_branch = branch_count.max_by { |k,v| v }.first
417
+
418
+ branch_group = {}
419
+ new_repos.each { |repo|
420
+ branch = repo.status_checker.current_branch(strict_mode:false, use_cache:true)
421
+ if branch.nil?
422
+ branch = 'HEAD游离'
423
+ elsif branch == max_branch
424
+ next
425
+ end
426
+ branch_group[branch] = [] if branch_group[branch].nil?
427
+ branch_group[branch].push(repo.name)
428
+ }
429
+
430
+ # 如果新仓库都在当前推荐分支则不操作
431
+ if branch_group.length == 0
432
+ return new_repos
433
+ # 指定了auto则跳过提示,直接开始同步
434
+ elsif branch_group.length > 0 && (filter_config.auto_exec || Output.continue_with_combined_interact_repos?(branch_group.to_a, "检测到已有的仓库大部分(或全部)处于分支:#{max_branch}\n 是否将以上仓库切换到该分支#{"(#{append_message})" if !append_message.nil?}?", title:'新仓库所在分支'))
435
+
436
+ do_repos = []
437
+ remind_repos = []
438
+
439
+ new_repos.each { |repo|
440
+ if repo.status_checker.local_branch_exist?(max_branch) || repo.status_checker.remote_branch_exist?(max_branch)
441
+ do_repos.push(repo)
442
+ else
443
+ remind_repos.push(repo)
444
+ end
445
+ }
446
+
447
+ Output.puts_fail_block(remind_repos.map { |e| e.name }, "以上仓库无对应分支,已跳过,请自行处理。") if remind_repos.length > 0
448
+
449
+ if do_repos.length > 0
450
+ Output.puts_processing_message("开始切换分支...")
451
+ _, error_repos = execute_git_cmd_with_repos('', '', do_repos) { |repo|
452
+ opts = "#{remind_repos.include?(repo) ? '-b ' : ''}#{max_branch}"
453
+ ["checkout", opts]
454
+ }
455
+
456
+ if error_repos.length > 0
457
+ return do_repos.select { |repo| !error_repos.keys.include?(repo.name)}
458
+ else
459
+ Output.puts_success_message("分支切换成功!\n")
460
+ return do_repos
461
+ end
462
+
463
+ end
464
+ end
465
+
466
+ return []
467
+ end
468
+
469
+ # 并发遍历
470
+ def concurrent_enumerate(array)
471
+ begin
472
+ max_concurrent_count = MGitConfig.query_with_key(root, :maxconcurrentcount)
473
+ rescue Error => e
474
+ Foundation.help!(e.msg)
475
+ end
476
+
477
+ array.peach(max_concurrent_count) { |item|
478
+ yield(item) if block_given?
479
+ }
480
+ end
481
+
482
+ # 带进度条串行执行
483
+ def serial_enumerate_with_progress(array)
484
+ task_count = 0
485
+ Output.update_progress(array.length, task_count)
486
+ array.each { |repo|
487
+ yield(repo) if block_given?
488
+ task_count += 1
489
+ Output.update_progress(array.length, task_count)
490
+ }
491
+ end
492
+
493
+ # git指令透传执行
494
+ def execute_git_cmd_with_repos(cmd, git_opts, repos)
495
+ mutex = Mutex.new
496
+ success_repos = {}
497
+ error_repos = {}
498
+ task_count = 0
499
+ Output.update_progress(repos.length, task_count)
500
+ concurrent_enumerate(repos) { |repo|
501
+ # 允许针对仓库对指令进行加工
502
+ cmd, git_opts = yield(repo) if block_given?
503
+ success, output = repo.execute_git_cmd(cmd, git_opts)
504
+
505
+ mutex.lock
506
+ if success
507
+ success_repos[repo.name] = output
508
+ else
509
+ error_repos[repo.name] = output
510
+ end
511
+ task_count += 1
512
+ Output.update_progress(repos.length, task_count)
513
+ mutex.unlock
514
+ }
515
+ show_error(error_repos)
516
+ return success_repos, error_repos
517
+ end
518
+
519
+ # shell指令透传执行(串行)
520
+ def execute_common_cmd_with_repos(abs_cmd, repos)
521
+ success_repos = {}
522
+ error_repos = {}
523
+ task_count = 0
524
+ Output.update_progress(repos.length, task_count)
525
+ repos.each { |repo|
526
+ # 允许针对仓库对指令进行加工
527
+ abs_cmd = yield(repo) if block_given?
528
+ new_abs_cmd = "cd \"#{repo.path}\" && #{abs_cmd}"
529
+ success, output = repo.execute(new_abs_cmd)
530
+ if success
531
+ success_repos[repo.name] = output
532
+ else
533
+ error_repos[repo.name] = output
534
+ end
535
+ task_count += 1
536
+ Output.update_progress(repos.length, task_count)
537
+ }
538
+ show_error(error_repos)
539
+ return success_repos, error_repos
540
+ end
541
+
542
+ # shell指令透传执行(并发)
543
+ def execute_common_cmd_with_repos_concurrent(abs_cmd, repos)
544
+ mutex = Mutex.new
545
+ success_repos = {}
546
+ error_repos = {}
547
+ task_count = 0
548
+ Output.update_progress(repos.length, task_count)
549
+ concurrent_enumerate(repos) { |repo|
550
+ # 允许针对仓库对指令进行加工
551
+ abs_cmd = yield(repo) if block_given?
552
+ new_abs_cmd = "cd \"#{repo.path}\" && #{abs_cmd}"
553
+ success, output = repo.execute(new_abs_cmd)
554
+
555
+ mutex.lock
556
+ if success
557
+ success_repos[repo.name] = output
558
+ else
559
+ error_repos[repo.name] = output
560
+ end
561
+ task_count += 1
562
+ Output.update_progress(repos.length, task_count)
563
+ mutex.unlock
564
+ }
565
+ show_error(error_repos)
566
+ return success_repos, error_repos
567
+ end
568
+
569
+ # 显示错误信息
570
+ def show_error(error_repos, action:nil)
571
+ if error_repos.keys.length > 0
572
+ # 压缩错误信息
573
+ error_detail = {}
574
+ error_repos.each { |repo_name, error|
575
+ error = '指令执行失败,但无任何输出,请自行检查。' if error == ''
576
+ error_detail[error] = [] if error_detail[error].nil?
577
+ error_detail[error].push(repo_name)
578
+ }
579
+
580
+ # 显示错误
581
+ error_detail.each { |error, repos|
582
+ Output.puts_fail_block(repos, "以上仓库执行#{action}失败,原因:\n#{error}")
583
+ }
584
+ end
585
+ end
586
+
587
+ # 获取当前分支远程仓库信息
588
+ def pre_fetch
589
+ Output.puts_processing_message("获取远程仓库信息...")
590
+ mutex = Mutex.new
591
+ error_repos = {}
592
+ task_count = 0
593
+ Output.update_progress(all_repos.length, task_count)
594
+ concurrent_enumerate(all_repos) { |repo|
595
+ Timer.start(repo.name, use_lock:true)
596
+ git_cmd = repo.git_cmd('fetch', '')
597
+ Utils.execute_shell_cmd(git_cmd) { |stdout, stderr, status|
598
+ error_msg = GitMessageParser.new(repo.config.url).parse_fetch_msg(stderr)
599
+
600
+ mutex.lock
601
+ if !status.success? || !error_msg.nil?
602
+ error_repos[repo.name] = error_msg.nil? ? stderr : error_msg
603
+ end
604
+ task_count += 1
605
+ Output.update_progress(all_repos.length, task_count)
606
+ mutex.unlock
607
+ Timer.stop(repo.name, use_lock:true)
608
+
609
+ # 标记状态更新
610
+ repo.status_checker.refresh
611
+ }
612
+ }
613
+ if error_repos.length > 0
614
+ show_error(error_repos, action:"远程查询")
615
+ else
616
+ Output.puts_success_message("获取成功!\n") if error_repos.length == 0
617
+ end
618
+ end
619
+
620
+ # 检查是否是全部定义的子仓库
621
+ #
622
+ # @param subrepos [LightRepo] 仓库轻量对象集合
623
+ #
624
+ # @return [Boolean] 是否是所有子仓库
625
+ #
626
+ def is_all_exec_sub_repos?(subrepos)
627
+ if subrepos.is_a?(Array)
628
+ subrepo_names = subrepos.map { |e| e.name }
629
+ return is_all_exec_sub_repos_by_name?(subrepo_names)
630
+ end
631
+ end
632
+
633
+ # 检查是否是全部定义的子仓库
634
+ #
635
+ # @param subrepos [String] 仓库名字数组
636
+ #
637
+ # @return [Boolean] 是否是所有子仓库
638
+ #
639
+ def is_all_exec_sub_repos_by_name?(subrepo_names)
640
+ if subrepo_names.is_a?(Array)
641
+ all_subrepo_name = config.repo_list.select { |e| !e.lock && !e.is_config_repo }.map { |e| e.name }
642
+ return subrepo_names == all_subrepo_name
643
+ end
644
+ end
645
+
646
+ end
647
+ end
648
+ end