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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +85 -0
- data/lib/m-git.rb +66 -0
- data/lib/m-git/argv.rb +170 -0
- data/lib/m-git/argv/opt.rb +38 -0
- data/lib/m-git/argv/opt_list.rb +71 -0
- data/lib/m-git/argv/parser.rb +66 -0
- data/lib/m-git/base_command.rb +271 -0
- data/lib/m-git/command/add.rb +41 -0
- data/lib/m-git/command/branch.rb +90 -0
- data/lib/m-git/command/checkout.rb +106 -0
- data/lib/m-git/command/clean.rb +64 -0
- data/lib/m-git/command/commit.rb +84 -0
- data/lib/m-git/command/config.rb +202 -0
- data/lib/m-git/command/delete.rb +99 -0
- data/lib/m-git/command/fetch.rb +32 -0
- data/lib/m-git/command/forall.rb +81 -0
- data/lib/m-git/command/info.rb +74 -0
- data/lib/m-git/command/init.rb +324 -0
- data/lib/m-git/command/log.rb +73 -0
- data/lib/m-git/command/merge.rb +381 -0
- data/lib/m-git/command/pull.rb +364 -0
- data/lib/m-git/command/push.rb +311 -0
- data/lib/m-git/command/rebase.rb +348 -0
- data/lib/m-git/command/reset.rb +31 -0
- data/lib/m-git/command/self.rb +223 -0
- data/lib/m-git/command/stash.rb +189 -0
- data/lib/m-git/command/status.rb +135 -0
- data/lib/m-git/command/sync.rb +327 -0
- data/lib/m-git/command/tag.rb +67 -0
- data/lib/m-git/command_manager.rb +24 -0
- data/lib/m-git/error.rb +20 -0
- data/lib/m-git/foundation.rb +25 -0
- data/lib/m-git/foundation/constants.rb +107 -0
- data/lib/m-git/foundation/dir.rb +25 -0
- data/lib/m-git/foundation/duration_recorder.rb +92 -0
- data/lib/m-git/foundation/git_message_parser.rb +50 -0
- data/lib/m-git/foundation/lock.rb +32 -0
- data/lib/m-git/foundation/loger.rb +129 -0
- data/lib/m-git/foundation/mgit_config.rb +222 -0
- data/lib/m-git/foundation/operation_progress_manager.rb +139 -0
- data/lib/m-git/foundation/timer.rb +74 -0
- data/lib/m-git/foundation/utils.rb +361 -0
- data/lib/m-git/hooks_manager.rb +96 -0
- data/lib/m-git/manifest.rb +181 -0
- data/lib/m-git/manifest/cache_manager.rb +44 -0
- data/lib/m-git/manifest/internal.rb +182 -0
- data/lib/m-git/manifest/light_repo.rb +108 -0
- data/lib/m-git/manifest/light_repo_generator.rb +87 -0
- data/lib/m-git/manifest/linter.rb +153 -0
- data/lib/m-git/open_api.rb +427 -0
- data/lib/m-git/open_api/script_download_info.rb +37 -0
- data/lib/m-git/output/output.rb +461 -0
- data/lib/m-git/plugin_manager.rb +112 -0
- data/lib/m-git/repo.rb +133 -0
- data/lib/m-git/repo/status.rb +481 -0
- data/lib/m-git/repo/sync_helper.rb +254 -0
- data/lib/m-git/template.rb +9 -0
- data/lib/m-git/template/local_manifest.rb +27 -0
- data/lib/m-git/template/manifest_hook.rb +28 -0
- data/lib/m-git/template/post_download_hook.rb +29 -0
- data/lib/m-git/template/post_hook.rb +31 -0
- data/lib/m-git/template/pre_exec_hook.rb +31 -0
- data/lib/m-git/template/pre_hook.rb +29 -0
- data/lib/m-git/template/pre_push_hook.rb +32 -0
- data/lib/m-git/version.rb +6 -0
- data/lib/m-git/workspace.rb +648 -0
- data/lib/m-git/workspace/path_helper.rb +56 -0
- data/lib/m-git/workspace/workspace_helper.rb +159 -0
- data/m-git +1 -0
- data/mgit +19 -0
- 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,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
|