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