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.
- 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,87 @@
|
|
1
|
+
|
2
|
+
module MGit
|
3
|
+
class Manifest
|
4
|
+
# @!scope lightrepo生成器
|
5
|
+
#
|
6
|
+
class LightRepoGenerator
|
7
|
+
|
8
|
+
# 简单初始化,有写字段缺失,仅包含名字,相对路径,url
|
9
|
+
#
|
10
|
+
# @param name [String] 仓库名
|
11
|
+
#
|
12
|
+
# @param path [String] 仓库相对路径
|
13
|
+
#
|
14
|
+
# @param url [String] 仓库url
|
15
|
+
#
|
16
|
+
# @return [LightRepo] 配置对象
|
17
|
+
#
|
18
|
+
def self.simple_init(name, path, url)
|
19
|
+
LightRepo.new(name, path, nil, nil, nil, url, false, false, false, false)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.light_repo_with(name, config_json, parent_json)
|
23
|
+
light_repo = LightRepo.new(name)
|
24
|
+
|
25
|
+
light_repo.path = __parse_path(name, config_json, parent_json)
|
26
|
+
lock_info = config_json[Constants::REPO_CONFIG_KEY[:lock]]
|
27
|
+
|
28
|
+
light_repo.lock = lock_info && !lock_info.empty?
|
29
|
+
if light_repo.lock
|
30
|
+
light_repo.commit_id = lock_info[Constants::REPO_CONFIG_LOCK_KEY[:commit_id]]
|
31
|
+
light_repo.tag = lock_info[Constants::REPO_CONFIG_LOCK_KEY[:tag]]
|
32
|
+
light_repo.branch = lock_info[Constants::REPO_CONFIG_LOCK_KEY[:branch]]
|
33
|
+
end
|
34
|
+
light_repo.url = __parse_url(config_json, parent_json)
|
35
|
+
|
36
|
+
dummy = config_json[Constants::REPO_CONFIG_KEY[:dummy]]
|
37
|
+
dummy = !dummy.nil? && dummy == true
|
38
|
+
if dummy
|
39
|
+
excluded = true
|
40
|
+
else
|
41
|
+
excluded = config_json[Constants::REPO_CONFIG_KEY[:mgit_excluded]]
|
42
|
+
excluded = parent_json[Constants::CONFIG_KEY[:mgit_excluded]] if excluded.nil?
|
43
|
+
excluded = !excluded.nil? && excluded == true
|
44
|
+
end
|
45
|
+
light_repo.mgit_excluded = excluded
|
46
|
+
light_repo.dummy = dummy
|
47
|
+
|
48
|
+
is_config_repo = config_json[Constants::REPO_CONFIG_KEY[:config_repo]]
|
49
|
+
light_repo.is_config_repo = is_config_repo.nil? ? false : is_config_repo
|
50
|
+
light_repo
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def __parse_path(repo_name, config_json, parent_json)
|
57
|
+
abs_path = config_json[Constants::REPO_CONFIG_KEY[:abs_dest]]
|
58
|
+
return abs_path if !abs_path.nil? && !abs_path.empty?
|
59
|
+
|
60
|
+
local_path = parent_json[Constants::REPO_CONFIG_KEY[:dest]]
|
61
|
+
# 替换key值中的‘/’字符,避免和路径混淆
|
62
|
+
repo_name = repo_name.gsub(/\//,':')
|
63
|
+
if local_path.nil?
|
64
|
+
Utils.safe_join(parent_json[Constants::CONFIG_KEY[:dest]], repo_name)
|
65
|
+
elsif !local_path.empty?
|
66
|
+
Utils.safe_join(local_path, repo_name)
|
67
|
+
else
|
68
|
+
repo_name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def __parse_url(config_json, parent_json)
|
73
|
+
source_remote = config_json[Constants::REPO_CONFIG_KEY[:remote]]
|
74
|
+
remote_path = config_json[Constants::REPO_CONFIG_KEY[:remote_path]]
|
75
|
+
return if remote_path.nil?
|
76
|
+
if source_remote.nil?
|
77
|
+
global_remote = parent_json[Constants::CONFIG_KEY[:remote]]
|
78
|
+
Utils.safe_join(global_remote, remote_path)
|
79
|
+
else
|
80
|
+
Utils.safe_join(source_remote, remote_path)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
|
2
|
+
module MGit
|
3
|
+
class Manifest
|
4
|
+
module Linter
|
5
|
+
|
6
|
+
# 校验配置文件路径
|
7
|
+
#
|
8
|
+
# @param path [Stirng] 配置文件路径或包含配置文件的目录
|
9
|
+
#
|
10
|
+
# @return [String] 配置文件合法路径
|
11
|
+
#
|
12
|
+
def lint_manifest_path(path)
|
13
|
+
manifest_name = Constants::CONFIG_FILE_NAME[:manifest]
|
14
|
+
|
15
|
+
if !File.exists?(path)
|
16
|
+
if File.symlink?(path)
|
17
|
+
terminate!("配置文件软链接#{path}失效,请执行\"mgit config -m <new_path>/manifest.json\"更新!")
|
18
|
+
else
|
19
|
+
terminate!("配置文件#{path}不存在!")
|
20
|
+
end
|
21
|
+
elsif File.basename(path) != manifest_name
|
22
|
+
terminate!("请指定名为#{manifest_name}的文件!", type:MGIT_ERROR_TYPE[:config_name_error])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# 校验本地配置文件路径
|
27
|
+
#
|
28
|
+
# @param path [String] 本地配置文件路径
|
29
|
+
#
|
30
|
+
# @return [String] 合法的本地配置文件路径
|
31
|
+
#
|
32
|
+
def lint_local_manifest_path(path)
|
33
|
+
local_manifest_name = Constants::CONFIG_FILE_NAME[:local_manifest]
|
34
|
+
terminate!("local配置文件#{path}不存在!") if !File.file?(path)
|
35
|
+
terminate!("请指定名为#{local_manifest_name}的文件!", type:MGIT_ERROR_TYPE[:config_name_error]) if File.basename(path) != local_manifest_name
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!scope 检查lightrepo的仓库url是否重复
|
39
|
+
#
|
40
|
+
def lint_light_repos!
|
41
|
+
repo_urls = {}
|
42
|
+
light_repos.each { |light_repo|
|
43
|
+
next if light_repo.url.nil? || light_repo.url.length == 0
|
44
|
+
repo_urls[light_repo.url] = [] if repo_urls[light_repo.url].nil?
|
45
|
+
repo_urls[light_repo.url].push(light_repo.name)
|
46
|
+
}
|
47
|
+
|
48
|
+
error_repos = []
|
49
|
+
repo_urls.each { |_, value|
|
50
|
+
if value.length > 1
|
51
|
+
error_repos += value
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
if error_repos.length > 0
|
56
|
+
puts Output.generate_table(error_repos, separator:'|')
|
57
|
+
Foundation.help!("以上仓库url配置重复,请修改后重试!")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# 解析并校验配置文件
|
62
|
+
def lint_raw_json!(dict)
|
63
|
+
required_keys = Constants::REQUIRED_CONFIG_KEY
|
64
|
+
missing_required_keys = required_keys - dict.keys
|
65
|
+
terminate!("配置文件中缺失必需字段:#{missing_required_keys}") if missing_required_keys.length > 0
|
66
|
+
|
67
|
+
valid_keys = Constants::CONFIG_KEY.values
|
68
|
+
valid_repo_keys = Constants::REPO_CONFIG_KEY.values
|
69
|
+
|
70
|
+
dict.each { |k, v|
|
71
|
+
terminate!("配置文件中存在冗余字段:#{k}") unless valid_keys.include?(k)
|
72
|
+
|
73
|
+
if k == Constants::CONFIG_KEY[:repositories]
|
74
|
+
terminate!("配置文件中#{k}字段下的数据应为json格式!") if !dict[k].is_a?(Hash)
|
75
|
+
|
76
|
+
config_repos = []
|
77
|
+
dict[k].each { |repo_name, config|
|
78
|
+
terminate!("配置文件中#{k}.#{repo_name}字段下的数据应为json格式!") if !config.is_a?(Hash)
|
79
|
+
|
80
|
+
# 校验值类型
|
81
|
+
config.each { |rk, rv|
|
82
|
+
if rk == Constants::REPO_CONFIG_KEY[:mgit_excluded] ||
|
83
|
+
rk == Constants::REPO_CONFIG_KEY[:config_repo] ||
|
84
|
+
rk == Constants::REPO_CONFIG_KEY[:dummy]
|
85
|
+
terminate!("配置文件中#{k}.#{repo_name}.#{rk}字段下的数据应为Bool类型!") if !rv.is_a?(TrueClass) && !rv.is_a?(FalseClass)
|
86
|
+
elsif rk == Constants::REPO_CONFIG_KEY[:lock]
|
87
|
+
terminate!("配置文件中#{k}.#{repo_name}.#{rk}字段下的数据应为Json类型!") if !rv.is_a?(Hash)
|
88
|
+
elsif valid_repo_keys.include?(rk)
|
89
|
+
terminate!("配置文件中#{k}.#{repo_name}.#{rk}字段下的数据应为String类型!") if !rv.is_a?(String)
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
# 如果mgit_excluded字段是false或者缺省,则纳入mgit多仓库管理,进行严格校验
|
94
|
+
mgit_excluded = config[Constants::REPO_CONFIG_KEY[:mgit_excluded]]
|
95
|
+
global_mgit_excluded = dict[Constants::CONFIG_KEY[:mgit_excluded]]
|
96
|
+
|
97
|
+
if (mgit_excluded.nil? || mgit_excluded == false) && (global_mgit_excluded.nil? || global_mgit_excluded == false)
|
98
|
+
# 校验仓库配置必须字段
|
99
|
+
valid_required_repo_keys = Constants::REQUIRED_REPO_CONFIG_KEY # 不可缺省的字段
|
100
|
+
missing_required_keys = valid_required_repo_keys - config.keys
|
101
|
+
terminate!("配置文件中#{k}.#{repo_name}下有缺失字段:#{missing_required_keys.join(', ')}") if missing_required_keys.length > 0
|
102
|
+
|
103
|
+
# 校验仓库配置冗余字段
|
104
|
+
# extra_keys = config.keys - valid_repo_keys
|
105
|
+
# terminate!("配置文件中#{k}.#{repo_name}下有冗余字段:#{extra_keys.join(', ')}") if extra_keys.length > 0
|
106
|
+
|
107
|
+
# 统计指定的配置仓库
|
108
|
+
config_repo = config[Constants::REPO_CONFIG_KEY[:config_repo]]
|
109
|
+
if !config_repo.nil? && config_repo == true
|
110
|
+
config_repos.push(repo_name)
|
111
|
+
end
|
112
|
+
|
113
|
+
# 校验锁定点
|
114
|
+
lock_key = Constants::REPO_CONFIG_KEY[:lock]
|
115
|
+
lock_config = config[lock_key]
|
116
|
+
if !lock_config.nil?
|
117
|
+
valid_lock_keys = Constants::REPO_CONFIG_LOCK_KEY.values
|
118
|
+
|
119
|
+
# 校验锁定点配置值
|
120
|
+
lock_config.each { |ck, cv|
|
121
|
+
terminate!("配置文件中#{k}.#{repo_name}.#{lock_key}.#{ck}字段下的数据应为String类型!") if !cv.is_a?(String)
|
122
|
+
}
|
123
|
+
|
124
|
+
# 校验锁定点配置必须字段
|
125
|
+
terminate!("配置文件中#{k}.#{repo_name}.#{lock_key}下只能指定字段:#{valid_lock_keys.join(', ')}中的一个!") if lock_config.keys.length != 1 || !valid_lock_keys.include?(lock_config.keys.first)
|
126
|
+
|
127
|
+
# 校验锁定点配置冗余字段
|
128
|
+
# extra_keys = lock_config.keys - valid_lock_keys
|
129
|
+
# terminate!("配置文件中#{k}.#{repo_name}.#{lock_key}下有冗余字段:#{extra_keys.join(', ')}") if extra_keys.length > 0
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
}
|
134
|
+
|
135
|
+
# 校验配置仓库字段的合法性
|
136
|
+
if config_repos.length > 1
|
137
|
+
puts Output.generate_table(config_repos)
|
138
|
+
terminate!("配置表中同时指定了以上多个仓库为配置仓库,仅允许指定最多一个!")
|
139
|
+
end
|
140
|
+
elsif k == Constants::CONFIG_KEY[:version]
|
141
|
+
terminate!("配置文件中#{k}字段下的数据应为Integer类型!") if !dict[k].is_a?(Integer)
|
142
|
+
elsif k == Constants::CONFIG_KEY[:mgit_excluded]
|
143
|
+
terminate!("配置文件中#{k}字段下的数据应为Boolean类型!") if !dict[k].is_a?(TrueClass) && !dict[k].is_a?(FalseClass)
|
144
|
+
else
|
145
|
+
terminate!("配置文件中#{k}字段下的数据应为String类型!") if !dict[k].is_a?(String)
|
146
|
+
end
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,427 @@
|
|
1
|
+
#coding=utf-8
|
2
|
+
|
3
|
+
require 'm-git/open_api/script_download_info'
|
4
|
+
|
5
|
+
module MGit
|
6
|
+
|
7
|
+
# --- Ruby环境调用 ---
|
8
|
+
class OpenApi
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# 根据配置表下载仓库,并同步工作区
|
12
|
+
#
|
13
|
+
# @param config_content [String/Hash] 配置表json字符串或字典(key值需为String),不可为nil。
|
14
|
+
#
|
15
|
+
# @param download_root [String] 仓库下载的根目录,不可为nil。
|
16
|
+
#
|
17
|
+
# Block: (OpenApi::ScriptDownloadInfo) 下载结果对象
|
18
|
+
#
|
19
|
+
# ========= 用法 ===========
|
20
|
+
### info对象:[OpenApi::ScriptDownloadInfo]
|
21
|
+
# MGit::OpenApi.sync_repos(json,root) { |info|
|
22
|
+
# puts "name:#{info.repo_name} \n path:#{info.repo_path}\n result:#{info.result}\n error:#{info.output}\n progress:#{info.progress}"
|
23
|
+
# if info.result == OpenApi::DOWNLOAD_RESULT::EXIST
|
24
|
+
# elsif info.result == OpenApi::DOWNLOAD_RESULT::SUCCESS
|
25
|
+
# elsif info.result == OpenApi::DOWNLOAD_RESULT::FAIL
|
26
|
+
# end
|
27
|
+
# }
|
28
|
+
def sync_repos(config_content, download_root)
|
29
|
+
# 空值校验
|
30
|
+
error = self.validate_argv(__method__, {"config_content" => config_content, "download_root" => config_content})
|
31
|
+
yield(ScriptDownloadInfo.new(nil, nil, DownloadResult::FAIL, error, 0)) if block_given? && !error.nil?
|
32
|
+
|
33
|
+
begin
|
34
|
+
config = Manifest.simple_parse(config_content, strict_mode:false)
|
35
|
+
rescue Error => e
|
36
|
+
yield(ScriptDownloadInfo.new(nil, nil, DownloadResult::FAIL, e.msg, 0)) if block_given?
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
# 同步工作区仓库(压入或弹出),此时配置表缓存未覆盖,同步操作若取消无需恢复缓存。
|
41
|
+
Utils.sync_workspace(download_root, config, recover_cache_if_cancelled:false)
|
42
|
+
|
43
|
+
# 更新配置缓存
|
44
|
+
config.update_cache_with_content(download_root, config.config)
|
45
|
+
|
46
|
+
# 下载
|
47
|
+
self.download(config, download_root, sync_exist:true) { |download_info|
|
48
|
+
yield(download_info) if block_given?
|
49
|
+
}
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# 根据配置表下载仓库
|
54
|
+
#
|
55
|
+
# @param config_content [String/Hash] 配置表json字符串或字典(key值需为String),不可为nil。
|
56
|
+
#
|
57
|
+
# @param download_root [String] 仓库下载的根目录,不可为nil。
|
58
|
+
#
|
59
|
+
# @param manage_git [Boolean] 新下载的仓库是否托管.git,若为false,则在工作区保留新克隆仓库的.git,否则将.git托管到.mgit/sourct-git中,并在工作区创建其软链接
|
60
|
+
#
|
61
|
+
# Block: (OpenApi::ScriptDownloadInfo) 下载结果对象
|
62
|
+
#
|
63
|
+
# ========= 用法 ===========
|
64
|
+
### info对象:[OpenApi::ScriptDownloadInfo]
|
65
|
+
# MGit::OpenApi.download_repos(json,root) { |info|
|
66
|
+
# puts "name:#{info.repo_name} \n path:#{info.repo_path}\n result:#{info.result}\n error:#{info.output}\n progress:#{info.progress}"
|
67
|
+
# if info.result == OpenApi::DOWNLOAD_RESULT::EXIST
|
68
|
+
# elsif info.result == OpenApi::DOWNLOAD_RESULT::SUCCESS
|
69
|
+
# elsif info.result == OpenApi::DOWNLOAD_RESULT::FAIL
|
70
|
+
# end
|
71
|
+
# }
|
72
|
+
def download_repos(config_content, download_root, manage_git:true)
|
73
|
+
|
74
|
+
# 空值校验
|
75
|
+
error = self.validate_argv(__method__, {"config_content" => config_content, "download_root" => config_content})
|
76
|
+
yield(ScriptDownloadInfo.new(nil, nil, DownloadResult::FAIL, error, 0)) if block_given? && !error.nil?
|
77
|
+
|
78
|
+
begin
|
79
|
+
config = Manifest.simple_parse(config_content, strict_mode:false)
|
80
|
+
rescue Error => e
|
81
|
+
yield(ScriptDownloadInfo.new(nil, nil, DownloadResult::FAIL, e.msg, 0)) if block_given?
|
82
|
+
return
|
83
|
+
end
|
84
|
+
|
85
|
+
self.download(config, download_root, manage_git:manage_git) { |download_info|
|
86
|
+
yield(download_info) if block_given?
|
87
|
+
}
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# 将仓库切到特定的分支或commit
|
92
|
+
#
|
93
|
+
# @param repo_name [String] 仓库名,不可为nil。
|
94
|
+
#
|
95
|
+
# @param repo_path [String] 仓库本地地址,不可为nil。
|
96
|
+
#
|
97
|
+
# @param create_branch [String] 是否创建新分支。
|
98
|
+
#
|
99
|
+
# @param branch [String] 需要切换的分支,可为nil。。
|
100
|
+
# 如果非nil,若仓库有该分支则直接切换,没有则在指定commit上创建。
|
101
|
+
# 如果为nil,则直接切换到指定commit。
|
102
|
+
#
|
103
|
+
# @param commit_id [String] 需要切换的commit id,不可为nil。
|
104
|
+
#
|
105
|
+
# @param allow_fetch [String] 指定在本地无指定分支或commit时是否fetch后重试。
|
106
|
+
#
|
107
|
+
# @return [(String, Boolean)] (error, did_create_new_branch)
|
108
|
+
# - error: 错误信息,若无错误,返回nile
|
109
|
+
# - did_create_new_branch: 本次checkout是否创建新分支
|
110
|
+
# ========= 用法 ===========
|
111
|
+
### 自信切commit
|
112
|
+
# MGit::OpenApi.checkout(name, abs_path, base_commit:'4922620')
|
113
|
+
#
|
114
|
+
### 自信切分支,分支确定存在
|
115
|
+
# MGit::OpenApi.checkout(name, abs_path, branch:'master')
|
116
|
+
#
|
117
|
+
### 尝试切分支,没有就创建
|
118
|
+
# MGit::OpenApi.checkout(name, abs_path, create_branch:true, branch:'master', base_commit:'4922620')
|
119
|
+
def checkout(repo_name, repo_path, create_branch:false, branch:nil, base_commit:nil, allow_fetch:false)
|
120
|
+
|
121
|
+
# 空值校验
|
122
|
+
error = self.validate_argv(__method__, {"repo_name" => repo_name, "repo_path" => repo_path})
|
123
|
+
return error, false if !error.nil?
|
124
|
+
return 'branch和base_commit必须传入一个值!', false if branch.nil? && base_commit.nil?
|
125
|
+
|
126
|
+
# 生成仓库对象
|
127
|
+
if Repo.is_git_repo?(repo_path)
|
128
|
+
repo = Repo.new(repo_name, repo_path)
|
129
|
+
else
|
130
|
+
return "路径位置\"#{repo_path}\"不是git仓库!", false
|
131
|
+
end
|
132
|
+
|
133
|
+
error = nil
|
134
|
+
did_create_new_branch = false
|
135
|
+
|
136
|
+
# 如果指定可fetch,那么在本地缺失信息时执行fetch
|
137
|
+
if allow_fetch
|
138
|
+
|
139
|
+
# 查询分支和commit
|
140
|
+
should_fetch = !branch.nil? && !repo.status_checker.local_branch_exist?(branch) ||
|
141
|
+
!base_commit.nil? && !repo.status_checker.commit_exist?(base_commit)
|
142
|
+
|
143
|
+
error = fetch(repo.name, repo.path) if should_fetch
|
144
|
+
|
145
|
+
return error, did_create_new_branch if !error.nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
if !branch.nil?
|
149
|
+
|
150
|
+
# 已在指定分支则不操作
|
151
|
+
if repo.status_checker.current_branch(strict_mode:false) != branch
|
152
|
+
|
153
|
+
# 有本地改动禁止新建/切换分支
|
154
|
+
if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
|
155
|
+
error = "仓库#{repo_name}有改动,无法切换/创建分支!"
|
156
|
+
|
157
|
+
# 有本地或对应远程分支,直接切换
|
158
|
+
elsif repo.status_checker.local_branch_exist?(branch) || repo.status_checker.remote_branch_exist?(branch)
|
159
|
+
success, output = repo.execute_git_cmd('checkout', branch)
|
160
|
+
error = output if !success
|
161
|
+
|
162
|
+
# 无分支,在指定commit上创建
|
163
|
+
elsif create_branch
|
164
|
+
|
165
|
+
if !base_commit.nil?
|
166
|
+
if repo.status_checker.commit_exist?(base_commit)
|
167
|
+
success, output = repo.execute_git_cmd('checkout', "-b #{branch} #{base_commit}")
|
168
|
+
if !success
|
169
|
+
error = output
|
170
|
+
else
|
171
|
+
did_create_new_branch = true
|
172
|
+
end
|
173
|
+
else
|
174
|
+
error = "仓库#{repo_name}创建新分支时,未找到指定基点commit!"
|
175
|
+
end
|
176
|
+
else
|
177
|
+
error = "仓库#{repo_name}创建新分支时,没有指定基点commit!"
|
178
|
+
end
|
179
|
+
|
180
|
+
else
|
181
|
+
error = "仓库#{repo_name}无分支#{branch},且未指定创建!"
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
elsif !base_commit.nil?
|
187
|
+
|
188
|
+
# 已在指定commit则不操作
|
189
|
+
if !repo.status_checker.current_branch(strict_mode:false).nil? ||
|
190
|
+
repo.status_checker.current_head(strict_mode:false) != base_commit
|
191
|
+
|
192
|
+
|
193
|
+
# 有本地改动禁止新建/切换分支
|
194
|
+
if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
|
195
|
+
error = "仓库#{repo_name}有改动,无法切换HEAD!"
|
196
|
+
|
197
|
+
# 未指定branch,直接切到指定commit
|
198
|
+
elsif repo.status_checker.commit_exist?(base_commit)
|
199
|
+
success, output = repo.execute_git_cmd('checkout', base_commit)
|
200
|
+
error = output if !success
|
201
|
+
|
202
|
+
# 指定commit不存在报错
|
203
|
+
else
|
204
|
+
error = "仓库#{repo_name}切换操作失败,指定commit不存在!"
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
return error, did_create_new_branch
|
212
|
+
end
|
213
|
+
|
214
|
+
# 执行fetch操作
|
215
|
+
#
|
216
|
+
# @param repo_name [Stirng] 仓库名
|
217
|
+
#
|
218
|
+
# @param repo_path [String] 仓库路径
|
219
|
+
#
|
220
|
+
# @return [String] 错误信息,如果执行成功返回nil
|
221
|
+
#
|
222
|
+
def fetch(repo_name, repo_path)
|
223
|
+
# 空值校验
|
224
|
+
error = self.validate_argv(__method__, {"repo_name" => repo_name, "repo_path" => repo_path})
|
225
|
+
return error if !error.nil?
|
226
|
+
|
227
|
+
if Dir.exist?(repo_path) && Repo.is_git_repo?(repo_path)
|
228
|
+
repo = Repo.new(repo_name, repo_path)
|
229
|
+
success, output = repo.execute_git_cmd('fetch', '')
|
230
|
+
return output if !success
|
231
|
+
else
|
232
|
+
return '指定路径不存在或不是git仓库!'
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# 执行pull操作
|
237
|
+
#
|
238
|
+
# @param repo_name [Stirng] 仓库名
|
239
|
+
#
|
240
|
+
# @param repo_path [String] 仓库路径
|
241
|
+
#
|
242
|
+
# @return [String] 错误信息,如果执行成功返回nil
|
243
|
+
#
|
244
|
+
def pull(repo_name, repo_path)
|
245
|
+
# 空值校验
|
246
|
+
error = self.validate_argv(__method__, {"repo_name" => repo_name, "repo_path" => repo_path})
|
247
|
+
return error if !error.nil?
|
248
|
+
|
249
|
+
if Dir.exist?(repo_path) && Repo.is_git_repo?(repo_path)
|
250
|
+
repo = Repo.new(repo_name, repo_path)
|
251
|
+
success, output = repo.execute_git_cmd('pull', '')
|
252
|
+
return output if !success
|
253
|
+
else
|
254
|
+
return '指定路径不存在或不是git仓库!'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# 检查给定仓库是否工作区脏,工作区的.git是否是软链接
|
259
|
+
#
|
260
|
+
# @param path_dict [Hash] 待校验的冗余仓库地址:{ "name": "abs_path" }
|
261
|
+
#
|
262
|
+
# @return [Hash] 冗余仓库状态信息:{ "name": { "clean": true, "git_linked": true , "error": "error_msg"} }
|
263
|
+
# - "clean": 为true,表示工作区干净,否则脏。
|
264
|
+
# - "git_linked": 为true,表示.git实体放在.mgit/source-git下,工作区.git仅为软链接,否则为实体。
|
265
|
+
# - "error": 如果校验成功则无该字段,否则校验失败,值为错误信息。注意,若出错则无"clean"和"git_linked"字段。
|
266
|
+
#
|
267
|
+
def check_extra_repos(path_dict)
|
268
|
+
|
269
|
+
# 空值校验
|
270
|
+
error = self.validate_argv(__method__, {"path_dict" => path_dict})
|
271
|
+
return {} if !error.nil?
|
272
|
+
|
273
|
+
output = {}
|
274
|
+
path_dict.each { |name, path|
|
275
|
+
output[name] = {}
|
276
|
+
# 生成仓库对象
|
277
|
+
if Repo.is_git_repo?(path)
|
278
|
+
repo = Repo.new(name, path)
|
279
|
+
output[name]['clean'] = repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:clean]
|
280
|
+
output[name]['git_linked'] = File.symlink?(File.join(path, '.git'))
|
281
|
+
else
|
282
|
+
output[name]['error'] = "路径位置\"#{repo_path}\"不是git仓库!"
|
283
|
+
end
|
284
|
+
}
|
285
|
+
return output
|
286
|
+
end
|
287
|
+
|
288
|
+
# 并发遍历
|
289
|
+
#
|
290
|
+
# @param array [<Object>] 遍历数组
|
291
|
+
#
|
292
|
+
# @param max_concurrent_count [Integer] 最大并发数
|
293
|
+
#
|
294
|
+
# ========= 用法 ===========
|
295
|
+
# concurrent_enumerate([item1, item2...]) { |item|
|
296
|
+
# do something with item...
|
297
|
+
# }
|
298
|
+
def concurrent_enumerate(array, max_concurrent_count:5)
|
299
|
+
if array.is_a?(Array) && array.length > 0
|
300
|
+
array.peach(max_concurrent_count) { |item|
|
301
|
+
yield(item) if block_given?
|
302
|
+
}
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# 在不拉仓库的情况下,批量查询当前用户是否有权限拉取代码
|
307
|
+
#
|
308
|
+
# @param root [String] 工程根目录
|
309
|
+
#
|
310
|
+
# @param url_list [Array<String>] 一组远程仓库url
|
311
|
+
#
|
312
|
+
# ========= 用法 ===========
|
313
|
+
# url_list = [
|
314
|
+
# 'https://github.com/baidu/baiduapp-platform/a',
|
315
|
+
# 'https://github.com/baidu/baiduapp-platform/b'
|
316
|
+
# ]
|
317
|
+
# MGit::OpenApi.check_permission_batch(arr) { |url, has_permission, progress|
|
318
|
+
# do something...
|
319
|
+
# }
|
320
|
+
def check_permission_batch(root, url_list)
|
321
|
+
mutex = Mutex.new
|
322
|
+
task_count = 0
|
323
|
+
total_task = url_list.length
|
324
|
+
concurrent_enumerate(url_list) { |url|
|
325
|
+
has_permission = self.check_permission(url, root:root)
|
326
|
+
mutex.lock
|
327
|
+
task_count += 1
|
328
|
+
progress = Float(task_count) / total_task
|
329
|
+
yield(url, has_permission, progress) if block_given?
|
330
|
+
mutex.unlock
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
# 在不拉仓库的情况下,查询当前用户是否有权限拉取代码
|
335
|
+
#
|
336
|
+
# @param root [String] 工程根目录
|
337
|
+
#
|
338
|
+
# @param url [String] 远程仓库url
|
339
|
+
#
|
340
|
+
# @return [Boolean] 是否有权限
|
341
|
+
#
|
342
|
+
# ========= 用法 ===========
|
343
|
+
# url = 'https://github.com/baidu/baiduapp-platform/a'
|
344
|
+
# result = MGit::OpenApi.check_permission(url)
|
345
|
+
def check_permission(url, root:nil)
|
346
|
+
if !root.nil?
|
347
|
+
git_store = Utils.generate_git_store(root, url)
|
348
|
+
if !git_store.nil? && Dir.exist?(git_store)
|
349
|
+
return true
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
return Utils.has_permission_of_remote?(url)
|
354
|
+
end
|
355
|
+
|
356
|
+
# ----- Util -----
|
357
|
+
# 校验参数合法性
|
358
|
+
#
|
359
|
+
# @param method_name [String] 方法名
|
360
|
+
#
|
361
|
+
# @param args [Hash] 参数数组
|
362
|
+
#
|
363
|
+
# @return [String] 错误信息,正常返回nil
|
364
|
+
#
|
365
|
+
def validate_argv(method_name, args)
|
366
|
+
args.each { |k,v|
|
367
|
+
if v.nil?
|
368
|
+
return "MGit API调用错误: MGit::OpenApi.#{method_name}()的#{k}参数不能为空!"
|
369
|
+
end
|
370
|
+
}
|
371
|
+
return nil
|
372
|
+
end
|
373
|
+
|
374
|
+
# 根据配置对象下载仓库
|
375
|
+
#
|
376
|
+
# @param config_content [String/Hash] 配置表json字符串或字典(key值需为String),不可为nil。
|
377
|
+
#
|
378
|
+
# @param download_root [String] 仓库下载的根目录,不可为nil。
|
379
|
+
#
|
380
|
+
# @param manage_git [Boolean] 新下载的仓库是否托管.git,若为false,则在工作区保留新克隆仓库的.git,否则将.git托管到.mgit/sourct-git中,并在工作区创建其软链接
|
381
|
+
#
|
382
|
+
# ========= 用法 ===========
|
383
|
+
# self.download(config, download_root) { |download_info|
|
384
|
+
# do something with download_info....
|
385
|
+
# }
|
386
|
+
def download(config, download_root, manage_git:true, sync_exist: false)
|
387
|
+
task_count = 0
|
388
|
+
total_task = config.light_repos.length
|
389
|
+
mutex = Mutex.new
|
390
|
+
concurrent_enumerate(config.light_repos) { |light_repo|
|
391
|
+
name = light_repo.name
|
392
|
+
path = light_repo.abs_dest(download_root)
|
393
|
+
result = nil
|
394
|
+
output = nil
|
395
|
+
progress = 0
|
396
|
+
|
397
|
+
if Dir.exist?(path) && Repo.is_git_repo?(path)
|
398
|
+
if sync_exist
|
399
|
+
repo, error = Repo.generate_softly(download_root, light_repo)
|
400
|
+
error = Repo::SyncHelper.sync_exist_repo(repo, repo.config) if !repo.nil?
|
401
|
+
end
|
402
|
+
result = DownloadResult::EXIST
|
403
|
+
elsif Utils.has_permission_of_remote?(light_repo.url)
|
404
|
+
error, repo = Repo::SyncHelper.sync_new_repo(light_repo, download_root, link_git:manage_git)
|
405
|
+
if !error.nil?
|
406
|
+
result = DownloadResult::FAIL
|
407
|
+
output = error
|
408
|
+
else
|
409
|
+
result = DownloadResult::SUCCESS
|
410
|
+
end
|
411
|
+
else
|
412
|
+
result = DownloadResult::FAIL
|
413
|
+
output = "当前用户没有该仓库的克隆权限:#{light_repo.name}(#{light_repo.url})!"
|
414
|
+
end
|
415
|
+
|
416
|
+
mutex.lock
|
417
|
+
task_count += 1
|
418
|
+
progress = Float(task_count) / total_task
|
419
|
+
yield(ScriptDownloadInfo.new(name, path, result, output, progress)) if block_given?
|
420
|
+
mutex.unlock
|
421
|
+
}
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
end
|
426
|
+
|
427
|
+
end
|