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,181 @@
1
+ #coding=utf-8
2
+
3
+ require 'm-git/manifest/light_repo'
4
+ require 'm-git/manifest/linter'
5
+ require 'm-git/manifest/internal'
6
+
7
+ module MGit
8
+
9
+ # Sample: 'm-git/manifest/manifest.sample'
10
+ #
11
+ class Manifest
12
+ include Linter
13
+ include Internal
14
+
15
+ # [Hash] 配置内容
16
+ attr_reader :config
17
+
18
+ # [String] 配置表内容哈希
19
+ # attr_reader :config_hash
20
+ attr_reader :hash_sha1
21
+
22
+ # [Array<LightRepo>] 与config中‘repositories’字段对应的Manifest::LightRepo对象数组,包含git仓库和非git仓库
23
+ attr_reader :light_repos
24
+
25
+ # [Array<LightRepo>] 类型同light_repos,表示上次操作,但本次未操作的仓库
26
+ attr_reader :previous_extra_light_repos
27
+
28
+ # [LightRepo] 包含配置表的配置仓库
29
+ attr_reader :config_repo
30
+
31
+ # [String] 配置文件路径
32
+ attr_reader :path
33
+
34
+ # [String] 缓存文件路径
35
+ attr_reader :cache_path
36
+
37
+ # [Hash] 上次改动生成的配置缓存
38
+ attr_reader :previous_config
39
+
40
+ # 在mgit根目录中搜索配置文件并根据配置文件生成对应LightRepo对象。
41
+ #
42
+ # [!!!!!] 请勿随意修改该接口
43
+ #
44
+ # @param root [String] mgit工作区根目录
45
+ #
46
+ # @param mgit_managed_only [Boolean] default: false,是否只获取mgit管理的git仓库
47
+ #
48
+ # @param only_exist [Boolean] default: false,是否只获取实际存在的仓库
49
+ #
50
+ # @param exclude_dummy [Boolean] default: false,是否排除dummy仓库
51
+ #
52
+ # @return [Array<LightRepo>] 仓库列表
53
+ #
54
+ def self.generate_light_repos(root, mgit_managed_only: false, only_exist:false, exclude_dummy:false)
55
+ config_path = File.join(root, Constants::PROJECT_DIR[:source_config])
56
+ config = self.parse(config_path)
57
+
58
+ if mgit_managed_only
59
+ repos = config.repo_list
60
+ elsif exclude_dummy
61
+ repos = config.light_repos.select { |repo| !repo.dummy }
62
+ else
63
+ repos = config.light_repos
64
+ end
65
+
66
+ if only_exist
67
+ existing_repos = []
68
+ repos.each { |repo|
69
+ abs_path = repo.abs_dest(root)
70
+ # 被管理的仓库验证是否是git仓库,不被管理的仓库只验证文件夹是否存在
71
+ if (repo.mgit_excluded && Dir.exist?(abs_path)) || (!repo.mgit_excluded && Repo.is_git_repo?(abs_path))
72
+ existing_repos.push(repo)
73
+ end
74
+ }
75
+ repos = existing_repos
76
+ end
77
+
78
+ return repos
79
+ end
80
+
81
+ # 全流程校验,除了字段合法性校验外,还包含缓存生成/校验,local配置文件校验/合并
82
+ #
83
+ # @param config_path [String] 配置文件本地路径
84
+ #
85
+ # @param strict_mode [Boolean] default: true 如果为true,出错直接报错退出,如果为false,出错抛出异常(MGit::Error)
86
+ #
87
+ # @return [Manifest] 配置对象
88
+ #
89
+ def self.parse(config_path, strict_mode:true)
90
+ config = Manifest.new
91
+ config.__setup(config_path, strict_mode)
92
+ return config
93
+ end
94
+
95
+ # 简单校验,仅校验配置内容,无缓存生成/校验,无local配置文件校验/合并
96
+ #
97
+ # @param config_content [String/Hash] 配置表json字符串或字典(key值需为String)
98
+ #
99
+ # @param strict_mode [Boolean] default: true 如果为true,出错直接报错退出,如果为false,出错抛出异常(MGit::Error)
100
+ #
101
+ # @return [Manifest] 配置对象
102
+ #
103
+ def self.simple_parse(config_content, strict_mode:true)
104
+ config = Manifest.new
105
+ config.__simple_setup(config_content, strict_mode)
106
+ return config
107
+ end
108
+
109
+
110
+ # 返回所有git仓库列表
111
+ #
112
+ # @param selection [Array<String>] default: nil,需要筛选的仓库名数组
113
+ #
114
+ # @param exclusion [Array<String>] default: nil,需要排除的仓库名数组
115
+ #
116
+ # @param all [Boolean] default: false 若指定为true,则忽略mgit_excluded的字段值,只要remote url存在(即有对应远程仓库),则选取
117
+ #
118
+ # @return [Array<LightRepo>] 被mgit管理的仓库生成的LightRepo数组
119
+ #
120
+ def repo_list(selection: nil, exclusion: nil, all:false)
121
+ list = []
122
+ light_repos.each { |light_repo|
123
+ if !light_repo.mgit_excluded || (all && !light_repo.url.nil?)
124
+ # 选取指定仓库
125
+ if (selection.nil? && exclusion.nil?) ||
126
+ (!selection.nil? && selection.is_a?(Array) && selection.any? { |e| e.downcase == light_repo.name.downcase }) ||
127
+ (!exclusion.nil? && exclusion.is_a?(Array) && !exclusion.any? { |e| e.downcase == light_repo.name.downcase })
128
+ list.push(light_repo)
129
+ end
130
+ end
131
+ }
132
+ list
133
+ end
134
+
135
+ # 获取全局配置
136
+ #
137
+ # @param key [Symbol] 配置字段【符号】
138
+ #
139
+ # @return [Object] 对应配置字段的值,可能是字符串,也可能是字典
140
+ #
141
+ def global_config(key)
142
+ key_str = Constants::CONFIG_KEY[key]
143
+ value = config[key_str]
144
+ terminate!("无法获取多仓库配置必需字段\"#{key}\"的值!") if value.nil? && Constants::REQUIRED_CONFIG_KEY.include?(key_str)
145
+ value
146
+ end
147
+
148
+ # 更新缓存
149
+ #
150
+ # @param root [String] 多仓库根目录
151
+ #
152
+ # @param config_content [Hash] 配置Hash
153
+ #
154
+ def update_cache_with_content(root, config_content)
155
+ config_dir = File.join(root, Constants::PROJECT_DIR[:source_config])
156
+ cache_path = File.join(config_dir, Constants::CONFIG_FILE_NAME[:manifest_cache])
157
+ sha1 = __generate_hash_sha1(config_content.to_json)
158
+ CacheManager.save_to_cache(cache_path, sha1, config_content)
159
+ end
160
+
161
+ # 更新(对比上次操作)冗余的轻量仓库对象
162
+ #
163
+ # @param root [String] 多仓库根目录
164
+ #
165
+ def update_previous_extra_light_repos(root)
166
+ cache_path = File.join(root, Constants::PROJECT_DIR[:source_config], Constants::CONFIG_FILE_NAME[:manifest_cache])
167
+ __load_cache(cache_path)
168
+ end
169
+
170
+ def terminate!(msg, type:nil)
171
+ if @strict_mode
172
+ Foundation.help!(msg)
173
+ else
174
+ raise Error.new(msg, type:type)
175
+ end
176
+ end
177
+
178
+ end
179
+
180
+ RepoConfig = Manifest
181
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module MGit
3
+ class Manifest
4
+ class CacheManager
5
+
6
+ attr_reader :path
7
+ attr_reader :hash_sha1
8
+ attr_reader :hash_data
9
+
10
+ def load_path(cache_path)
11
+ return unless File.exist?(cache_path)
12
+ begin
13
+ cache = JSON.parse(File.read(cache_path))
14
+ rescue => _
15
+ Output.puts_fail_message("配置文件缓存解析失败!将根据原配置文件进行仓库配置。")
16
+ end
17
+
18
+ @path = cache_path
19
+ @hash_sha1 = cache[Constants::CONFIG_CACHE_KEY[:hash]]
20
+ @hash_data = cache[Constants::CONFIG_CACHE_KEY[:cache]]
21
+ end
22
+
23
+ # 缓存配置文件
24
+ #
25
+ # @param cache_path [string] 配置文件目录
26
+ #
27
+ # @param hash_sha1 [String] 配置哈希字符串
28
+ #
29
+ # @param hash_data [Hash] 配置字典
30
+ #
31
+ def self.save_to_cache(cache_path, hash_sha1, hash_data)
32
+ FileUtils.mkdir_p(File.dirname(cache_path))
33
+ File.open(cache_path, 'w') do |file|
34
+ file.write({
35
+ Constants::CONFIG_CACHE_KEY[:hash] => hash_sha1,
36
+ Constants::CONFIG_CACHE_KEY[:cache] => hash_data
37
+ }.to_json)
38
+ end
39
+ end
40
+
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,182 @@
1
+
2
+ require 'm-git/manifest/cache_manager'
3
+
4
+ module MGit
5
+ class Manifest
6
+ module Internal
7
+
8
+
9
+ # 配置对象
10
+ #
11
+ # @param config_path [String] 配置文件路径或目录
12
+ #
13
+ # @param strict_mode [Boolean] 是否使用严格模式。在严格模式下,出错将终止执行。在非严格模式下,出错将抛出异常,程序有机会继续执行。
14
+ #
15
+ def __setup(config_path, strict_mode)
16
+ @strict_mode = strict_mode
17
+ if File.directory?(config_path)
18
+ config_dir = config_path
19
+ config_path = File.join(config_dir, Constants::CONFIG_FILE_NAME[:manifest])
20
+ else
21
+ config_dir = File.dirname(config_path)
22
+ config_path = File.join(config_dir, File.basename(config_path))
23
+ end
24
+
25
+ local_path = File.join(config_dir, Constants::CONFIG_FILE_NAME[:local_manifest])
26
+ cache_path = File.join(config_dir, Constants::CONFIG_FILE_NAME[:manifest_cache])
27
+
28
+ __load_config(config_path, local_config_path: local_path, cache_path: cache_path)
29
+ end
30
+
31
+ # 简单配置对象,部分属性为nil
32
+ #
33
+ # @param config_content [Hash] 配置Hash
34
+ #
35
+ # @param strict_mode [Boolean] 是否使用严格模式。在严格模式下,出错将终止执行。在非严格模式下,出错将抛出异常,程序有机会继续执行。
36
+ #
37
+ def __simple_setup(config_content, strict_mode)
38
+ @strict_mode = strict_mode
39
+
40
+ if config_content.is_a?(Hash)
41
+ @config = config_content.deep_clone
42
+ else
43
+ @config = JSON.parse(config_content)
44
+ end
45
+ lint_raw_json!(config)
46
+
47
+ @light_repos = __generate_light_repos(config)
48
+ @config_repo = light_repos.find { |light_repo| light_repo.is_config_repo }
49
+
50
+ # 计算配置文件哈希
51
+ @hash_sha1 = __generate_hash_sha1(config.to_json)
52
+ end
53
+
54
+
55
+ private
56
+
57
+ def cache_manager
58
+ @cache_manager ||= CacheManager.new
59
+ end
60
+
61
+
62
+ # 加载配置文件
63
+ #
64
+ def __load_config(config_path, config_content: nil, local_config_path: nil, cache_path: nil)
65
+ if config_content
66
+ config_hash = __parse_manifest_json(config_content)
67
+ else
68
+ # 校验配置文件路径
69
+ lint_manifest_path(config_path)
70
+
71
+ # 读取配置文件
72
+ config_hash = __parse_manifest(config_path)
73
+ end
74
+
75
+ lint_raw_json!(config_hash)
76
+
77
+ if local_config_path && File.exist?(local_config_path)
78
+ lint_local_manifest_path(local_config_path)
79
+ local_config_hash = __parse_manifest(local_config_path)
80
+
81
+ __merge_manifest_hash(config_hash, local_config_hash)
82
+ end
83
+
84
+ @light_repos = __generate_light_repos(config_hash)
85
+ @config_repo = light_repos.find { |light_repo| light_repo.is_config_repo }
86
+ lint_light_repos!
87
+
88
+ @path = config_path
89
+ @config = config_hash
90
+ # 计算配置文件哈希
91
+ @hash_sha1 = __generate_hash_sha1(config_hash.to_json)
92
+
93
+ __load_cache(cache_path)
94
+ if previous_config.nil? || previous_config != config_hash
95
+ # 更新缓存
96
+ @cache_path = cache_path
97
+ @previous_config = config
98
+ CacheManager.save_to_cache(cache_path, hash_sha1, config)
99
+ end
100
+ end
101
+
102
+ def __load_cache(cache_path)
103
+ if cache_path && File.exist?(cache_path)
104
+ cache_manager.load_path(cache_path)
105
+ @cache_path = cache_manager.path
106
+ @previous_config = cache_manager.hash_data
107
+
108
+ @previous_extra_light_repos = __generate_extra_light_repos(config, previous_config)
109
+ end
110
+ end
111
+
112
+ def __parse_manifest_json(raw_string)
113
+ begin
114
+ raw_json = JSON.parse(raw_string)
115
+ rescue => _
116
+ terminate!("manifest文件解析错误,请检查json文件格式是否正确!")
117
+ end
118
+
119
+ raw_json
120
+ end
121
+
122
+ def __parse_manifest(path)
123
+ begin
124
+ raw_string = File.read(path)
125
+ rescue => e
126
+ terminate!("配置文件#{path}读取失败:#{e.message}")
127
+ end
128
+ __parse_manifest_json(raw_string)
129
+ end
130
+
131
+ def __generate_light_repos(config_hash)
132
+ light_repos = []
133
+ repositories = config_hash[Constants::CONFIG_KEY[:repositories]]
134
+ repositories.each do |repo_name, repo_cfg|
135
+ light_repos << LightRepoGenerator.light_repo_with(repo_name, repo_cfg, config_hash)
136
+ end
137
+ light_repos
138
+ end
139
+
140
+ def __generate_extra_light_repos(current_hash, previous_hash)
141
+ return if previous_hash.nil?
142
+ extra_light_repos = []
143
+ repositories = current_hash[Constants::CONFIG_KEY[:repositories]]
144
+ previous_repos = previous_hash[Constants::CONFIG_KEY[:repositories]]
145
+ extra_keys = previous_repos.keys - repositories.keys
146
+ extra_keys.each do |repo_name|
147
+ extra_light_repos << LightRepoGenerator.light_repo_with(repo_name, previous_repos[repo_name], previous_hash)
148
+ end
149
+ extra_light_repos
150
+ end
151
+
152
+
153
+ # 计算配置文件的哈希
154
+ def __generate_hash_sha1(json_string)
155
+ begin
156
+ return Digest::SHA256.hexdigest(json_string)
157
+ rescue => e
158
+ terminate!("配置文件哈希计算失败:#{e.message}")
159
+ end
160
+ end
161
+
162
+ def __merge_manifest_hash(base_hash, attach_hash)
163
+ dict = base_hash
164
+ attach_hash.each { |key, value|
165
+ if key == Constants::CONFIG_KEY[:repositories] && value.is_a?(Hash)
166
+ dict[key] = {} if dict[key].nil?
167
+ value.each { |repo_name, config|
168
+ dict[key][repo_name] = {} if dict[key][repo_name].nil?
169
+ if config.is_a?(Hash)
170
+ config.each { |r_key, r_value|
171
+ dict[key][repo_name][r_key] = r_value if Constants::REPO_CONFIG_KEY.values.include?(r_key)
172
+ }
173
+ end
174
+ }
175
+ elsif Constants::CONFIG_KEY.values.include?(key)
176
+ dict[key] = value
177
+ end
178
+ }
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,108 @@
1
+ #coding=utf-8
2
+
3
+ require_relative 'light_repo_generator'
4
+
5
+ module MGit
6
+ class Manifest
7
+ # @!scope manifest.json 配置的reponsitories 对象
8
+ # 该类用于配置解析后的初步处理,其字段与配置文件一致,但不保证与本地仓库状态一致。
9
+ # 而Repo类则会校验本地仓库,其状态与本地仓库状态一致,主要用于执行多仓库操作。
10
+ #
11
+ LightRepo = Struct.new(
12
+ # [String] 仓库名
13
+ :name,
14
+
15
+ # [String] 仓库相对mgit根目录的存放路径(完整路径如:<.mgit所在路径>/path/<repo_name>),或绝对路径(指定abs-dest)
16
+ # 绝对路径
17
+ :path,
18
+
19
+ # [String] 仓库配置分支
20
+ :branch,
21
+
22
+ # [String] 仓库配置commit id
23
+ :commit_id,
24
+
25
+ # [String] 仓库配置tag
26
+ :tag,
27
+
28
+ # [String] 仓库的git地址
29
+ :url,
30
+
31
+ # [Boolean] 是否纳入mgit管理
32
+ :mgit_excluded,
33
+
34
+ # [Boolean] 是否是占位仓库【2.3.0废弃】
35
+ :dummy,
36
+
37
+ # [Boolean] 是否是配置仓库(包含配置表的仓库),若是,则某些操作如merge,checkout等会优先操作配置仓库,确保配置表最新
38
+ :is_config_repo,
39
+
40
+ # [Boolean] 是否锁定仓库(即任何操作均保证仓库状态与指定配置一致)
41
+ :lock
42
+ ) do
43
+
44
+ # 根据解析内容拼接一个clone地址
45
+ #
46
+ # @param root [String] mgit根目录
47
+ # @param local_url [String] default: nil 如果从本地clone,可传入一个本地的xxx.git实体地址
48
+ #
49
+ # @return [String] clone地址
50
+ #
51
+ def clone_url(root, local_url:nil, clone_branch:nil)
52
+ url = local_url.nil? ? self.url : local_url
53
+
54
+ if !clone_branch.nil? && Utils.branch_exist_on_remote?(clone_branch, url)
55
+ branch_opt = "-b #{clone_branch}"
56
+ elsif !self.branch.nil? && Utils.branch_exist_on_remote?(self.branch, url)
57
+ branch_opt = "-b #{self.branch}"
58
+ else
59
+ branch_opt = ''
60
+ end
61
+
62
+ "git clone #{branch_opt} -- #{url} #{abs_dest(root)}"
63
+ end
64
+
65
+ # 生成绝对路径
66
+ #
67
+ # @param root [String] 多仓库根目录
68
+ #
69
+ # @return [String] 仓库绝对路径
70
+ #
71
+ def abs_dest(root)
72
+ Utils.expand_path(self.path, base:root)
73
+ end
74
+
75
+ # 生成.git存储的完整路径
76
+ #
77
+ # @param root [String] 多仓库根目录
78
+ #
79
+ # @return [String] .git存储的完整路径
80
+ #
81
+ def git_store_dir(root)
82
+ # 替换key值中的‘/’字符,避免和路径混淆
83
+ name = self.name.gsub(/\//,':')
84
+
85
+ # 删除冗余字符
86
+ url = Utils.normalize_url(self.url)
87
+
88
+ git_store = Utils.generate_git_store(root, url)
89
+ if git_store.nil?
90
+ git_dir = File.join(root, Constants::PROJECT_DIR[:source_git])
91
+ git_store = File.join(git_dir, name)
92
+ end
93
+
94
+ git_store
95
+ end
96
+
97
+ # 生成缓存存放完整路径
98
+ #
99
+ # @param root [String] 多仓库根目录
100
+ #
101
+ # @return [String] 缓存存放完整路径
102
+ #
103
+ def cache_store_dir(root)
104
+ File.join(git_store_dir(root), 'cache')
105
+ end
106
+ end
107
+ end
108
+ end