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,99 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope [command] delete 删除某个仓库的`所有`文件,包括工作区、暂存区和版本库
6
+ #
7
+ # eg: mgit delete subA
8
+ #
9
+ class Delete < BaseCommand
10
+
11
+ # --- 覆写前后hook,不需要预设操作 ---
12
+ def pre_exec
13
+ MGit::DurationRecorder.start
14
+ Workspace.setup_multi_repo_root
15
+ # 配置log
16
+ MGit::Loger.config(Workspace.root)
17
+ MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")
18
+ Workspace.setup_config
19
+ end
20
+
21
+ def post_exec
22
+ # 打点结束
23
+ duration = MGit::DurationRecorder.end
24
+ MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
25
+ end
26
+ # --------------------------------
27
+
28
+ def execute(argv)
29
+ delete_repo_names = parse_repo_name(argv)
30
+ include_central = Workspace.config.light_repos.find do |e|
31
+ delete_repo_names.include?(e.name.downcase) && e.is_config_repo
32
+ end
33
+ Foundation.help!("禁止删除配置仓库=> #{include_central.name}") if include_central
34
+
35
+ Output.puts_start_cmd
36
+ delete_light_repos = Workspace.config.light_repos.select { |e| delete_repo_names.include?(e.name.downcase) }
37
+ extra_repo_names = delete_repo_names - delete_light_repos.map { |e| e.name.downcase}
38
+ if delete_light_repos.length > 0
39
+ error_repos = {}
40
+ delete_light_repos.each { |light_repo|
41
+ begin
42
+ git_dir = light_repo.git_store_dir(Workspace.root)
43
+ repo_dir = light_repo.abs_dest(Workspace.root)
44
+ if !Dir.exist?(git_dir) && !Dir.exist?(repo_dir)
45
+ Output.puts_remind_message("#{light_repo.name}本地不存在,已跳过。")
46
+ end
47
+
48
+ # 删除git实体
49
+ if Dir.exist?(git_dir)
50
+ Output.puts_processing_message("删除仓库#{light_repo.name}的.git实体...")
51
+ FileUtils.remove_dir(git_dir, true)
52
+ end
53
+
54
+ # 删除工作区文件
55
+ if Dir.exist?(repo_dir)
56
+ Output.puts_processing_message("删除仓库#{light_repo.name}工作区文件...")
57
+ FileUtils.remove_dir(repo_dir, true)
58
+ end
59
+ rescue => e
60
+ error_repos[light_repo.name] = e.message
61
+ end
62
+ }
63
+ if error_repos.length > 0
64
+ Workspace.show_error(error_repos)
65
+ else
66
+ Output.puts_succeed_cmd(argv.absolute_cmd)
67
+ end
68
+ end
69
+
70
+ if extra_repo_names.length > 0
71
+ Output.puts_fail_block(extra_repo_names, "以上仓库配置表中未定义,请重试!")
72
+ end
73
+ end
74
+
75
+ def parse_repo_name(argv)
76
+ return if argv.git_opts.nil?
77
+
78
+ repos = argv.git_opts.split(' ')
79
+ extra_opts = repos.select { |e| argv.is_option?(e) }
80
+ Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
81
+ Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit info repo1 repo2 ...") if repos.length == 0
82
+ repos.map { |e| e.downcase }
83
+ end
84
+
85
+ def enable_short_basic_option
86
+ true
87
+ end
88
+
89
+ def self.description
90
+ "删除指定单个或多个仓库(包含被管理的.git文件和工程文件以及跟该.git关联的所有缓存)。"
91
+ end
92
+
93
+ def self.usage
94
+ "mgit delete <repo1> <repo2>... [-h]"
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,32 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope [command] fetch
6
+ # follow git fetch
7
+ # eg: mgit fetch
8
+ #
9
+ class Fetch < BaseCommand
10
+ def execute(argv)
11
+ Output.puts_start_cmd
12
+ _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, all_repos)
13
+ if error_repos.length == 0
14
+ Output.puts_succeed_cmd(argv.absolute_cmd)
15
+ Timer.show_time_consuming_repos
16
+ end
17
+ end
18
+
19
+ def enable_repo_selection
20
+ true
21
+ end
22
+
23
+ def self.description
24
+ "与远程仓库同步分支引用和数据对象。"
25
+ end
26
+
27
+ def self.usage
28
+ "mgit fetch [<git-fetch-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,81 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope [command] forall 对管理的仓库依次执行shell命令
6
+ #
7
+ # eg: mgit forall -c 'git status'
8
+ #
9
+ class Forall < BaseCommand
10
+
11
+ OPT_LIST = {
12
+ :command => '--command',
13
+ :command_s => '-c',
14
+ :concurrent => '--concurrent',
15
+ :concurrent_s => '-n',
16
+ }.freeze
17
+
18
+ def options
19
+ return [
20
+ ARGV::Opt.new(OPT_LIST[:command],
21
+ short_key:OPT_LIST[:command_s],
22
+ info:'必须参数,指定需要执行的shell命令,如:"mgit forall -c \'git status -s\'"(注意要带引号)。',
23
+ type: :string),
24
+ ARGV::Opt.new(OPT_LIST[:concurrent],
25
+ short_key:OPT_LIST[:concurrent_s],
26
+ info:'可选参数,若指定,则shell命令以多线程方式执行。',
27
+ type: :boolean)
28
+ ].concat(super)
29
+ end
30
+
31
+ def validate(argv)
32
+ Foundation.help!("输入非法参数:#{argv.git_opts}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if argv.git_opts.length > 0
33
+ Foundation.help!("请输入必须参数--command,示例:mgit forall -c 'git status'") if argv.opt(OPT_LIST[:command]).nil?
34
+ end
35
+
36
+ def execute(argv)
37
+ # 校验分支统一
38
+ Workspace.check_branch_consistency
39
+
40
+ Output.puts_start_cmd
41
+ for_all_cmd = argv.opt(OPT_LIST[:command]).value
42
+
43
+ use_concurrent = !argv.opt(OPT_LIST[:concurrent]).nil?
44
+ if use_concurrent
45
+ succeed_repos, error_repos = Workspace.execute_common_cmd_with_repos_concurrent(for_all_cmd, all_repos)
46
+ else
47
+ succeed_repos, error_repos = Workspace.execute_common_cmd_with_repos(for_all_cmd, all_repos)
48
+ end
49
+
50
+ no_output_repos = []
51
+ succeed_repos.each { |repo_name, output|
52
+ if output.length > 0
53
+ puts Output.generate_title_block(repo_name) { output } + "\n"
54
+ else
55
+ no_output_repos.push(repo_name)
56
+ end
57
+ }
58
+
59
+ Output.puts_remind_block(no_output_repos, "以上仓库无输出!") if no_output_repos.length > 0
60
+ Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
61
+ end
62
+
63
+ def enable_repo_selection
64
+ true
65
+ end
66
+
67
+ def enable_short_basic_option
68
+ true
69
+ end
70
+
71
+ def self.description
72
+ "对多仓库批量执行指令。"
73
+ end
74
+
75
+ def self.usage
76
+ "mgit forall -c '<instruction>' [(-m|-e) <repo>...] [-n] [-h]"
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,74 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope 指定仓库的信息
6
+ #
7
+ class Info < BaseCommand
8
+
9
+ def execute(argv)
10
+ Output.puts_start_cmd
11
+
12
+ query_repo_names = parse_repo_name(argv)
13
+ quere_repos = (all_repos + locked_repos).select { |e| query_repo_names.include?(e.name.downcase) }
14
+ if quere_repos.length > 0
15
+ quere_repos.each { |repo|
16
+ puts Output.generate_title_block(repo.name) {
17
+ info = []
18
+ info.push(['仓库位置'.bold, ["#{repo.path}"]])
19
+ info.push(['占用磁盘大小'.bold, ["#{calculate_size(repo)}"]])
20
+ info.push(['创建时间'.bold, ["#{File.ctime(repo.path)}"]])
21
+
22
+ current_branch = repo.status_checker.current_branch(strict_mode:false)
23
+ branch_message = repo.status_checker.branch_message
24
+ info.push(['当前分支'.bold, ["#{current_branch.nil? ? '无' : current_branch}"]])
25
+ info.push(['分支状态'.bold, ["#{branch_message}"]])
26
+
27
+ info.push(['文件改动'.bold, ["#{repo.status_checker.status != Repo::Status::GIT_REPO_STATUS[:clean] ? '有本地改动,请用status指令查看细节' : '本地无修改'}"]])
28
+ info.push(['Stash状态'.bold, ["#{check_stash(repo)}"]])
29
+ Output.generate_table_combination(info) + "\n\n"
30
+ }
31
+ }
32
+ Output.puts_succeed_cmd(argv.absolute_cmd)
33
+ else
34
+ Output.puts_fail_message("未找到与输入仓库名匹配的仓库,请重试!")
35
+ end
36
+
37
+ end
38
+
39
+ def parse_repo_name(argv)
40
+ return nil if argv.git_opts.nil?
41
+ repos = argv.git_opts.split(' ')
42
+ extra_opts = repos.select { |e| argv.is_option?(e) }
43
+ Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
44
+ Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit info repo1 repo2 ...") if repos.length == 0
45
+ repos.map { |e| e.downcase }
46
+ end
47
+
48
+ def calculate_size(repo)
49
+ success, output = repo.execute("du -sh #{repo.path} | awk '{print $1}'")
50
+ return '计算失败'.red unless success
51
+ output.chomp
52
+ end
53
+
54
+ def check_stash(repo)
55
+ success, output = repo.execute("git -C \"#{repo.path}\" stash list")
56
+ return "查询失败".red unless success
57
+ output.length > 0 ? '有内容' : '无内容'
58
+ end
59
+
60
+ def enable_short_basic_option
61
+ false
62
+ end
63
+
64
+ def self.description
65
+ "输出指定仓库的信息。"
66
+ end
67
+
68
+ def self.usage
69
+ "mgit info <repo>... [-h]"
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,324 @@
1
+ #coding=utf-8
2
+
3
+ module MGit
4
+
5
+ # @!scope 初始化多仓库命令
6
+ #
7
+ class Init < BaseCommand
8
+
9
+ OPT_LIST = {
10
+ :git_source => '--git-source',
11
+ :git_source_s => '-g',
12
+ :config_source => '--config-source',
13
+ :config_source_s => '-f',
14
+ :branch => '--branch',
15
+ :branch_s => '-b',
16
+ :local_config => '--local-config',
17
+ :local_config_s => '-l',
18
+ :all => '--all',
19
+ :all_s => '-a'
20
+ }.freeze
21
+
22
+ def options
23
+ return [
24
+ ARGV::Opt.new(OPT_LIST[:git_source],
25
+ short_key:OPT_LIST[:git_source_s],
26
+ info:'通过包含多仓库配置表的git仓库来初始化,传入远程仓库地址,如:"mgit init -g https://someone@bitbucket.org/someone"。不可与"-f","--config-source"同时指定。',
27
+ type: :string),
28
+ ARGV::Opt.new(OPT_LIST[:config_source],
29
+ short_key:OPT_LIST[:config_source_s],
30
+ info:'通过本地的多仓库配置表来初始化,传入本地配置文件路径,如:"mgit init -f <local_config_path>/manifest.json"。不可与"-g","--git-source"同时指定。',
31
+ type: :string),
32
+ ARGV::Opt.new(OPT_LIST[:branch],
33
+ short_key:OPT_LIST[:branch_s],
34
+ info:'指定配置仓库克隆分支。',
35
+ type: :string),
36
+ ARGV::Opt.new(OPT_LIST[:local_config],
37
+ short_key:OPT_LIST[:local_config_s],
38
+ info:'指定是否自动生成本地配置文件模版。指定后会在主仓库下生成名为local_manifest.json的本地配置文件,其内容只包含主仓库信息,并指定其余仓库不纳入mgit管理。初始化时指定该参数将只下载主仓库,若同时指定了"-a"或"--all",则其余仓库也会被下载。',
39
+ type: :boolean),
40
+ ARGV::Opt.new(OPT_LIST[:all],
41
+ short_key:OPT_LIST[:all_s],
42
+ info:'指定后会下载所有在配置表中配置了远程地址的仓库,无论该仓库是否被纳入mgit管理(无论是否指定"mgit_excluded:true")。',
43
+ type: :boolean)
44
+ ].concat(super)
45
+ end
46
+
47
+ def validate(argv)
48
+ Foundation.help!("输入非法参数:#{argv.git_opts}") if argv.git_opts.length > 0
49
+
50
+ git_source_opt = argv.opt(OPT_LIST[:git_source])
51
+ file_source_opt = argv.opt(OPT_LIST[:config_source])
52
+ if !git_source_opt.nil? && !file_source_opt.nil?
53
+ Foundation.help!("不能同时指定参数\"#{OPT_LIST[:git_source]}\"和\"#{OPT_LIST[:config_source]}!\"")
54
+ elsif git_source_opt.nil? && file_source_opt.nil?
55
+ Foundation.help!("缺失参数\"#{OPT_LIST[:git_source]}\"或\"#{OPT_LIST[:config_source]}!\"")
56
+ end
57
+ end
58
+
59
+ # --- 覆写前后hook,不需要预设操作 ---
60
+ def pre_exec
61
+ Output.puts_processing_message("开始初始化多仓库...")
62
+ # 开始计时
63
+ MGit::DurationRecorder.start
64
+
65
+ initial_multi_repo_root
66
+
67
+ # 配置log
68
+ MGit::Loger.config(Workspace.root)
69
+ MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")
70
+ end
71
+
72
+ def post_exec
73
+ Output.puts_success_message("多仓库初始化成功!")
74
+ # 打点结束
75
+ duration = MGit::DurationRecorder.end
76
+ MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
77
+ end
78
+
79
+ # --------------------------------
80
+
81
+ def execute(argv)
82
+ init_dir
83
+
84
+ begin
85
+ git_url_opt = argv.opt(OPT_LIST[:git_source])
86
+ local_url_opt = argv.opt(OPT_LIST[:config_source])
87
+ clone_all = argv.opt_list.did_set_opt?(OPT_LIST[:all])
88
+ if !git_url_opt.nil?
89
+ git_url = git_url_opt.value
90
+ branch = argv.opt(OPT_LIST[:branch]).value if argv.opt_list.did_set_opt?(OPT_LIST[:branch])
91
+ use_local = argv.opt_list.did_set_opt?(OPT_LIST[:local_config])
92
+ clone_with_git_url(git_url, branch, use_local, clone_all)
93
+ elsif !local_url_opt.nil?
94
+ clone_with_local_config(local_url_opt.value, clone_all)
95
+ end
96
+ rescue Interrupt => e
97
+ terminate!(e.message)
98
+ end
99
+ end
100
+
101
+ def init_dir
102
+ Constants::PROJECT_DIR.each { |key, relative_dir|
103
+ abs_dir = File.join(Workspace.root, relative_dir)
104
+ FileUtils.mkdir_p(abs_dir)
105
+ if key == :hooks
106
+ setup_hooks(File.join(abs_dir, Constants::HOOK_NAME[:pre_hook]),
107
+ File.join(abs_dir, Constants::HOOK_NAME[:post_hook]),
108
+ File.join(abs_dir, Constants::HOOK_NAME[:manifest_hook]),
109
+ File.join(abs_dir, Constants::HOOK_NAME[:post_download_hook]))
110
+ end
111
+ }
112
+ end
113
+
114
+ def write_content(path, content)
115
+ file = File.new(path, 'w')
116
+ if !file.nil?
117
+ file.write(content)
118
+ file.close
119
+ end
120
+ end
121
+
122
+ def setup_hooks(pre_hook_path, post_hook_path, manifest_hook_path, post_download_hook)
123
+ write_content(pre_hook_path, Template::PRE_CUSTOMIZED_HOOK_TEMPLATE)
124
+ write_content(post_hook_path, Template::POST_CUSTOMIZED_HOOK_TEMPLATE)
125
+ write_content(manifest_hook_path, Template::MANIFEST_HOOK_TEMPLATE)
126
+ write_content(post_download_hook, Template::POST_DOWNLOAD_HOOK_TEMPLATE)
127
+ end
128
+
129
+ def initial_multi_repo_root
130
+
131
+ if exist_root = Workspace.multi_repo_root_path
132
+ Foundation.help!("当前已在多仓库目录下,请勿重复初始化!\n`#{exist_root}`")
133
+ end
134
+
135
+ @origin_root = Dir.pwd
136
+ tmp_root = Utils.generate_init_cache_path(@origin_root)
137
+ FileUtils.mkdir_p(tmp_root)
138
+ Workspace.setup_multi_repo_root(tmp_root)
139
+ end
140
+
141
+ def setup_local_config(path, config_repo, use_local)
142
+ if use_local
143
+ content = Template.local_config_template(config_repo)
144
+ else
145
+ content = Template.default_template
146
+ end
147
+
148
+ write_content(path, content)
149
+ Utils.link(path, File.join(Workspace.root, Constants::PROJECT_DIR[:source_config], Constants::CONFIG_FILE_NAME[:local_manifest]))
150
+ end
151
+
152
+ def clone_with_git_url(git_url, branch, use_local, clone_all)
153
+ # 先将主仓库clone到mgit root目录下
154
+ central_repo_temp_path = File.join(Workspace.root, Constants::CENTRAL_REPO)
155
+ Output.puts_processing_message("正在克隆主仓库...")
156
+ Utils.execute_shell_cmd("git clone -b #{branch.nil? ? 'master' : branch} -- #{git_url} #{central_repo_temp_path}") { |stdout, stderr, status|
157
+ if status.success?
158
+ # 获取主仓库中的配置文件
159
+ begin
160
+ config = Manifest.parse(central_repo_temp_path, strict_mode:false)
161
+ rescue Error => e
162
+ terminate!(e.msg)
163
+ end
164
+ central_light_repo = config.config_repo
165
+ terminate!("配置文件中未找到主仓库配置,请添加后重试!") if central_light_repo.nil?
166
+ terminate!("配置文件中主仓库url与传入的url不一致, 请处理后重试!") if git_url != central_light_repo.url
167
+
168
+ central_repo_dest_path = central_light_repo.abs_dest(Workspace.root)
169
+
170
+ # 如果不是子目录则删除已有
171
+ if Dir.exist?(central_repo_dest_path) && !central_repo_temp_path.include?(central_repo_dest_path)
172
+ FileUtils.remove_dir(central_repo_dest_path, true)
173
+ end
174
+ FileUtils.mkdir_p(central_repo_dest_path)
175
+ mv_cmd = "mv #{central_repo_temp_path + '/{*,.[^.]*}'} #{central_repo_dest_path}"
176
+
177
+ `#{mv_cmd}`
178
+ FileUtils.rm_rf(central_repo_temp_path)
179
+
180
+ # 链接.git实体
181
+ Utils.link_git(central_repo_dest_path, central_light_repo.git_store_dir(Workspace.root))
182
+ Output.puts_success_message("主仓库克隆完成!")
183
+
184
+ # 链接本地配置文件
185
+ setup_local_config(File.join(central_repo_dest_path, Constants::CONFIG_FILE_NAME[:local_manifest]), central_light_repo.name, use_local)
186
+
187
+ # 由于更新了名字和可能的位置移动,重新解析配置文件
188
+ begin
189
+ config = Manifest.parse(central_repo_dest_path, strict_mode:false)
190
+ rescue Error => e
191
+ terminate!(e.msg)
192
+ end
193
+
194
+ # clone其余仓库
195
+ clone_sub_repos(config.repo_list(exclusion:[config.config_repo.name], all:clone_all), branch)
196
+ finish_init(config)
197
+ else
198
+ terminate!("主仓库克隆失败,初始化停止,请重试:\n#{stderr}")
199
+ end
200
+ }
201
+ end
202
+
203
+ def clone_with_local_config(config_path, clone_all)
204
+ terminate!("指定路径\"#{config_path}\"文件不存在!") if !File.exist?(config_path)
205
+
206
+ begin
207
+ config = Manifest.parse(Utils.expand_path(config_path), strict_mode:false)
208
+ rescue Error => e
209
+ terminate!(e.msg)
210
+ end
211
+
212
+ if !config.config_repo.nil?
213
+ clone_list = config.repo_list(exclusion: [config.config_repo.name], all:clone_all)
214
+ else
215
+ clone_list = config.repo_list(all:clone_all)
216
+ end
217
+
218
+ clone_sub_repos(clone_list, 'master')
219
+ finish_init(config)
220
+ end
221
+
222
+ def clone_sub_repos(repo_list, default_branch)
223
+ if repo_list.length != 0
224
+ Output.puts_processing_message("正在克隆子仓库...")
225
+ try_repos = repo_list
226
+ retry_repos = []
227
+ error_repos = {}
228
+
229
+ mutex = Mutex.new
230
+ mutex_progress = Mutex.new
231
+
232
+ task_count = 0
233
+ total_try_count = 4
234
+ total_retry_count = total_try_count - 1
235
+ # 保证失败仓库有3次重新下载的机会
236
+ total_try_count.times { |try_count|
237
+ Workspace.concurrent_enumerate(try_repos) { |light_repo|
238
+ # 如果mainfest中指定了分支,就替换default branch
239
+ branch = light_repo.branch ? light_repo.branch : default_branch
240
+ Utils.execute_shell_cmd(light_repo.clone_url(Workspace.root, clone_branch:branch)) { |stdout, stderr, status|
241
+ repo_dir = light_repo.abs_dest(Workspace.root)
242
+ if status.success?
243
+ Utils.link_git(repo_dir, light_repo.git_store_dir(Workspace.root))
244
+
245
+ mutex_progress.lock
246
+ task_count += 1
247
+ Output.puts_success_message("(#{task_count}/#{try_repos.length}) \"#{light_repo.name}\"克隆完成!")
248
+ mutex_progress.unlock
249
+ else
250
+ Output.puts_remind_message("\"#{light_repo.name}\"克隆失败,已加入重试队列。") if try_count < total_retry_count
251
+ mutex.lock
252
+ retry_repos.push(light_repo)
253
+ error_repos[light_repo.name] = stderr
254
+ FileUtils.remove_dir(repo_dir, true) if Dir.exist?(repo_dir)
255
+ mutex.unlock
256
+ end
257
+ }
258
+ }
259
+
260
+ if retry_repos.length == 0 || try_count >= total_retry_count
261
+ break
262
+ else
263
+ Output.puts_processing_block(error_repos.keys, "以上仓库克隆失败,开始第#{try_count + 1}次重试(最多#{total_retry_count}次)...")
264
+ try_repos = retry_repos
265
+ retry_repos = []
266
+ error_repos = {}
267
+ task_count = 0
268
+ end
269
+ }
270
+
271
+ if error_repos.length > 0
272
+ Workspace.show_error(error_repos)
273
+ terminate!("初始化停止,请重试!")
274
+ end
275
+ end
276
+ end
277
+
278
+ def link_config(config_path, config_cache_path, root)
279
+ Utils.execute_under_dir("#{File.join(root, Constants::PROJECT_DIR[:source_config])}") {
280
+ # 在.mgit/source-config文件夹下创建原始配置文件的软连接
281
+ config_link_path = File.join(Dir.pwd, Constants::CONFIG_FILE_NAME[:manifest])
282
+ Utils.link(config_path, config_link_path) if config_path != config_link_path
283
+
284
+ # 将配置缓存移动到.mgit/source-config文件夹下
285
+ FileUtils.mv(config_cache_path, Dir.pwd) if File.dirname(config_cache_path) != Dir.pwd
286
+ }
287
+ end
288
+
289
+ def terminate!(msg)
290
+ Output.puts_fail_message(msg)
291
+ Output.puts_processing_message("删除缓存...")
292
+ FileUtils.remove_dir(Workspace.root, true) if Dir.exist?(Workspace.root)
293
+ Output.puts_success_message("删除完成!")
294
+ exit
295
+ end
296
+
297
+ def move_project_to_root
298
+ Dir.foreach(Workspace.root) { |item|
299
+ if item != '.' && item != '..' && item != '.DS_Store'
300
+ FileUtils.mv(File.join(Workspace.root, item), @origin_root)
301
+ end
302
+ }
303
+ FileUtils.remove_dir(Workspace.root, true) if Dir.exist?(Workspace.root)
304
+ end
305
+
306
+ def finish_init(config)
307
+ move_project_to_root
308
+ config_path, config_cache_path = config.path.sub(Workspace.root, @origin_root), config.cache_path.sub(Workspace.root, @origin_root)
309
+ link_config(config_path, config_cache_path, @origin_root)
310
+ end
311
+
312
+ def enable_short_basic_option
313
+ true
314
+ end
315
+
316
+ def self.description
317
+ "初始化多仓库目录。"
318
+ end
319
+
320
+ def self.usage
321
+ "mgit init (-f <path> | -g <url> [-b <branch>] [-l]) [-a]"
322
+ end
323
+ end
324
+ end