solano 1.31.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 (54) hide show
  1. checksums.yaml +15 -0
  2. data/bin/solano +29 -0
  3. data/bin/tddium +29 -0
  4. data/lib/solano.rb +19 -0
  5. data/lib/solano/agent.rb +3 -0
  6. data/lib/solano/agent/solano.rb +128 -0
  7. data/lib/solano/cli.rb +25 -0
  8. data/lib/solano/cli/api.rb +368 -0
  9. data/lib/solano/cli/commands/account.rb +50 -0
  10. data/lib/solano/cli/commands/activate.rb +16 -0
  11. data/lib/solano/cli/commands/api.rb +15 -0
  12. data/lib/solano/cli/commands/config.rb +78 -0
  13. data/lib/solano/cli/commands/console.rb +85 -0
  14. data/lib/solano/cli/commands/describe.rb +104 -0
  15. data/lib/solano/cli/commands/find_failing.rb +64 -0
  16. data/lib/solano/cli/commands/heroku.rb +17 -0
  17. data/lib/solano/cli/commands/hg.rb +48 -0
  18. data/lib/solano/cli/commands/keys.rb +81 -0
  19. data/lib/solano/cli/commands/login.rb +37 -0
  20. data/lib/solano/cli/commands/logout.rb +14 -0
  21. data/lib/solano/cli/commands/password.rb +26 -0
  22. data/lib/solano/cli/commands/rerun.rb +59 -0
  23. data/lib/solano/cli/commands/server.rb +21 -0
  24. data/lib/solano/cli/commands/spec.rb +401 -0
  25. data/lib/solano/cli/commands/status.rb +117 -0
  26. data/lib/solano/cli/commands/stop.rb +19 -0
  27. data/lib/solano/cli/commands/suite.rb +110 -0
  28. data/lib/solano/cli/commands/support.rb +24 -0
  29. data/lib/solano/cli/commands/web.rb +29 -0
  30. data/lib/solano/cli/config.rb +246 -0
  31. data/lib/solano/cli/params_helper.rb +66 -0
  32. data/lib/solano/cli/prompt.rb +128 -0
  33. data/lib/solano/cli/show.rb +136 -0
  34. data/lib/solano/cli/solano.rb +208 -0
  35. data/lib/solano/cli/suite.rb +104 -0
  36. data/lib/solano/cli/text_helper.rb +16 -0
  37. data/lib/solano/cli/timeformat.rb +21 -0
  38. data/lib/solano/cli/util.rb +132 -0
  39. data/lib/solano/constant.rb +581 -0
  40. data/lib/solano/scm.rb +18 -0
  41. data/lib/solano/scm/configure.rb +37 -0
  42. data/lib/solano/scm/git.rb +349 -0
  43. data/lib/solano/scm/git_log_parser.rb +67 -0
  44. data/lib/solano/scm/hg.rb +263 -0
  45. data/lib/solano/scm/hg_log_parser.rb +66 -0
  46. data/lib/solano/scm/scm.rb +119 -0
  47. data/lib/solano/scm/scm_stub.rb +9 -0
  48. data/lib/solano/scm/url.rb +75 -0
  49. data/lib/solano/script.rb +12 -0
  50. data/lib/solano/script/git-remote-hg +1258 -0
  51. data/lib/solano/ssh.rb +66 -0
  52. data/lib/solano/util.rb +63 -0
  53. data/lib/solano/version.rb +5 -0
  54. metadata +413 -0
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SCM
5
+ SCMS = %w(git hg)
6
+ end
7
+ end
8
+
9
+ require 'solano/scm/git_log_parser'
10
+ require 'solano/scm/hg_log_parser'
11
+
12
+ require 'solano/scm/configure'
13
+ require 'solano/scm/url'
14
+
15
+ require 'solano/scm/scm'
16
+ require 'solano/scm/scm_stub'
17
+ require 'solano/scm/git'
18
+ require 'solano/scm/hg'
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class SCM
5
+ def self.configure
6
+ scm = nil
7
+ ok = false
8
+ scms = [::Solano::Git, ::Solano::Hg]
9
+
10
+ # Select SCM based on command availability and current repo type
11
+ scms.each do |scm_class|
12
+ sniff_scm = scm_class.new
13
+ if sniff_scm.repo? && scm_class.version_ok then
14
+ ok = true
15
+ scm = sniff_scm
16
+ break
17
+ end
18
+ end
19
+
20
+ # Fall back to first SCM type that is available
21
+ if !ok then
22
+ scms.each do |scm_class|
23
+ sniff_scm = scm_class.new
24
+ if scm_class.version_ok then
25
+ ok = true
26
+ scm = sniff_scm
27
+ break
28
+ end
29
+ end
30
+ end
31
+
32
+ # Default to a null SCM implementation
33
+ scm ||= ::Solano::StubSCM.new
34
+ return [scm, ok]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,349 @@
1
+ # Copyright (c) 2011-2016 Solano Labs All Rights Reserved
2
+
3
+ module Solano
4
+ class Git < SCM
5
+ include SolanoConstant
6
+
7
+ def initialize
8
+ super
9
+ end
10
+
11
+ def scm_name
12
+ return 'git'
13
+ end
14
+
15
+ def repo?
16
+ if File.directory?('.git') then
17
+ return true
18
+ end
19
+ ignore = `git status 2>&1`
20
+ ok = $?.success?
21
+ return ok
22
+ end
23
+
24
+ def root
25
+ root = `git rev-parse --show-toplevel 2>&1`
26
+ if $?.exitstatus == 0 then
27
+ root.chomp! if root
28
+ return root
29
+ end
30
+ return Dir.pwd
31
+ end
32
+
33
+ def mirror_path
34
+ return nil
35
+ end
36
+
37
+ def repo_name
38
+ return File.basename(self.root)
39
+ end
40
+
41
+ def origin_url
42
+ return @default_origin_url if @default_origin_url
43
+
44
+ result = `git config --get remote.origin.url`
45
+ return nil unless $?.success?
46
+
47
+ result = result.strip
48
+
49
+ # no slashes before first colon
50
+ # [user@]host.xz:path/to/repo.git/
51
+ scp_pat = /^([A-Za-z0-9_]+@)?([A-Za-z0-9._-]+):\/?([^\/].*)/
52
+ if m = scp_pat.match(result) then
53
+ result = "ssh://#{m[1]}#{m[2]}/#{m[3]}"
54
+ end
55
+
56
+ return result
57
+ end
58
+
59
+ def ignore_path
60
+ path = File.join(self.root, Config::GIT_IGNORE)
61
+ return path
62
+ end
63
+
64
+ def current_branch
65
+ `git symbolic-ref HEAD`.gsub("\n", "").split("/")[2..-1].join("/")
66
+ end
67
+
68
+ def default_branch
69
+ `git remote show origin | grep HEAD | awk '{print $3}'`.gsub("\n", "")
70
+ end
71
+
72
+ # XXX DANGER: This method will edit the current workspace. It's meant to
73
+ # be run to make a git mirror up-to-date.
74
+ def checkout(branch, options={})
75
+ if !!options[:update] then
76
+ `git fetch origin`
77
+ return false if !$?.success?
78
+ end
79
+
80
+ cmd = "git checkout "
81
+ if !!options[:force] then
82
+ cmd += "-f "
83
+ end
84
+ cmd += Shellwords.shellescape(branch)
85
+ `#{cmd}`
86
+
87
+ return false if !$?.success?
88
+
89
+ `git reset --hard origin/#{branch}`
90
+ return $?.success?
91
+ end
92
+
93
+ def changes?(options={})
94
+ return Solano::Git.git_changes?(:exclude=>".gitignore")
95
+ end
96
+
97
+ def push_latest(session_data, suite_details, options={})
98
+ branch = options[:branch] || self.current_branch
99
+ remote_branch = options[:remote_branch] || branch
100
+ git_repo_uri = if options[:git_repo_uri] then
101
+ options[:git_repo_uri]
102
+ elsif options[:use_private_uri] then
103
+ suite_details["git_repo_private_uri"] || suite_details["git_repo_uri"]
104
+ else
105
+ suite_details["git_repo_uri"]
106
+ end
107
+ this_ref = (session_data['commit_data'] || {})['git_ref']
108
+ refs = this_ref ? ["HEAD:#{this_ref}"] : []
109
+
110
+ if options[:git_repo_origin_uri] then
111
+ Solano::Git.git_set_remotes(options[:git_repo_origin_uri], 'origin')
112
+ end
113
+
114
+ Solano::Git.git_set_remotes(git_repo_uri)
115
+ return Solano::Git.git_push(branch, refs, remote_branch)
116
+ end
117
+
118
+ def current_commit
119
+ `git rev-parse --verify HEAD`.strip
120
+ end
121
+
122
+ def commits
123
+ commits = GitCommitLogParser.new(self.latest_commit).commits
124
+ return commits
125
+ end
126
+
127
+ def number_of_commits(id_from, id_to)
128
+ result = `git log --pretty='%H' #{id_from}..#{id_to}`
129
+ result.split("\n").length
130
+ end
131
+
132
+ def offer_snapshot_creation(session_id, options={})
133
+ say Text::Process::ASK_FOR_SNAPSHOT
134
+ answer = STDIN.gets.chomp
135
+ if /Y/.match(answer) then
136
+ create_snapshot(session_id, options.merge({ :force=>true }))
137
+ else
138
+ raise Text::Error::ANSWER_NOT_Y
139
+ end
140
+ end
141
+
142
+ def create_snapshot(session_id, options={})
143
+ api = options[:api]
144
+ res = api.request_snapshot_url({:session_id => session_id})
145
+ auth_url = res['auth_url']
146
+
147
+ say Text::Process::SNAPSHOT_URL % auth_url
148
+
149
+ unique = SecureRandom.hex(10)
150
+ snaphot_path = File.join(Dir.tmpdir,".solano-#{unique}-snapshot")
151
+ file = File.join(Dir.tmpdir, "solano-#{unique}-snapshot.tar")
152
+
153
+ if !options[:force] then
154
+ #git default branch
155
+ branch = options[:default_branch]
156
+ branch ||= self.default_branch
157
+ if branch.nil? then
158
+ raise Text::Error::DEFAULT_BRANCH
159
+ end
160
+ if branch == (`git rev-parse --abbrev-ref HEAD`).strip && !/Your branch is up-to-date with/.match(`git status`).nil? then
161
+ raise Text::Error::NEED_TO_FORCE % branch
162
+ end
163
+ say Text::Process::CREATING_REPO_SNAPSHOT_BRANCH % [root, branch]
164
+ out = `git clone --mirror -b #{branch} #{root} #{snaphot_path}`
165
+ if !$?.success? then
166
+ raise Text::Error::FAILED_TO_CREATE_SNAPSHOT % out
167
+ end
168
+ else
169
+ say Text::Process::CREATING_REPO_SNAPSHOT % root
170
+ out = `git clone --mirror #{root} #{snaphot_path}`
171
+ if !$?.success? then
172
+ raise Text::Error::FAILED_TO_CREATE_SNAPSHOT % out
173
+ end
174
+ end
175
+ out = `tar -C #{snaphot_path} -czpf #{file} .`
176
+ upload_file(auth_url, file)
177
+ Dir.chdir(snaphot_path){
178
+ @snap_id = (`git rev-parse HEAD`).strip
179
+ }
180
+
181
+ desc = {"url" => auth_url.gsub(/\?.*/,''),
182
+ "size" => File.stat(file).size,
183
+ "sha1"=> Digest::SHA1.file(file).hexdigest.upcase,
184
+ "commit_id"=> @snap_id,
185
+ "session_id" => session_id,
186
+ }
187
+ api.update_snapshot({:repo_snapshot => desc})
188
+ ensure
189
+ FileUtils.rm_rf(snaphot_path) if snaphot_path && File.exists?(snaphot_path)
190
+ FileUtils.rm_f(file) if file && File.exists?(file)
191
+ end
192
+
193
+ def create_patch(session_id, options={})
194
+ #oldest version of git that has been tested with diff patching
195
+ if !check_version('1.7.12.4') then
196
+ say Text::Warning::SAME_SNAPSHOT_COMMIT
197
+ warn(Text::Warning::GIT_VERSION_FOR_PATCH)
198
+ raise
199
+ end
200
+ api = options[:api]
201
+ patch_base_sha = options[:commit]
202
+ if "#{patch_base_sha}" == self.current_commit then
203
+ say Text::Warning::SAME_SNAPSHOT_COMMIT
204
+ return
205
+ end
206
+ #check if snapshot commit is known locally
207
+ `git branch -q --contains #{patch_base_sha}`
208
+ if !$?.success? then
209
+ #try and create a patch from upstream instread of repo snapshot
210
+ upstream = self.origin_url
211
+ reg = Regexp.new('([^\s]*)\s*' + upstream.to_s + '\s*\(fetch\)')
212
+ if !upstream.nil? && (reg_match = reg.match(`git remote -v`)) then
213
+ origin_name = reg_match[1]
214
+ end
215
+ origin_name ||= "origin"
216
+ say Text::Process::ATTEMPT_UPSTREAM_PATCH % upstream
217
+ #should be the remote name
218
+ patch_base_sha = `git rev-parse #{origin_name}`.to_s.strip
219
+ if !$?.success? then
220
+ say Text::Error::PATCH_CREATION_ERROR % patch_base_sha
221
+ offer_snapshot_creation(session_id, :api=>api)
222
+ return
223
+ end
224
+ end
225
+
226
+ file_name = "solano-#{SecureRandom.hex(10)}.patch"
227
+ file_path = File.join(Dir.tmpdir, file_name)
228
+ say Text::Process::CREATING_PATCH % [patch_base_sha, self.current_commit]
229
+ out = ` git diff-index -p --minimal #{patch_base_sha} > #{file_path}`
230
+ if !$?.success? then
231
+ say Text::Error::FAILED_TO_CREATE_PATCH % [patch_base_sha, out]
232
+ offer_snapshot_creation(session_id, :api=>api)
233
+ return
234
+ end
235
+
236
+ file_size = File.size(file_path)
237
+ if file_size != 0 then
238
+
239
+ file_sha1 = Digest::SHA1.file(file_path).hexdigest.upcase
240
+
241
+ #upload patch
242
+ say Text::Process::REQUST_PATCH_URL
243
+ res = api.request_patch_url({:session_id => session_id})
244
+ if (auth_url = res['auth_url']) then
245
+ say Text::Process::UPLOAD_PATCH % auth_url
246
+ upload_file(auth_url, file_path)
247
+ else
248
+ raise Text::Error::NO_PATCH_URL
249
+ end
250
+
251
+ args = { :session_id => session_id,
252
+ :sha1 => file_sha1,
253
+ :size => file_size,
254
+ :base_commit => patch_base_sha,
255
+ :git_version_used => current_version,
256
+ :cli_version_used => Solano::VERSION,
257
+ }
258
+
259
+ api.upload_session_patch(args)
260
+ else
261
+ say Text::Warning::EMPTY_PATCH
262
+ return
263
+ end
264
+
265
+ ensure
266
+ FileUtils.rm_rf(file_path) if file_path && File.exists?(file_path)
267
+ end
268
+
269
+ def current_version
270
+ `git --version`.strip.match(Dependency::VERSION_REGEXP)[0] rescue nil
271
+ end
272
+
273
+ def check_version(allowed_version)
274
+ Gem::Version.new(allowed_version) <= Gem::Version.new(current_version)
275
+ end
276
+
277
+ protected
278
+
279
+ def latest_commit
280
+ `git log --pretty='%H%n%s%n%aN%n%aE%n%at%n%cN%n%cE%n%ct%n' -1`
281
+ end
282
+
283
+ class << self
284
+ include SolanoConstant
285
+
286
+ def git_changes?(options={})
287
+ options[:exclude] ||= []
288
+ options[:exclude] = [options[:exclude]] unless options[:exclude].is_a?(Array)
289
+ cmd = "git status --porcelain -uno"
290
+ p = IO.popen(cmd)
291
+ changes = false
292
+ while line = p.gets do
293
+ line = line.strip
294
+ status, name = line.split(/\s+/)
295
+ next if options[:exclude].include?(name)
296
+ if status !~ /^\?/ then
297
+ changes = true
298
+ break
299
+ end
300
+ end
301
+ unless $?.success? then
302
+ warn(Text::Warning::SCM_UNABLE_TO_DETECT)
303
+ return false
304
+ end
305
+ return changes
306
+ end
307
+
308
+ def git_set_remotes(git_repo_uri, remote_name=nil)
309
+ remote_name ||= Config::REMOTE_NAME
310
+
311
+ unless `git remote show -n #{remote_name}` =~ /#{git_repo_uri}/
312
+ IO.popen("git remote rm #{remote_name}") {} # Discard output on *nix & windows
313
+ `git remote add #{remote_name} #{git_repo_uri.shellescape}`
314
+ end
315
+ end
316
+
317
+ def git_push(this_branch, additional_refs=[], remote_branch=nil)
318
+ say Text::Process::SCM_PUSH
319
+ remote_branch ||= this_branch
320
+ refs = ["#{this_branch}:#{remote_branch}"]
321
+ refs += additional_refs
322
+ refspec = refs.map(&:shellescape).join(" ")
323
+ cmd = "git push -f #{Config::REMOTE_NAME} #{refspec}"
324
+ say "Running '#{cmd}'"
325
+ system(cmd)
326
+ end
327
+
328
+ def version_ok
329
+ version = nil
330
+ begin
331
+ version_string = `git --version`
332
+ m = version_string.match(Dependency::VERSION_REGEXP)
333
+ version = m[0] unless m.nil?
334
+ rescue Errno
335
+ rescue Exception
336
+ end
337
+ if version.nil? || version.empty? then
338
+ return false
339
+ end
340
+ version_parts = version.split(".")
341
+ if version_parts[0].to_i < 1 ||
342
+ (version_parts[0].to_i < 2 && version_parts[1].to_i == 1 && version_parts[1].to_i < 7) then
343
+ warn(Text::Warning::GIT_VERSION % version)
344
+ end
345
+ true
346
+ end
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ # this is not namespaced under Solano because we want to eventually move this out into another gem
4
+ class GitCommitLogParser
5
+ attr_accessor :commit_log
6
+
7
+ # example commit_log generated by
8
+ # `git log --pretty='%H%n%s%n%aN%n%aE%n%at%n%cN%n%cE%n%ct%n' HEAD^..HEAD`
9
+
10
+ # 15e8cbd88d68d210953d51c28e26c6b9944a313b
11
+ # ignore .ruby-version for rvm
12
+ # Bob Smith
13
+ # bob@example.com
14
+ # 1367556311
15
+ # Fred Smith
16
+ # fred@example.com
17
+ # 1367556311
18
+ #
19
+
20
+ def initialize(commit_log)
21
+ @commit_log = commit_log
22
+ end
23
+
24
+ # Returns a list of commits in the following format
25
+ # [{
26
+ # "id" => "15e8cbd88d68d210953d51c28e26c6b9944a313b",
27
+ # "author" => {"name"=>"Bob Smith", "email"=>"bob@example.com"},
28
+ # "committer" => {"name"=>"Fred Smith", "email"=>"fred@example.com"},
29
+ # "summary" => "ignore .ruby-version for rvm",
30
+ # "date" => 1380603292
31
+ # }]
32
+
33
+ def commits
34
+ record = []
35
+ commits = []
36
+ commit_log.lines.each do |line|
37
+ line.strip!
38
+ line.sanitize!
39
+ if line.empty?
40
+ c = parse_commit(record)
41
+ commits.push(c)
42
+ record = []
43
+ else
44
+ record.push(line)
45
+ end
46
+ end
47
+
48
+ commits
49
+ end
50
+
51
+ private
52
+
53
+ def parse_commit(record)
54
+ time = record[4].to_i
55
+ author = build_user(record[2], record[3])
56
+ committer = build_user(record[5], record[6])
57
+ build_commit(record[0], author, committer, record[1], time)
58
+ end
59
+
60
+ def build_user(name, email)
61
+ {"name" => name, "email" => email}
62
+ end
63
+
64
+ def build_commit(sha, author, committer, summary, date)
65
+ {"id" => sha, "author" => author, "committer" => committer, "summary" => summary, "date" => date}
66
+ end
67
+ end