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,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
|