qcloudhive 0.1.0

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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/.idea/.rakeTasks +7 -0
  3. data/.idea/Hive.iml +54 -0
  4. data/.idea/dictionaries/dongzhao.xml +7 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/vcs.xml +6 -0
  8. data/.idea/workspace.xml +699 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Documents/Images/93A5AC58-17D5-4D3F-B5A8-E509CF429BD0.png +0 -0
  11. data/Documents/Images/C702C824-11B1-47A8-BB15-9F6F14F2B649.png +0 -0
  12. data/Gem/qcloudhive/.idea/misc.xml +4 -0
  13. data/Gem/qcloudhive/.idea/modules.xml +8 -0
  14. data/Gem/qcloudhive/.idea/qcloudhive.iml +8 -0
  15. data/Gem/qcloudhive/.idea/workspace.xml +383 -0
  16. data/Gemfile +4 -0
  17. data/Gemfile.lock +122 -0
  18. data/LICENSE.txt +21 -0
  19. data/README.md +273 -0
  20. data/Rakefile +5 -0
  21. data/bin/console +14 -0
  22. data/bin/setup +8 -0
  23. data/exe/Hive +302 -0
  24. data/lib/qcloudhive.rb +16 -0
  25. data/lib/qcloudhive/config.rb +176 -0
  26. data/lib/qcloudhive/feature.rb +92 -0
  27. data/lib/qcloudhive/framework.rb +192 -0
  28. data/lib/qcloudhive/git_helper.rb +75 -0
  29. data/lib/qcloudhive/gitlab.rb +69 -0
  30. data/lib/qcloudhive/manifest.rb +346 -0
  31. data/lib/qcloudhive/module.rb +228 -0
  32. data/lib/qcloudhive/pod_helper.rb +119 -0
  33. data/lib/qcloudhive/product.rb +32 -0
  34. data/lib/qcloudhive/project.rb +108 -0
  35. data/lib/qcloudhive/repo.rb +177 -0
  36. data/lib/qcloudhive/spec_helper.rb +45 -0
  37. data/lib/qcloudhive/utils.rb +46 -0
  38. data/lib/qcloudhive/version.rb +3 -0
  39. data/lib/qcloudhive/xcodeproj.rb +163 -0
  40. data/qcloudhive.gemspec +43 -0
  41. data/resources/shellscriptes/HiveApp.sh +156 -0
  42. data/resources/shellscriptes/HiveFramework +158 -0
  43. data/resources/shellscriptes/build_framework.sh +76 -0
  44. data/resources/templates/AppDefaultTemplate/.gitignore +41 -0
  45. data/resources/templates/AppDefaultTemplate/AppDefaultTemplate.xcodeproj/project.pbxproj +290 -0
  46. data/resources/templates/AppDefaultTemplate/AppDefaultTemplate.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  47. data/resources/templates/AppDefaultTemplate/AppDefaultTemplate/Info.plist +24 -0
  48. data/resources/templates/AppDefaultTemplate/Podfile +9 -0
  49. data/resources/templates/HiveAppTemplate/.gitignore +41 -0
  50. data/resources/templates/HiveAppTemplate/HiveAppTemplate.xcodeproj/project.pbxproj +539 -0
  51. data/resources/templates/HiveAppTemplate/HiveAppTemplate.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  52. data/resources/templates/HiveAppTemplate/HiveAppTemplate/AppDelegate.h +17 -0
  53. data/resources/templates/HiveAppTemplate/HiveAppTemplate/AppDelegate.m +51 -0
  54. data/resources/templates/HiveAppTemplate/HiveAppTemplate/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  55. data/resources/templates/HiveAppTemplate/HiveAppTemplate/Base.lproj/LaunchScreen.storyboard +27 -0
  56. data/resources/templates/HiveAppTemplate/HiveAppTemplate/Base.lproj/Main.storyboard +26 -0
  57. data/resources/templates/HiveAppTemplate/HiveAppTemplate/Info.plist +45 -0
  58. data/resources/templates/HiveAppTemplate/HiveAppTemplate/ViewController.h +15 -0
  59. data/resources/templates/HiveAppTemplate/HiveAppTemplate/ViewController.m +29 -0
  60. data/resources/templates/HiveAppTemplate/HiveAppTemplate/main.m +16 -0
  61. data/resources/templates/HiveAppTemplate/HiveAppTemplateTests/HiveAppTemplateTests.m +39 -0
  62. data/resources/templates/HiveAppTemplate/HiveAppTemplateTests/Info.plist +22 -0
  63. data/resources/templates/HiveAppTemplate/HiveAppTemplateUITests/HiveAppTemplateUITests.m +40 -0
  64. data/resources/templates/HiveAppTemplate/HiveAppTemplateUITests/Info.plist +22 -0
  65. data/resources/templates/HiveAppTemplate/Podfile +20 -0
  66. data/resources/templates/commonConf.sh +9 -0
  67. data/resources/templates/manifests/build.rb +8 -0
  68. data/resources/templates/manifests/default.xml +12 -0
  69. data/resources/templates/template.podspec +37 -0
  70. metadata +321 -0
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rake'
3
+ require 'optparse'
4
+ require 'pathname'
5
+ require 'uri'
6
+ require 'cocoapods'
7
+ require 'git'
8
+ require 'xcodeproj'
9
+ require 'parseconfig'
10
+ require 'qcloudhive/config'
11
+ require 'qcloudhive'
12
+ require 'qcloudhive/version'
13
+ require 'qcloudhive/project'
14
+ require 'qcloudhive/framework'
15
+ require 'qcloudhive/gitlab'
16
+ require 'qcloudhive/utils'
17
+ require 'qcloudhive/product'
18
+ require 'qcloudhive/manifest'
19
+ require 'qcloudhive/module'
20
+ require 'qcloudhive/repo'
21
+ require 'qcloudhive/xcodeproj'
22
+ require "qcloudhive/feature"
23
+ QCloudHive::Config.setup
24
+
25
+
26
+
27
+ # moudle commands
28
+ CMD_MODULE = "module"
29
+ CMD_MODULE_ADD = "add"
30
+ CMD_MODULE_REALASE = "release"
31
+ CMD_MODULE_DISLINK = 'dislink'
32
+ CMD_MODULE_GENERATE_CODE = "generate"
33
+
34
+ # manifest commands
35
+ CMD_MANIFEST = "manifest"
36
+ CMD_MANIFEST_PUSH = "push"
37
+
38
+ # framework commands
39
+ CMD_FRAMEWORK = "framework"
40
+ CMD_FRAMEWORK_BUILD = "build"
41
+
42
+ # product commands
43
+ CMD_PRODUCT = "product"
44
+ CMD_PRODUCT_INIT = "init"
45
+ CMD_PRODUCT_UPDATE = 'update'
46
+ CMD_PRODUCT_BUILD = 'build'
47
+
48
+
49
+ # project command
50
+ CMD_PROJECT = "project"
51
+ CMD_PROJECT_INIT = "init"
52
+
53
+
54
+ # repo command
55
+ CMD_REPO = "repo"
56
+ CMD_REPO_STATUS = "status"
57
+ CMD_REPO_REQUEST = 'request'
58
+
59
+ # feature command
60
+ CMD_FEATURE = "feature"
61
+ CMD_FEATURE_LIST = "list"
62
+ CMD_FEATURE_START = "start"
63
+ CMD_FEATURE_COMMIT = "commit"
64
+ CMD_FEATURE_PUSH = "push"
65
+ CMD_FEATURE_CHECKDEPENDENCY = "dependency"
66
+
67
+
68
+ puts "Hive-V:#{QCloudHive::VERSION} "
69
+ optionParser = OptionParser.new do |opts|
70
+ opts.banner = 'Hive is a toolkit for mananger iOS multi modules, it base google-repo & git & cocoapods'
71
+ opts.on('-h', '--help', 'Displays Help') do
72
+ puts opts
73
+ puts '''
74
+ Please use sub commnd:
75
+ create-framework create a module
76
+ push-manifest push the manifest files
77
+ init 创建新的产品,空
78
+ build-framework 打包framework
79
+ add-module add exist module
80
+ '''
81
+ exit
82
+ end
83
+ end
84
+
85
+ options = {}
86
+ #default value
87
+ # options[:c_repo] = false
88
+ REPO_COMMANDS = {
89
+ CMD_REPO_STATUS => OptionParser.new do |opts|
90
+ end ,
91
+ CMD_REPO_REQUEST => OptionParser.new do |opts|
92
+ opts.on("-m message", "--message Message", "the commit message") do |v|
93
+ options[:i_message] = v
94
+ end
95
+ end
96
+ }
97
+
98
+ MANIFEST_COMMANDS = {
99
+ CMD_MANIFEST_PUSH => OptionParser.new do |opts|
100
+ end
101
+ }
102
+ FRAMEWORK_COMMANDS = {
103
+ CMD_FRAMEWORK_BUILD => OptionParser.new do |opts|
104
+ opts.on("-n NAME", "--name Name", "the project name") do |v|
105
+ options[:i_name] = v
106
+ end
107
+ opts.on("-u a,b,c","--unlink a,b,c", Array, "Array unlined frameworks or modules") { |list|
108
+ options[:i_unlinked_frameworks] = list
109
+ }
110
+ opts.on("--[no-]sim", "build similuator arch") { |value|
111
+ options[:i_build_sim] = value
112
+ }
113
+ end,
114
+ }
115
+
116
+
117
+ PRODUCST_COMMANDS = {
118
+ CMD_PRODUCT_INIT => OptionParser.new do |opts|
119
+ opts.banner = "Create A Demo App (empty)"
120
+ opts.on("-n NAME", "--name Name", "the module name") do |v|
121
+ options[:c_name] = v
122
+ end
123
+ opts.on("-u URL", "--url url", "the git remote url") do |v|
124
+ options[:c_url] = v
125
+ end
126
+ end,
127
+ CMD_PRODUCT_UPDATE => OptionParser.new do |opts|
128
+ opts.banner = "update"
129
+ opts.on("-l" , "--library", "use static library ") do
130
+ L.info("setup use framework false")
131
+ L.info("Config #{QCloudHive::Config}")
132
+ QCloudHive::Config.useFrameworks = false
133
+ end
134
+ end,
135
+ CMD_PRODUCT_BUILD => OptionParser.new do |opts|
136
+ end
137
+ }
138
+
139
+ PROJECT_COMMANDS = {
140
+ CMD_PROJECT_INIT => OptionParser.new do |opts|
141
+ opts.on("-n NAME", "--name Name", "the project name") do |v|
142
+ options["name"] = v
143
+ end
144
+ end,
145
+ }
146
+
147
+ MODULE_COMMANDS = {
148
+ CMD_MODULE_ADD => OptionParser.new do |opts|
149
+ opts.banner = "Add a exist modules for ul"
150
+ opts.on("-n NAME", "--name Name", "the module name") do |v|
151
+ options[:c_name] = v
152
+ end
153
+ opts.on("-u URL", "--url url", "the git remote url") do |v|
154
+ options[:c_url] = v
155
+ end
156
+ opts.on("-p PATH", "--repo-path Path", "repo local relevate path") do |v|
157
+ options[:c_rpath] = v
158
+ end
159
+ end,
160
+ CMD_MODULE_REALASE => OptionParser.new do |opts|
161
+
162
+ end,
163
+
164
+ CMD_MODULE_DISLINK => OptionParser.new do |opts|
165
+ opts.banner = "delete a module form manifest"
166
+ opts.on("-n NAME", "--name Name", "the project name") do |v|
167
+ options[:i_name] = v
168
+ end
169
+ end,
170
+
171
+ CMD_MODULE_GENERATE_CODE => OptionParser.new do |opts|
172
+
173
+ end,
174
+ }
175
+
176
+ FEATURE_COMMANDS = {
177
+ CMD_FEATURE_LIST => OptionParser.new do |opts|
178
+ opts.banner = "delete a module form manifest"
179
+ opts.on("-n NAME", "--name Name", "the project name") do |v|
180
+ options[:i_name] = v
181
+ end
182
+ end,
183
+ CMD_FEATURE_START => OptionParser.new do |opts|
184
+ opts.banner = "delete a module form manifest"
185
+ opts.on("-n NAME", "--name Name", "the project name") do |v|
186
+ options[:i_name] = v
187
+ end
188
+ end,
189
+ CMD_FEATURE_COMMIT => OptionParser.new do |opts|
190
+ opts.banner = "delete a module form manifest"
191
+ opts.on("-m message", "--message Message", "the commit message") do |v|
192
+ options[:i_message] = v
193
+ end
194
+ end,
195
+ CMD_FEATURE_PUSH => OptionParser.new do |opts|
196
+ end,
197
+ CMD_FEATURE_CHECKDEPENDENCY =>OptionParser.new do |opts|
198
+ end,
199
+ }
200
+
201
+
202
+ GLOBAL_COMMANDS = {
203
+ CMD_MANIFEST => MANIFEST_COMMANDS,
204
+ CMD_PROJECT => PROJECT_COMMANDS,
205
+ CMD_MODULE => MODULE_COMMANDS,
206
+ CMD_PRODUCT => PRODUCST_COMMANDS,
207
+ CMD_FRAMEWORK => FRAMEWORK_COMMANDS,
208
+ CMD_REPO => REPO_COMMANDS,
209
+ CMD_FEATURE => FEATURE_COMMANDS,
210
+ }
211
+
212
+
213
+
214
+ # begin parse and exe
215
+ optionParser.order!
216
+ command = ARGV.shift
217
+ if command == nil
218
+ L.error "Please input sub command"
219
+ exit(1)
220
+ end
221
+ cmd = GLOBAL_COMMANDS[command]
222
+ if cmd == nil
223
+ L.error "Please input sub command, unkonw cmd [#{command}]"
224
+ exit(1)
225
+ end
226
+
227
+ if cmd.instance_of? OptionParser
228
+ cmd.order!
229
+ end
230
+
231
+ L.debug "Command: #{command} "
232
+
233
+ COMMAND_MAPPER = {
234
+ CMD_PROJECT => {
235
+ CMD_PROJECT_INIT => Proc.new do |c, opts|
236
+ QCloudHive::Project.init(c, opts)
237
+ end,
238
+ },
239
+ CMD_MODULE => {
240
+ CMD_MODULE_ADD => Proc.new do |c, opts|
241
+ QCloudHive::HiveModule.add(c,opts)
242
+ end,
243
+ CMD_MODULE_REALASE => Proc.new do |c, opts|
244
+ QCloudHive::HiveModule.release(c, opts)
245
+ end,
246
+ CMD_MODULE_DISLINK => Proc.new do |c, opts|
247
+ QCloudHive::HiveModule.dislink(c, opts)
248
+ end
249
+ },
250
+ CMD_MANIFEST => {
251
+ CMD_MANIFEST_PUSH => Proc.new do |c, opts|
252
+ QCloudHive.syncManifest
253
+ end
254
+ },
255
+ CMD_FRAMEWORK => {
256
+ CMD_FRAMEWORK_BUILD => Proc.new do |c, opts|
257
+ QCloudHive.BuildFramework(c, opts)
258
+ end
259
+ },
260
+ CMD_PRODUCT => {
261
+ CMD_PRODUCT_INIT => Proc.new do |c, opts|
262
+ QCloudHive.CreateAPP(c, opts)
263
+ end,
264
+ CMD_PRODUCT_UPDATE => Proc.new do |c, opts|
265
+ QCloudHive.UpdateProducts c, opts
266
+ end,
267
+ CMD_PRODUCT_BUILD => Proc.new do |c, opts|
268
+ QCloudHive.RemapProduct c, opts
269
+ end
270
+ },
271
+
272
+ CMD_REPO => {
273
+ CMD_REPO_STATUS => Proc.new do |c, opts|
274
+ QCloudHive::Repo.checkStatus(c, opts)
275
+ end,
276
+ CMD_REPO_REQUEST => Proc.new do |c, opts|
277
+ QCloudHive::Repo.request(c, opts)
278
+ end
279
+ },
280
+
281
+ CMD_FEATURE => {
282
+ CMD_FEATURE_COMMIT => Proc.new do |c, opts|
283
+ QCloudHive.commitFeature(c, opts)
284
+ end,
285
+ CMD_FEATURE_PUSH => Proc.new do |c, opts|
286
+ QCloudHive.pushFeature(c, opts)
287
+ end,
288
+ CMD_FEATURE_CHECKDEPENDENCY => Proc.new do |c, opts|
289
+ QCloudHive.checkDependencies(c, opts)
290
+ end,
291
+ }
292
+ }
293
+
294
+
295
+ if GLOBAL_COMMANDS[command].instance_of? Hash
296
+ subcommand = ARGV.shift
297
+ cmd = GLOBAL_COMMANDS[command][subcommand]
298
+ cmd.order!
299
+ method = COMMAND_MAPPER[command][subcommand]
300
+ L.debug "#{options}"
301
+ method.call(cmd , options)
302
+ end
@@ -0,0 +1,16 @@
1
+ require "qcloudhive/version"
2
+
3
+ module QCloudHive
4
+ require 'pathname'
5
+ require 'qcloudhive/version'
6
+ require 'qcloudhive/project'
7
+ require 'qcloudhive/framework'
8
+ require 'qcloudhive/config'
9
+ require 'qcloudhive/gitlab'
10
+ require 'qcloudhive/utils'
11
+ require 'qcloudhive/product'
12
+ require 'qcloudhive/manifest'
13
+ require 'qcloudhive/module'
14
+ require 'qcloudhive/repo'
15
+ require 'colorize'
16
+ end
@@ -0,0 +1,176 @@
1
+ require 'parseconfig'
2
+ require "pathname"
3
+ require_relative 'utils'
4
+
5
+
6
+ module QCloudHive
7
+ CODE_OA_GROUDP = "CodeOA"
8
+ CODE_OA_CONFIG_PRIVATE_TOKEN = "private_token"
9
+ CODE_OA_TEAM_NAME = "team"
10
+ REPO_ROOT_NAME = ".repo"
11
+ module Config
12
+ SHARE_CONFIG = "Share"
13
+ USE_FRAMEWORKS = "use_framework"
14
+ class << self
15
+ @@projectRootDirectory = nil
16
+ @@reporoot = nil
17
+ @@manifest = nil
18
+ @@setuped = false
19
+ attr_accessor :team
20
+ attr_accessor :cmdPath
21
+ attr_accessor :runPath
22
+ attr_accessor :namespaceID
23
+ attr_accessor :templateManifestPath
24
+ attr_accessor :templatesPath
25
+ attr_accessor :scriptesDirectory
26
+ attr_accessor :bitcodeEnable
27
+ attr_accessor :buildFromCommit
28
+ attr_accessor :useFrameworks
29
+ end
30
+
31
+ def Config.repoRoot()
32
+ if @@reporoot == nil
33
+ @@reporoot = QCloudHive.FindRepoRoot(Dir.pwd)
34
+ if @@reporoot == nil
35
+ Error "当前不再任何项目目录下面,请确认!!!"
36
+ end
37
+ end
38
+ return @@reporoot
39
+ end
40
+ def Config.dumpShareConfig
41
+ conf = "[#{SHARE_CONFIG}]
42
+ #{USE_FRAMEWORKS} = #{useFrameworks}"
43
+ file = Pathname(repoRoot()).join("share.conf").to_path
44
+ File.open(file, "w") { |f|
45
+ f.write conf
46
+ }
47
+ end
48
+ def Config.loadShareConfig
49
+ configFilePath = Pathname(repoRoot()).join("share.conf").to_path
50
+ if File.exist?(configFilePath) != true
51
+ return
52
+ end
53
+ config = ParseConfig.new(configFilePath)
54
+ L.info("config #{config} #{config.class}")
55
+ if not config.nil?
56
+ shareConfig = config[SHARE_CONFIG]
57
+ L.info("shareConfig #{shareConfig} #{shareConfig.class}")
58
+ if not shareConfig.nil?
59
+ useFramework = config[SHARE_CONFIG][USE_FRAMEWORKS]
60
+ if useFramework == "true"
61
+ Config.useFrameworks = true
62
+ elsif useFramework == "false"
63
+ Config.useFrameworks = false
64
+ end
65
+ L.info("#{useFramework} #{useFramework.class}")
66
+ end
67
+ end
68
+ end
69
+ def Config.projectRootDirectory()
70
+ if @@projectRootDirectory.nil?
71
+ @@projectRootDirectory = Pathname(repoRoot).parent.to_path
72
+ end
73
+ return @@projectRootDirectory
74
+ end
75
+ def Config.version()
76
+ return QCloudHive::VERSION
77
+ end
78
+
79
+ def Config.podSource()
80
+ Pod::Config::instance.sources_manager.aggregate
81
+ end
82
+
83
+ def Config.manifest()
84
+ if @@manifest == nil
85
+ manifestPath = repoRoot+ "manifests/default.xml"
86
+ L.debug "manifestPath #{manifestPath}"
87
+ @@manifest = Manifest.new(manifestPath)
88
+ end
89
+ return @@manifest
90
+ end
91
+
92
+ def Config.setup()
93
+ if @@setuped == true
94
+ return
95
+ end
96
+ @@setuped = true
97
+ Config.useFrameworks = true
98
+ configFilePath = Pathname("~/.hiveconfig").expand_path
99
+ if File.exist?(configFilePath) != true
100
+ raise "Hive 配置文件不存在,请检查"
101
+ end
102
+ config = ParseConfig.new(configFilePath)
103
+ if config[CODE_OA_GROUDP].nil?
104
+ Error "没有CodeOA的配置信息,请检查"
105
+ end
106
+ private_token = config[CODE_OA_GROUDP][CODE_OA_CONFIG_PRIVATE_TOKEN]
107
+ if private_token == nil
108
+ raise "没有设置CodeOA的private_token请检查并设置"
109
+ end
110
+ team = config[CODE_OA_GROUDP][CODE_OA_TEAM_NAME]
111
+ if team == nil
112
+ raise "没有设置Team Name请设置"
113
+ end
114
+ #log level config
115
+ hiveGroup = config["Hive"]
116
+ if not hiveGroup.nil?
117
+ logLevel = hiveGroup["loglevel"]
118
+ if logLevel.nil?
119
+ L.level = Logger::ERROR
120
+ elsif logLevel == "INFO"
121
+ L.level = Logger::INFO
122
+ elsif logLevel == "DEBUG"
123
+ L.level = Logger::DEBUG
124
+ elsif logLevel == "ERROR"
125
+ L.level = Logger::ERROR
126
+ end
127
+ end
128
+
129
+ puts "LogLevel ........ #{L.level} logLevel"
130
+ #code OA
131
+ CodeOA.setup(private_token)
132
+ Config.team = team
133
+ Config.cmdPath = Pathname.new(__FILE__).realpath.parent.parent.parent.to_path+"/"
134
+ Config.runPath = Dir.pwd + "/"
135
+ Config.templatesPath = Config.cmdPath+"resources/templates/"
136
+ Config.templateManifestPath = Config.templatesPath+"manifests/"
137
+ Config.scriptesDirectory = Config.cmdPath+"resources/shellscriptes/"
138
+ L.info "manifest path is #{Config.templateManifestPath}"
139
+ codeGroup = Gitlab.groups.select{|g| g.name == team}.first
140
+ if codeGroup == nil
141
+ Error("您不属于Group #{team}")
142
+ end
143
+ Config.namespaceID = codeGroup.id
144
+ @@reporoot = nil
145
+ @@manifest = nil
146
+ @@projectRootDirectory = nil
147
+ @@bitcodeEnable = false
148
+ @@buildFromCommit = false
149
+ end
150
+ end
151
+ def QCloudHive.FindRepoRoot(path)
152
+ pwd = Pathname(path)
153
+ if pwd.to_path == "/"
154
+ return nil
155
+ end
156
+ if pwd.to_path == '.repo'
157
+ return pwd
158
+ end
159
+ aimPath = nil
160
+ pwd.entries.each { |x|
161
+ if x.to_path == REPO_ROOT_NAME
162
+ aimPath = pwd.join(x)
163
+ break
164
+ end
165
+ }
166
+ if aimPath != nil
167
+ return aimPath
168
+ else
169
+ parentDir = pwd.dirname
170
+ if parentDir.to_path == REPO_ROOT_NAME
171
+ return nil
172
+ end
173
+ return FindRepoRoot(parentDir)
174
+ end
175
+ end
176
+ end