m-git 2.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +85 -0
- data/lib/m-git.rb +66 -0
- data/lib/m-git/argv.rb +170 -0
- data/lib/m-git/argv/opt.rb +38 -0
- data/lib/m-git/argv/opt_list.rb +71 -0
- data/lib/m-git/argv/parser.rb +66 -0
- data/lib/m-git/base_command.rb +271 -0
- data/lib/m-git/command/add.rb +41 -0
- data/lib/m-git/command/branch.rb +90 -0
- data/lib/m-git/command/checkout.rb +106 -0
- data/lib/m-git/command/clean.rb +64 -0
- data/lib/m-git/command/commit.rb +84 -0
- data/lib/m-git/command/config.rb +202 -0
- data/lib/m-git/command/delete.rb +99 -0
- data/lib/m-git/command/fetch.rb +32 -0
- data/lib/m-git/command/forall.rb +81 -0
- data/lib/m-git/command/info.rb +74 -0
- data/lib/m-git/command/init.rb +324 -0
- data/lib/m-git/command/log.rb +73 -0
- data/lib/m-git/command/merge.rb +381 -0
- data/lib/m-git/command/pull.rb +364 -0
- data/lib/m-git/command/push.rb +311 -0
- data/lib/m-git/command/rebase.rb +348 -0
- data/lib/m-git/command/reset.rb +31 -0
- data/lib/m-git/command/self.rb +223 -0
- data/lib/m-git/command/stash.rb +189 -0
- data/lib/m-git/command/status.rb +135 -0
- data/lib/m-git/command/sync.rb +327 -0
- data/lib/m-git/command/tag.rb +67 -0
- data/lib/m-git/command_manager.rb +24 -0
- data/lib/m-git/error.rb +20 -0
- data/lib/m-git/foundation.rb +25 -0
- data/lib/m-git/foundation/constants.rb +107 -0
- data/lib/m-git/foundation/dir.rb +25 -0
- data/lib/m-git/foundation/duration_recorder.rb +92 -0
- data/lib/m-git/foundation/git_message_parser.rb +50 -0
- data/lib/m-git/foundation/lock.rb +32 -0
- data/lib/m-git/foundation/loger.rb +129 -0
- data/lib/m-git/foundation/mgit_config.rb +222 -0
- data/lib/m-git/foundation/operation_progress_manager.rb +139 -0
- data/lib/m-git/foundation/timer.rb +74 -0
- data/lib/m-git/foundation/utils.rb +361 -0
- data/lib/m-git/hooks_manager.rb +96 -0
- data/lib/m-git/manifest.rb +181 -0
- data/lib/m-git/manifest/cache_manager.rb +44 -0
- data/lib/m-git/manifest/internal.rb +182 -0
- data/lib/m-git/manifest/light_repo.rb +108 -0
- data/lib/m-git/manifest/light_repo_generator.rb +87 -0
- data/lib/m-git/manifest/linter.rb +153 -0
- data/lib/m-git/open_api.rb +427 -0
- data/lib/m-git/open_api/script_download_info.rb +37 -0
- data/lib/m-git/output/output.rb +461 -0
- data/lib/m-git/plugin_manager.rb +112 -0
- data/lib/m-git/repo.rb +133 -0
- data/lib/m-git/repo/status.rb +481 -0
- data/lib/m-git/repo/sync_helper.rb +254 -0
- data/lib/m-git/template.rb +9 -0
- data/lib/m-git/template/local_manifest.rb +27 -0
- data/lib/m-git/template/manifest_hook.rb +28 -0
- data/lib/m-git/template/post_download_hook.rb +29 -0
- data/lib/m-git/template/post_hook.rb +31 -0
- data/lib/m-git/template/pre_exec_hook.rb +31 -0
- data/lib/m-git/template/pre_hook.rb +29 -0
- data/lib/m-git/template/pre_push_hook.rb +32 -0
- data/lib/m-git/version.rb +6 -0
- data/lib/m-git/workspace.rb +648 -0
- data/lib/m-git/workspace/path_helper.rb +56 -0
- data/lib/m-git/workspace/workspace_helper.rb +159 -0
- data/m-git +1 -0
- data/mgit +19 -0
- 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
|