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