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,73 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope 查询多仓库的日志
6
+ #
7
+ class Log < BaseCommand
8
+
9
+ OPT_LIST = {
10
+ :number => '--number',
11
+ :number_s => '-n'
12
+ }.freeze
13
+
14
+ def options
15
+ return [
16
+ ARGV::Opt.new(OPT_LIST[:number], short_key:OPT_LIST[:number_s], default:500, info:"指定需要显示的提交log个数,默认500。", type: :string)
17
+ ].concat(super)
18
+ end
19
+
20
+ def revise_option_value(opt)
21
+ opt.value = Integer(opt.value) if opt.key == OPT_LIST[:number]
22
+ end
23
+
24
+ def execute(argv)
25
+ repo_name = parse_repo_name(argv)
26
+ repo = all_repos.find { |e| e.name.downcase == repo_name.downcase }
27
+ number = argv.opt(OPT_LIST[:number]).value
28
+ if repo.nil?
29
+ Output.puts_fail_message("未找到与输入仓库名\"#{repo_name}\"匹配的仓库,请重试!") && return
30
+ return
31
+ end
32
+ # print(Output.processing_message("正在提取#{repo.name}最新的#{number}条log信息..."))
33
+ success, output = repo.execute_git_cmd(argv.cmd, "-n #{number}")
34
+ if success
35
+ if output.length > 0
36
+ Output.puts_in_pager(output.gsub(/commit.*/) { |s| Output.yellow_message(s) })
37
+ # print("\r")
38
+ else
39
+ Output.puts_remind_message("无提交记录")
40
+ end
41
+ else
42
+ Output.puts_fail_message("执行失败:#{output}")
43
+ end
44
+ end
45
+
46
+ def parse_repo_name(argv)
47
+ return nil if argv.git_opts.nil?
48
+ repo = argv.git_opts.split(' ')
49
+ extra_opts = repo.select { |e| argv.is_option?(e) }
50
+ Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
51
+ Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit log some_repo") if repo.length == 0
52
+ Foundation.help!("仅允许查询一个仓库!") if repo.length > 1
53
+ repo.first
54
+ end
55
+
56
+ def is_integer?(string)
57
+ true if Integer(string) rescue false
58
+ end
59
+
60
+ def enable_short_basic_option
61
+ true
62
+ end
63
+
64
+ def self.description
65
+ "输出指定(单个)仓库的提交历史。"
66
+ end
67
+
68
+ def self.usage
69
+ "mgit log <repo> [-n] [-h]"
70
+ end
71
+ end
72
+
73
+ end
@@ -0,0 +1,381 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope 类似 git merge
6
+ #
7
+ class Merge < BaseCommand
8
+
9
+ PROGRESS_STAGE = {
10
+ :new_start => 0,
11
+ :did_pull_config => 1,
12
+ :did_refresh_config => 2,
13
+ :did_pull_sub => 3
14
+ }.freeze
15
+ PROGRESS_STAGE_KEY = 'progress_stage'
16
+
17
+ PROGRESS_AUTO = 'auto_exec'
18
+
19
+ OPT_LIST = {
20
+ :pull => '--pull'
21
+ }.freeze
22
+
23
+ def options
24
+ return [
25
+ ARGV::Opt.new(OPT_LIST[:pull], info:'可选参数,指定后在合并仓库前会自动拉取远程分支更新代码,否则会有交互式询问。如:"mgit merge --pull"。', type: :boolean)
26
+ ].concat(super)
27
+ end
28
+
29
+ def __progress_type
30
+ OperationProgressManager::PROGRESS_TYPE[:merge]
31
+ end
32
+
33
+ def execute(argv)
34
+ return if do_abort(argv)
35
+
36
+ Workspace.check_branch_consistency
37
+
38
+ Output.puts_start_cmd
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
+ cmd = context.cmd
59
+ opts = context.opts
60
+ config_error = continue_execute(cmd, opts, config_repo, context.other[PROGRESS_STAGE_KEY], context.other[PROGRESS_AUTO])
61
+ if config_error
62
+ Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
63
+ return
64
+ end
65
+ else
66
+ # 处于中间态则提示
67
+ if OperationProgressManager.is_in_progress?(Workspace.root, __progress_type)
68
+ if Output.continue_with_user_remind?("当前处于操作中间态,建议取消操作并执行\"mgit merge --continue\"继续操作未完成仓库。\n 继续执行将清除中间态并重新操作所有仓库,是否取消?")
69
+ Output.puts_cancel_message
70
+ return
71
+ end
72
+ end
73
+
74
+ cmd = argv.cmd
75
+ opts = argv.git_opts
76
+ if !opts.include?('--continue') && !opts.include?('--abort')
77
+ # 自动添加--no-ff
78
+ if !opts.include?('--no-ff') && !opts.include?('--ff') && !opts.include?('--ff-only') && !opts.include?('--squash')
79
+ opts += ' --no-ff'
80
+ end
81
+
82
+ # 检测提示
83
+ if !opts.include?('--no-commit') &&
84
+ !opts.include?('--commit') &&
85
+ !opts.include?('-m ') &&
86
+ Output.continue_with_user_remind?("未添加\"-m\"或\"--no-commit\",建议使用:\n 1. mgit merge <branch> -m \"xxx comment...\"\n 2. 或 mgit merge <branch> --no-commit\n 更新本次提交的comment,是否取消并重新输入?")
87
+
88
+ Output.puts_cancel_message
89
+ return
90
+
91
+ end
92
+ end
93
+
94
+ # 优先操作配置仓库
95
+ config_error = merge_config_repo(cmd, opts, config_repo, argv.opt_list.did_set_opt?(OPT_LIST[:pull]))
96
+ if config_error
97
+ Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
98
+ return
99
+ end
100
+ end
101
+
102
+ do_repos = []
103
+ dirty_repos = []
104
+ detached_repos = []
105
+ no_tracking_repos = []
106
+
107
+ Output.puts_processing_message("检查各仓库状态...")
108
+ Workspace.serial_enumerate_with_progress(all_repos) { |repo|
109
+ next if !config_repo.nil? && repo.name == config_repo.name
110
+
111
+ status = repo.status_checker.status
112
+ branch_status = repo.status_checker.branch_status
113
+
114
+ if status == Repo::Status::GIT_REPO_STATUS[:clean] &&
115
+ branch_status != Repo::Status::GIT_BRANCH_STATUS[:detached] &&
116
+ branch_status != Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
117
+ do_repos.push(repo)
118
+ else
119
+ if status == Repo::Status::GIT_REPO_STATUS[:dirty]
120
+ dirty_repos.push(repo)
121
+ end
122
+ if branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
123
+ detached_repos.push(repo)
124
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
125
+ no_tracking_repos.push(repo)
126
+ end
127
+ end
128
+ }
129
+ Output.puts_success_message("检查完成!\n")
130
+
131
+ if dirty_repos.length > 0 || no_tracking_repos.length > 0 || detached_repos.length > 0
132
+ remind_repos = []
133
+ remind_repos.push(['有本地改动', dirty_repos.map { |e| e.name }])
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(['HEAD游离,当前不在任何分支上', detached_repos.map { |e| e.name }]) if detached_repos.length > 0
136
+ Output.interact_with_multi_selection_combined_repos(remind_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 强制执行', 'c: 终止']) { |input|
137
+ if input == 'b'
138
+ do_repos += dirty_repos
139
+ do_repos += detached_repos
140
+ do_repos += no_tracking_repos
141
+ do_repos.uniq! { |repo| repo.name }
142
+ elsif input == 'c' || input != 'a'
143
+ Output.puts_cancel_message
144
+ return
145
+ end
146
+ }
147
+ end
148
+
149
+ error_repos = {}
150
+ if do_repos.length == 0
151
+ Output.puts_remind_message("没有仓库需要执行merge指令!") if config_repo.nil?
152
+ else
153
+ Output.puts_processing_message("开始merge子仓库...")
154
+ _, error_repos = Workspace.execute_git_cmd_with_repos(cmd, opts, do_repos)
155
+ end
156
+
157
+ if config_error.nil? && error_repos.length == 0
158
+ Output.puts_succeed_cmd("#{cmd} #{opts}")
159
+ end
160
+
161
+ # 清除中间态
162
+ OperationProgressManager.remove_progress(Workspace.root, __progress_type)
163
+ end
164
+
165
+ # 合并主仓库
166
+ #
167
+ # @param cmd [String] 合并指令
168
+ #
169
+ # @param opts [String] 合并参数
170
+ #
171
+ # @param repo [Repo] 配置仓库对象
172
+ #
173
+ # @param exec_repos [Array<Repo>] 本次操作的所有仓库(含配置仓库)
174
+ #
175
+ def merge_config_repo(cmd, opts, repo, auto_update)
176
+ if !repo.nil?
177
+ branch_status = repo.status_checker.branch_status
178
+ if branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
179
+ remind_config_repo_fail("主仓库\"#{repo.name}\"HEAD游离,当前不在任何分支上,无法执行!")
180
+ elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
181
+ remind_config_repo_fail("主仓库\"#{repo.name}\"未跟踪对应远程分支,无法执行!(需要执行'mgit branch -u origin/<branch>')")
182
+ elsif repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
183
+ remind_config_repo_fail("主仓库\"#{repo.name}\"有改动,无法执行\"#{cmd}\"!")
184
+ else
185
+ return continue_execute(cmd, opts, repo, PROGRESS_STAGE[:new_start], auto_update)
186
+ end
187
+ end
188
+ end
189
+
190
+ def continue_execute(cmd, opts, repo, check_point, auto_update)
191
+
192
+ # 现场信息
193
+ exec_subrepos = all_repos(except_config:true)
194
+ is_all = Workspace.is_all_exec_sub_repos?(exec_subrepos)
195
+ context = OperationProgressContext.new(__progress_type)
196
+ context.cmd = cmd
197
+ context.opts = opts
198
+ context.repos = is_all ? nil : exec_subrepos.map { |e| e.name } # nil表示操作所有子仓库
199
+ context.branch = repo.status_checker.current_branch(use_cache:true)
200
+
201
+ # 更新主仓库
202
+ update_config_repo(repo, context, auto_update) if check_point < PROGRESS_STAGE[:did_pull_config]
203
+
204
+ if check_point < PROGRESS_STAGE[:did_refresh_config]
205
+ # 操作主仓库
206
+ config_error = exec_config_repo(repo, cmd, opts)
207
+ return config_error if config_error
208
+ # 更新配置表
209
+ refresh_config(repo, context, auto_update)
210
+ end
211
+
212
+ if check_point < PROGRESS_STAGE[:did_pull_sub]
213
+ # 如果本次操作所有子仓库,则再次获取所有子仓库(因为配置表可能已经更新,子仓库列表也有更新,此处获取的仓库包含:已有的子仓库 + 合并后新下载仓库 + 从缓存弹出的仓库)
214
+ exec_subrepos = all_repos(except_config:true) if is_all
215
+ # 更新子仓库
216
+ update_subrepos(exec_subrepos, context, auto_update)
217
+ end
218
+
219
+ config_error
220
+ end
221
+
222
+ def update_config_repo(repo, context, auto)
223
+ if auto || Output.continue_with_user_remind?("即将合并主仓库,是否先拉取远程代码更新?")
224
+ Output.puts_processing_message("正在更新主仓库...")
225
+ success, output = repo.execute_git_cmd('pull', '')
226
+ if !success
227
+ context.other = {
228
+ PROGRESS_STAGE_KEY => PROGRESS_STAGE[:did_pull_config],
229
+ PROGRESS_AUTO => auto
230
+ }
231
+ OperationProgressManager.trap_into_progress(Workspace.root, context)
232
+ show_progress_error("主仓库更新失败", "#{output}")
233
+ else
234
+ Output.puts_success_message("更新成功!\n")
235
+ end
236
+ end
237
+ end
238
+
239
+ def exec_config_repo(repo, cmd, opts)
240
+ error = nil
241
+ Output.puts_processing_message("开始操作主仓库...")
242
+ success, output = repo.execute_git_cmd(cmd, opts)
243
+ if success
244
+ Output.puts_success_message("操作成功!\n")
245
+ else
246
+ Output.puts_fail_message("操作失败!\n")
247
+ error = output
248
+ end
249
+ return error
250
+ end
251
+
252
+ # 刷新配置表
253
+ def refresh_config(repo, context, auto)
254
+ begin
255
+ Workspace.update_config(strict_mode:false) { |missing_repos|
256
+ if missing_repos.length > 0
257
+ # 这里分支引导仅根据主仓库来进行,如果使用all_repos来作为引导
258
+ # 基准,可能不准确(因为all_repos可能包含merge分支已有的本地
259
+ # 仓库,而这些仓库所在分支可能五花八门,数量也可能多于处于正确
260
+ # 分支的仓库)。
261
+ success_missing_repos = Workspace.guide_to_checkout_branch(missing_repos, [repo], append_message:"拒绝该操作本次执行将忽略以上仓库")
262
+ all_repos.concat(success_missing_repos)
263
+ # success_missing_repos包含新下载的和当前分支已有的新仓库,其中已有仓库包含在@all_repos内,需要去重
264
+ all_repos.uniq! { |repo| repo.name }
265
+ end
266
+ }
267
+ refresh_context(context)
268
+ rescue Error => e
269
+ if e.type == MGIT_ERROR_TYPE[:config_generate_error]
270
+ context.other = {
271
+ PROGRESS_STAGE_KEY => PROGRESS_STAGE[:did_refresh_config],
272
+ PROGRESS_AUTO => auto
273
+ }
274
+ OperationProgressManager.trap_into_progress(Workspace.root, context)
275
+ show_progress_error("配置表生成失败", "#{e.msg}")
276
+ end
277
+ end
278
+ end
279
+
280
+ def update_subrepos(subrepos, context, auto)
281
+ if auto || Output.continue_with_user_remind?("即将合并子仓库,是否先拉取远程代码更新?")
282
+ Output.puts_processing_message("正在更新子仓库...")
283
+ _, error_repos = Workspace.execute_git_cmd_with_repos('pull', '', subrepos)
284
+ if error_repos.length > 0
285
+ context.other = {
286
+ PROGRESS_STAGE_KEY => PROGRESS_STAGE[:did_pull_sub],
287
+ PROGRESS_AUTO => auto
288
+ }
289
+ OperationProgressManager.trap_into_progress(Workspace.root, context)
290
+ show_progress_error("子仓库更新失败", "见上述输出")
291
+ else
292
+ Output.puts_success_message("更新成功!\n")
293
+ end
294
+ end
295
+ end
296
+
297
+ def refresh_context(context)
298
+ exec_subrepos = all_repos(except_config:true)
299
+ is_all = Workspace.is_all_exec_sub_repos?(exec_subrepos)
300
+ context.repos = is_all ? nil : exec_subrepos.map { |e| e.name } # nil表示操作所有子仓库
301
+ end
302
+
303
+ def remind_config_repo_fail(msg)
304
+ Output.puts_fail_message(msg)
305
+ if Output.continue_with_user_remind?("是否继续操作其余仓库?")
306
+ return
307
+ else
308
+ Output.puts_cancel_message
309
+ exit
310
+ end
311
+ end
312
+
313
+ def do_abort(argv)
314
+ if argv.git_opts.include?('--abort')
315
+ Output.puts_start_cmd
316
+ OperationProgressManager.remove_progress(Workspace.root, __progress_type)
317
+ do_repos = all_repos.select { |repo| repo.status_checker.is_in_merge_progress? }
318
+
319
+ if do_repos.length > 0
320
+ append_message = ",另有#{all_repos.length - do_repos.length}个仓库无须操作" if do_repos.length < all_repos.length
321
+ Output.puts_processing_block(do_repos.map { |e| e.name }, "开始操作以上仓库#{append_message}...")
322
+ _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, do_repos)
323
+ Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
324
+ else
325
+ Output.puts_success_message("没有仓库需要操作!")
326
+ end
327
+
328
+ return true
329
+ else
330
+ return false
331
+ end
332
+ end
333
+
334
+ def show_progress_error(summary, detail)
335
+ error = "#{summary} 已进入操作中间态。
336
+
337
+ 原因:
338
+ #{detail}
339
+
340
+ 可选:
341
+ - 使用\"mgit merge --continue\"继续合并。
342
+ - 使用\"mgit merge --abort\"取消合并。"
343
+ Foundation.help!(error, title:'暂停')
344
+ end
345
+
346
+ def enable_repo_selection
347
+ return true
348
+ end
349
+
350
+ def enable_continue_operation
351
+ return true
352
+ end
353
+
354
+ def self.description
355
+ return "合并两个或多个开发历史。"
356
+ end
357
+
358
+ def self.usage
359
+ return "mgit merge [<git-merge-option>] [--pull] [(--mrepo|--el-mrepo) <repo>...] [--help]\nmgit merge --continue\nmgit merge --abort"
360
+ end
361
+
362
+ # 判断是否需要fast forward
363
+ # def is_fast_forward?(argv, repos)
364
+ # git_opts = argv.git_opts(raw:false)
365
+ # git_opts.each { |opt_arr|
366
+ # if !argv.is_option?(opt_arr.first)
367
+ # repos.each { |repo|
368
+ # opt_arr.each { |branch|
369
+ # if repo.status_checker.is_ancestor_of_branch?(branch)
370
+ # return true
371
+ # end
372
+ # }
373
+ # }
374
+ # end
375
+ # }
376
+ # return false
377
+ # end
378
+
379
+ end
380
+
381
+ end