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,37 @@
1
+ #coding=utf-8
2
+ module MGit
3
+ class OpenApi
4
+
5
+ module DownloadResult
6
+ SUCCESS = 0
7
+ FAIL = 1
8
+ EXIST = 2
9
+ end
10
+
11
+ class ScriptDownloadInfo
12
+
13
+ # [String] 下载仓库名
14
+ attr_reader :repo_name
15
+
16
+ # [String] 下载仓库本地地址
17
+ attr_reader :repo_path
18
+
19
+ # [OpenApi::DOWNLOAD_RESULT] 下载结果
20
+ attr_reader :result
21
+
22
+ # [String] 执行输出,若出错,该变量保存出错信息,可能为nil
23
+ attr_reader :output
24
+
25
+ # [Float] 当前仓库在整个下载任务中所处进度(如10个任务,当前第5个下载完,则progress=0.5),并非单仓库下载进度。
26
+ attr_reader :progress
27
+
28
+ def initialize(repo_name, repo_path, result, output, progress)
29
+ @repo_name = repo_name
30
+ @repo_path = repo_path
31
+ @result = result
32
+ @output = output
33
+ @progress = progress
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,461 @@
1
+ #coding=utf-8
2
+
3
+ require 'm-git/foundation'
4
+
5
+ module MGit
6
+ module Output
7
+ class << self
8
+
9
+ # --- 简单输出 ---
10
+
11
+ def puts_cancel_message
12
+ puts_fail_message("执行取消!")
13
+ end
14
+
15
+ def puts_succeed_cmd(cmd)
16
+ puts_success_message("指令[ mgit #{cmd} ]执行成功!")
17
+ end
18
+
19
+ def puts_start_cmd
20
+ puts_processing_message("开始执行...")
21
+ end
22
+
23
+ def puts_fail_cmd(cmd)
24
+ puts_fail_message("指令[ mgit #{cmd} ]执行失败!")
25
+ end
26
+
27
+ def puts_nothing_to_do_cmd
28
+ puts_remind_message("没有仓库需要执行指令!")
29
+ end
30
+
31
+ def puts_success_message(str)
32
+ MGit::Loger.info(str.strip)
33
+ puts success_message(str)
34
+ end
35
+
36
+ def puts_remind_message(str)
37
+ MGit::Loger.info(str)
38
+ puts remind_message(str)
39
+ end
40
+
41
+ def puts_terminate_message(str, title:nil)
42
+ MGit::Loger.error("#{title}: #{str}")
43
+ puts terminate_message(str, title:title)
44
+ end
45
+
46
+ def puts_fail_message(str)
47
+ MGit::Loger.error(str)
48
+ puts fail_message(str)
49
+ end
50
+
51
+ def puts_processing_message(str)
52
+ MGit::Loger.info(str)
53
+ puts processing_message(str)
54
+ end
55
+
56
+ def puts_fail_block(list, bottom_summary)
57
+ puts generate_fail_block(list, bottom_summary)
58
+ end
59
+
60
+ def puts_remind_block(list, bottom_summary)
61
+ puts generate_remind_block(list, bottom_summary)
62
+ end
63
+
64
+ def puts_processing_block(list, bottom_summary)
65
+ puts generate_processing_block(list, bottom_summary)
66
+ end
67
+
68
+ def puts_fail_combined_block(list_array, bottom_summary, title:nil)
69
+ puts generate_fail_combined_block(list_array, bottom_summary, title:title)
70
+ end
71
+
72
+ def puts_in_pager(output)
73
+ begin
74
+ pager = TTY::Pager.new
75
+ pager.page(output)
76
+ rescue => _
77
+ end
78
+ end
79
+
80
+ # --- 交互式输出 ---
81
+
82
+ def continue_with_user_remind?(msg)
83
+ puts blue_message("[?] #{msg} Y/n")
84
+ MGit::DurationRecorder.pause
85
+ input = nil
86
+ loop do
87
+ input = STDIN.gets.chomp.downcase
88
+ if input == 'y' || input == 'yes' || input == 'n' || input == 'no'
89
+ break
90
+ end
91
+ puts blue_message("[?] 输入不合法,#{msg} Y/n")
92
+ end
93
+ MGit::DurationRecorder.resume
94
+ return input == 'y' || input == 'yes'
95
+ end
96
+
97
+ def continue_with_interact_repos?(repos, msg)
98
+ output = generate_table(repos, separator:'|') + "\n"
99
+ output += blue_message("[?] #{msg} Y/n")
100
+ puts output
101
+ MGit::DurationRecorder.pause
102
+ input = nil
103
+ loop do
104
+ input = STDIN.gets.chomp.downcase
105
+ if input == 'y' || input == 'yes' || input == 'n' || input == 'no'
106
+ break
107
+ end
108
+ puts blue_message("[?] 输入不合法,#{msg} Y/n")
109
+ end
110
+ MGit::DurationRecorder.resume
111
+ return input == 'y' || input == 'yes'
112
+ end
113
+
114
+ def continue_with_combined_interact_repos?(repos_array, msg, title:nil)
115
+ output = generate_table_combination(repos_array, title:title, separator:'|') + "\n"
116
+ output += blue_message("[?] #{msg} Y/n")
117
+ puts output
118
+ MGit::DurationRecorder.pause
119
+ input = nil
120
+ loop do
121
+ input = STDIN.gets.chomp.downcase
122
+ if input == 'y' || input == 'yes' || input == 'n' || input == 'no'
123
+ break
124
+ end
125
+ puts blue_message("[?] 输入不合法,#{msg} Y/n")
126
+ end
127
+ MGit::DurationRecorder.resume
128
+ return input == 'y' || input == 'yes'
129
+ end
130
+
131
+ # 显示一组复合表格并显示多选项操作
132
+ #
133
+ # @param list_array [Array<Array>] 包含表格内容的数组,其中每个元素为数组,表示一张表格内容,list_array内容为:【【title1, list1】,【title2, list2】...】, title<String>为标题,list<Array>为单张表格元素数组,所有内容会被渲染为多张表格然后合并为一张
134
+ #
135
+ # @param msg [string] 交互消息
136
+ #
137
+ # @param selection [Array] 选型数组,如:【'a: 跳过并继续', 'b: 强制执行', 'c: 终止'】
138
+ #
139
+ def interact_with_multi_selection_combined_repos(list_array, msg, selection)
140
+ puts generate_table_combination(list_array, separator:'|')
141
+ puts blue_message("[?] #{msg},请选择操作:\n#{selection.join("\n")}")
142
+ MGit::DurationRecorder.pause
143
+ input = STDIN.gets.chomp
144
+ MGit::DurationRecorder.resume
145
+ yield(input) if block_given?
146
+ end
147
+
148
+ # --- 特定消息生成器 ---
149
+
150
+ def processing_message(str)
151
+ return yellow_message("[~] #{str}")
152
+ end
153
+
154
+ def remind_message(str)
155
+ return blue_message("[!] #{str}")
156
+ end
157
+
158
+ def success_message(str)
159
+ return green_message("[✔] #{str}")
160
+ end
161
+
162
+ def fail_message(str)
163
+ return red_message("[✘] #{str}")
164
+ end
165
+
166
+ def terminate_message(str, title:nil)
167
+ header = "执行终止"
168
+ header = title if !title.nil?
169
+ return red_message("[✘✘✘ #{header} ✘✘✘] #{str}")
170
+ end
171
+
172
+ # --- 有色输出生成器 ---
173
+
174
+ # 绿色提示信息
175
+ def green_message(str)
176
+ return "\033[32m#{str}\033[0m"
177
+ end
178
+
179
+ # 青色提示信息
180
+ def blue_message(str)
181
+ return "\033[36m#{str}\033[0m"
182
+ end
183
+
184
+ # 紫红色提示信息
185
+ def red_message(str)
186
+ return "\033[31m#{str}\033[0m"
187
+ end
188
+
189
+ # 黄色提示信息
190
+ def yellow_message(str)
191
+ return "\033[33m#{str}\033[0m"
192
+ end
193
+
194
+ def info_title(str)
195
+ return "\033[4m#{str}\033[0m"
196
+ end
197
+
198
+ # --- 格式化输出生成器 ---
199
+
200
+ def generate_fail_combined_block(list_array, bottom_summary, title:nil)
201
+ return '' if list_array.nil? || list_array.length == 0
202
+ output = generate_table_combination(list_array, title: title, separator:'|') + "\n"
203
+ output += fail_message(bottom_summary)
204
+ return output
205
+ end
206
+
207
+ def generate_remind_block(list, bottom_summary)
208
+ msg = generate_block(list, remind_message(bottom_summary))
209
+ MGit::Loger.info(bottom_summary)
210
+ return msg
211
+ end
212
+
213
+ def generate_fail_block(list, bottom_summary)
214
+ msg = generate_block(list, fail_message(bottom_summary))
215
+ MGit::Loger.info(bottom_summary)
216
+ return msg
217
+ end
218
+
219
+ def generate_processing_block(list, bottom_summary)
220
+ msg = generate_block(list, processing_message(bottom_summary))
221
+ MGit::Loger.info(bottom_summary)
222
+ return msg
223
+ end
224
+
225
+ def generate_block(list, bottom_summary)
226
+ return '' if list.nil? || list.length == 0
227
+ output = generate_table(list, separator:'|') + "\n"
228
+ output += bottom_summary
229
+ MGit::Loger.info(list)
230
+ return output
231
+ end
232
+
233
+ def generate_title_block(title, has_separator:true)
234
+ title = "--- #{title} ---"
235
+ separator = ''
236
+ separator = "\n" + '=' * string_length_by_ascii(title) if has_separator
237
+ output = blue_message(title + separator) + "\n"
238
+ output += yield(output)
239
+ return output
240
+ end
241
+
242
+ # 生成合成表格(将多个表格融合为一个)
243
+ #
244
+ # @param list_array [Array<Array>] 包含表格内容的数组,其中每个元素为数组,表示一张表格内容,list_array内容为:【【title1, list1】,【title2, list2】...】, title<String>为标题,list<Array>为单张表格元素数组,所有内容会被渲染为多张表格然后合并为一张
245
+ #
246
+ # @param title [String] default: nil 表格标题
247
+ #
248
+ # @param separator [String] default: '' 表格内元素分割线
249
+ #
250
+ # @return [String] 生成的合成表格
251
+ #
252
+ def generate_table_combination(list_array, title:nil, separator:'')
253
+ table_width = -1
254
+ head_separator = "| "
255
+ middle_separator = " #{separator} "
256
+ tail_separator = " |"
257
+ head_tail_padding = head_separator.length + tail_separator.length
258
+
259
+ list_array.each { |list|
260
+ items = list.last
261
+ max_table_width, _, _ = calculate_table_info(items, head_separator, middle_separator, tail_separator, title:list.first)
262
+ table_width = max_table_width if !max_table_width.nil? && max_table_width > table_width
263
+ }
264
+
265
+ output = ''
266
+ if !title.nil? && table_width > 0
267
+
268
+ title_length = string_length_by_ascii(title)
269
+ table_width = title_length + head_tail_padding if table_width < title_length + head_tail_padding
270
+
271
+ space = secure(table_width - head_tail_padding - title_length) / 2
272
+ head_line = head_separator + ' ' * space + title
273
+ head_line = head_line + ' ' * secure(table_width - string_length_by_ascii(head_line) - tail_separator.length) + tail_separator + "\n"
274
+
275
+ output += '-' * table_width + "\n"
276
+ output += head_line
277
+ end
278
+
279
+ list_array.each_with_index { |list, idx|
280
+ sub_title = list.first if !list.first.nil? && list.first != ''
281
+ output += generate_table(list.last, title:sub_title, separator:separator, fixed_width:table_width, hide_footer_line:idx != list_array.length - 1)
282
+ }
283
+ return output
284
+ end
285
+
286
+ # 生成表格
287
+ #
288
+ # @param list [Array] 包含表格中显示内容
289
+ #
290
+ # @param title [String] default: nil 表格标题
291
+ #
292
+ # @param separator [String] default: '' 表格内部分割线
293
+ #
294
+ # @param fixed_width [Number] default: -1 可指定表格宽度,若指定宽度大于计算所得最大宽度,则使用该指定宽度
295
+ #
296
+ # @param hide_footer_line [Boolean] default: false 隐藏底部分割线
297
+ #
298
+ # @return [String] 已生成的表格字符串
299
+ #
300
+ def generate_table(list, title:nil, separator:'', fixed_width:-1, hide_footer_line:false)
301
+ return '' if list.nil? || list.length == 0
302
+
303
+ output = ''
304
+ head_separator = "| "
305
+ middle_separator = " #{separator} "
306
+ tail_separator = " |"
307
+ head_tail_padding = head_separator.length + tail_separator.length
308
+
309
+ max_table_width, column, max_meta_display_length_by_ascii = calculate_table_info(list, head_separator, middle_separator, tail_separator, title:title, fixed_width:fixed_width)
310
+
311
+ if !max_table_width.nil? && !column.nil? && !max_meta_display_length_by_ascii.nil?
312
+ if !title.nil?
313
+ title_length = string_length_by_ascii(title)
314
+ title = head_separator + title + ' ' * secure(max_table_width - title_length - head_tail_padding) + tail_separator
315
+
316
+ max_table_width = title_length + head_tail_padding if max_table_width < title_length + head_tail_padding
317
+ output += '-' * max_table_width + "\n"
318
+ output += title + "\n"
319
+
320
+ # 处理标题下的分割线
321
+ output += head_separator + '-' * title_length + ' ' * secure(max_table_width - head_tail_padding - title_length) + tail_separator + "\n"
322
+ else
323
+ output += '-' * max_table_width + "\n"
324
+ end
325
+
326
+ list.each_slice(column).to_a.each { |row|
327
+ line = head_separator
328
+ row.each_with_index { |item, idx|
329
+ # 最大显示宽度由纯ascii字符个数度量,而输出时中文占2个ascii字符宽度
330
+ # ljust方法以字符做计算,一个汉字会被认为是一个字符,但占了2单位宽度,需要把显示宽度根据汉字个数做压缩,如:
331
+ # 最长显示字符(纯ascii字符):'abcdef1234', 字符长度10,占10个ascii字符显示宽度
332
+ # 需要输出字符串:'[删除]abc',字符长度7,但有2个汉字,占9个ascii字符显示宽度
333
+ # 因此,ljust接受宽度字符个数:10 - 2 = 8
334
+ # 即对于'[删除]abc'这样的字符串,8个字符对应的ascii字符显示宽度('[删除]abc ', 注意8个字符宽度此时有一个空格)和'abcdef1234'显示宽度等长
335
+ line += item.ljust(max_meta_display_length_by_char(item, max_meta_display_length_by_ascii))
336
+ line += middle_separator if idx != row.length - 1
337
+ }
338
+ last_line_space = ' ' * secure(max_table_width - string_length_by_ascii(line) - tail_separator.length)
339
+ line += last_line_space + tail_separator
340
+ output += line + "\n"
341
+ }
342
+ output += '-' * max_table_width if !hide_footer_line
343
+ else
344
+ if list.length > 1
345
+ output = list.join("\n")
346
+ else
347
+ output = list.first + "\n"
348
+ end
349
+ end
350
+
351
+ return output
352
+ end
353
+
354
+ # 计算表格信息
355
+ #
356
+ # @param list [Array] 包含表格中显示内容
357
+ #
358
+ # @param head_separator [String] 表格头部分割线
359
+ #
360
+ # @param middle_separator [String] 表格中部分割线
361
+ #
362
+ # @param tail_separator [String] 表格尾部分割线
363
+ #
364
+ # @param fixed_width [Number] default: -1 可指定表格宽度,若指定宽度大于计算所得最大宽度,则使用该指定宽度
365
+ #
366
+ # @return [Objtct...] 返回表格最大宽度,表格列数,表格中一个项目显示的最大长度
367
+ #
368
+ def calculate_table_info(list, head_separator, middle_separator, tail_separator, title:nil, fixed_width:-1)
369
+ return nil if list.nil? || list.length == 0
370
+
371
+ head_tail_padding = head_separator.length + tail_separator.length
372
+
373
+ max_meta_display_length_by_ascii = -1
374
+ list.each { |item|
375
+ display_length = string_length_by_ascii(item)
376
+ if max_meta_display_length_by_ascii < display_length
377
+ max_meta_display_length_by_ascii = display_length
378
+ end
379
+ }
380
+
381
+ # 终端宽度。
382
+ # -1:减去最后一行"\n"
383
+ screen_width = `tput cols`.to_i - 1
384
+ if screen_width > 0 && screen_width > max_meta_display_length_by_ascii + head_tail_padding
385
+ # 定义:
386
+ # n:列数
387
+ # a:单个item最大长度
388
+ # b:分隔字符长度
389
+ # l:表格宽度减去前后padding的长度
390
+ # 如:| [abc]def | [def]abcd | [zz]asdf |
391
+ # 有: |<------------ l ------------>|
392
+ # |<- a -->|
393
+ # n=3,a=9,b=3(' | '.length == 3),l=31
394
+
395
+ # 计算l
396
+ raw_length = screen_width - head_tail_padding
397
+
398
+ # 计算列数:n * a + (n - 1) * b = l => n = (l + b) / (a + b)
399
+ column = (raw_length + middle_separator.length) / (max_meta_display_length_by_ascii + middle_separator.length)
400
+
401
+ # 如果计算得到的列数小于item个数,那么取item个数为最大列数
402
+ column = list.length if column > list.length
403
+
404
+ # 计算一个item的平均长度“al”,由
405
+ # 1. n * a + (n - 1) * b = l
406
+ # 2. al = l / n
407
+ # => al = (n * (a + b) - b) / n
408
+ average_length = (column * (max_meta_display_length_by_ascii + middle_separator.length) - middle_separator.length) / column.to_f
409
+
410
+ # 表格最大宽度为:n * al + head_tail_padding
411
+ max_table_width = (column * average_length + head_tail_padding).ceil
412
+
413
+ # 如果title最宽,则使用title宽度
414
+ if !title.nil? && title.is_a?(String)
415
+ title_length = string_length_by_ascii(head_separator + title + tail_separator)
416
+ max_table_width = title_length if max_table_width < title_length && title_length < screen_width
417
+ end
418
+
419
+ # 如果指定了一个合理的宽度,则使用该宽度
420
+ max_table_width = fixed_width if max_table_width < fixed_width && fixed_width <= screen_width
421
+
422
+ return max_table_width, column, max_meta_display_length_by_ascii
423
+ else
424
+ return nil
425
+ end
426
+ end
427
+
428
+ # 输出时中文占2个字符宽度,根据字符串中的汉字个数重新计算字符串长度(以纯ascii字符作度量)
429
+ def string_length_by_ascii(str)
430
+ chinese_chars = str.scan(/\p{Han}/)
431
+ length = str.length + chinese_chars.length
432
+ return length
433
+ end
434
+
435
+ # 输出时中文占2个字符宽度,根据字符串中的汉字个数重新计算最大显示字符长度(以包括汉字在内的字符作度量)
436
+ def max_meta_display_length_by_char(str, max_meta_display_length_by_ascii)
437
+ chinese_chars = str.scan(/\p{Han}/)
438
+ return max_meta_display_length_by_ascii - chinese_chars.length
439
+ end
440
+
441
+ # 保证数字大于0
442
+ def secure(num)
443
+ return num > 0 ? num : 0
444
+ end
445
+
446
+ # --- 进度条 ---
447
+
448
+ def update_progress(totaltasks, finishtasks)
449
+ totalmark = 30
450
+ progress = totaltasks > 0 ? (finishtasks * 100.0 / totaltasks).round : 100
451
+ progress_str = ''
452
+ pre_num = (totalmark / 100.0 * progress).round
453
+ progress_str += '#' * pre_num
454
+ progress_str += ' ' * (totalmark - pre_num)
455
+ bar = "\r[#{progress_str}] #{progress}%"
456
+ bar += "\n" if progress == 100
457
+ print bar
458
+ end
459
+ end
460
+ end
461
+ end