m-git 2.5.4

Sign up to get free protection for your applications and to get access to all the features.
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