solano 1.31.0

Sign up to get free protection for your applications and to get access to all the features.
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