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.
- checksums.yaml +15 -0
- data/bin/solano +29 -0
- data/bin/tddium +29 -0
- data/lib/solano.rb +19 -0
- data/lib/solano/agent.rb +3 -0
- data/lib/solano/agent/solano.rb +128 -0
- data/lib/solano/cli.rb +25 -0
- data/lib/solano/cli/api.rb +368 -0
- data/lib/solano/cli/commands/account.rb +50 -0
- data/lib/solano/cli/commands/activate.rb +16 -0
- data/lib/solano/cli/commands/api.rb +15 -0
- data/lib/solano/cli/commands/config.rb +78 -0
- data/lib/solano/cli/commands/console.rb +85 -0
- data/lib/solano/cli/commands/describe.rb +104 -0
- data/lib/solano/cli/commands/find_failing.rb +64 -0
- data/lib/solano/cli/commands/heroku.rb +17 -0
- data/lib/solano/cli/commands/hg.rb +48 -0
- data/lib/solano/cli/commands/keys.rb +81 -0
- data/lib/solano/cli/commands/login.rb +37 -0
- data/lib/solano/cli/commands/logout.rb +14 -0
- data/lib/solano/cli/commands/password.rb +26 -0
- data/lib/solano/cli/commands/rerun.rb +59 -0
- data/lib/solano/cli/commands/server.rb +21 -0
- data/lib/solano/cli/commands/spec.rb +401 -0
- data/lib/solano/cli/commands/status.rb +117 -0
- data/lib/solano/cli/commands/stop.rb +19 -0
- data/lib/solano/cli/commands/suite.rb +110 -0
- data/lib/solano/cli/commands/support.rb +24 -0
- data/lib/solano/cli/commands/web.rb +29 -0
- data/lib/solano/cli/config.rb +246 -0
- data/lib/solano/cli/params_helper.rb +66 -0
- data/lib/solano/cli/prompt.rb +128 -0
- data/lib/solano/cli/show.rb +136 -0
- data/lib/solano/cli/solano.rb +208 -0
- data/lib/solano/cli/suite.rb +104 -0
- data/lib/solano/cli/text_helper.rb +16 -0
- data/lib/solano/cli/timeformat.rb +21 -0
- data/lib/solano/cli/util.rb +132 -0
- data/lib/solano/constant.rb +581 -0
- data/lib/solano/scm.rb +18 -0
- data/lib/solano/scm/configure.rb +37 -0
- data/lib/solano/scm/git.rb +349 -0
- data/lib/solano/scm/git_log_parser.rb +67 -0
- data/lib/solano/scm/hg.rb +263 -0
- data/lib/solano/scm/hg_log_parser.rb +66 -0
- data/lib/solano/scm/scm.rb +119 -0
- data/lib/solano/scm/scm_stub.rb +9 -0
- data/lib/solano/scm/url.rb +75 -0
- data/lib/solano/script.rb +12 -0
- data/lib/solano/script/git-remote-hg +1258 -0
- data/lib/solano/ssh.rb +66 -0
- data/lib/solano/util.rb +63 -0
- data/lib/solano/version.rb +5 -0
- metadata +413 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
# Copyright (c) 2011-2015 Solano Labs All Rights Reserved
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
module Solano
|
7
|
+
class Hg < SCM
|
8
|
+
include SolanoConstant
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def scm_name
|
15
|
+
return 'hg'
|
16
|
+
end
|
17
|
+
|
18
|
+
def repo?
|
19
|
+
if File.directory?('.hg') then
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
ignore = `hg status 2>&1`
|
23
|
+
ok = $?.success?
|
24
|
+
return ok
|
25
|
+
end
|
26
|
+
|
27
|
+
def root
|
28
|
+
root = `hg root`
|
29
|
+
if $?.exitstatus == 0 then
|
30
|
+
root.chomp! if root
|
31
|
+
return root
|
32
|
+
end
|
33
|
+
return Dir.pwd
|
34
|
+
end
|
35
|
+
|
36
|
+
def repo_name
|
37
|
+
return File.basename(self.root)
|
38
|
+
end
|
39
|
+
|
40
|
+
def origin_url
|
41
|
+
return @default_origin_url if @default_origin_url
|
42
|
+
|
43
|
+
result = `hg paths default`
|
44
|
+
return nil unless $?.success?
|
45
|
+
return nil if result.empty?
|
46
|
+
result.strip!
|
47
|
+
u = URI.parse(result) rescue nil
|
48
|
+
if u && u.host.nil? then
|
49
|
+
warn(Text::Warning::HG_PATHS_DEFAULT_NOT_URI)
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
return result
|
53
|
+
end
|
54
|
+
|
55
|
+
def ignore_path
|
56
|
+
path = File.join(self.root, Config::HG_IGNORE)
|
57
|
+
return path
|
58
|
+
end
|
59
|
+
|
60
|
+
def current_branch
|
61
|
+
branch = `hg branch`
|
62
|
+
branch.chomp!
|
63
|
+
return branch
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_branch
|
67
|
+
# NOTE: not necessarily quite right in HG 2.1+ with a default bookmark
|
68
|
+
return "default"
|
69
|
+
end
|
70
|
+
|
71
|
+
def changes?(options={})
|
72
|
+
return Solano::Hg.hg_changes?(:exclude=>".hgignore")
|
73
|
+
end
|
74
|
+
|
75
|
+
def hg_push(uri)
|
76
|
+
cmd = "hg push -f -b #{self.current_branch} "
|
77
|
+
cmd += " #{uri}"
|
78
|
+
|
79
|
+
# git outputs something to stderr when it runs git push.
|
80
|
+
# hg doesn't always ... so show the command that's being run and its
|
81
|
+
# output to indicate progress.
|
82
|
+
puts cmd
|
83
|
+
puts `#{cmd}`
|
84
|
+
return [0,1].include?( $?.exitstatus )
|
85
|
+
end
|
86
|
+
|
87
|
+
def push_latest(session_data, suite_details, options={})
|
88
|
+
uri = if options[:use_private_uri] then
|
89
|
+
suite_details["git_repo_private_uri"] || suite_details["git_repo_uri"]
|
90
|
+
else
|
91
|
+
suite_details["git_repo_uri"]
|
92
|
+
end
|
93
|
+
self.hg_push(uri)
|
94
|
+
end
|
95
|
+
|
96
|
+
def current_commit
|
97
|
+
commit = `hg id -i`
|
98
|
+
commit.chomp!
|
99
|
+
return commit
|
100
|
+
end
|
101
|
+
|
102
|
+
def commits
|
103
|
+
commits = HgCommitLogParser.new(self.latest_commit).commits
|
104
|
+
return commits
|
105
|
+
end
|
106
|
+
|
107
|
+
def number_of_commits(id_from, id_to)
|
108
|
+
result = `hg log --template='{node}\\n' #{id_from}..#{id_to}`
|
109
|
+
result.split("\n").length
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_snapshot(session_id, options={})
|
113
|
+
api = options[:api]
|
114
|
+
res = api.request_snapshot_url({:session_id => session_id})
|
115
|
+
auth_url = res['auth_url']
|
116
|
+
|
117
|
+
say Text::Process::SNAPSHOT_URL % auth_url
|
118
|
+
|
119
|
+
unique = SecureRandom.hex(10)
|
120
|
+
snaphot_path = File.join(Dir.tmpdir,".solano-#{unique}-snapshot")
|
121
|
+
file = File.join(Dir.tmpdir, "solano-#{unique}-snapshot.tar")
|
122
|
+
|
123
|
+
# #git default branch
|
124
|
+
# branch = options[:default_branch]
|
125
|
+
# branch ||= /\-\>.*\/(.*)$/.match( (`git branch -r | grep origin/HEAD`).strip)[1]
|
126
|
+
|
127
|
+
# if branch.nil? then
|
128
|
+
# raise Text::Error::DEFAULT_BRANCH
|
129
|
+
# end
|
130
|
+
|
131
|
+
out = `hg clone #{root} #{snaphot_path}`
|
132
|
+
out = `tar -C #{snaphot_path} -czpf #{file} .`
|
133
|
+
upload_file(auth_url, file)
|
134
|
+
Dir.chdir(snaphot_path){
|
135
|
+
@snap_id = (`hg --debug id -i`).strip
|
136
|
+
}
|
137
|
+
|
138
|
+
desc = {"url" => auth_url.gsub(/\?.*/,''),
|
139
|
+
"size" => File.stat(file).size,
|
140
|
+
"sha1"=> Digest::SHA1.file(file).hexdigest.upcase,
|
141
|
+
"commit_id"=> @snap_id,
|
142
|
+
"session_id" => session_id,
|
143
|
+
}
|
144
|
+
api.update_snapshot({:repo_snapshot => desc})
|
145
|
+
ensure
|
146
|
+
FileUtils.rm_rf(snaphot_path) if snaphot_path && File.exists?(snaphot_path)
|
147
|
+
FileUtils.rm_f(file) if file && File.exists?(file)
|
148
|
+
end
|
149
|
+
|
150
|
+
def create_patch(session_id, options={})
|
151
|
+
api = options[:api]
|
152
|
+
snapshot_commit = options[:commit]
|
153
|
+
if "#{snapshot_commit}" == `hg --debug id -i`.to_s.strip then
|
154
|
+
say Text::Warning::SAME_SNAPSHOT_COMMIT
|
155
|
+
return
|
156
|
+
end
|
157
|
+
#check if commit is known locally
|
158
|
+
if (`hg log --rev "ancestors(.) and #{snapshot_commit}" 2>&1 >/dev/null | grep -o 'error:' | wc -l`).to_i > 0 then
|
159
|
+
raise Text::Error::PATCH_CREATION_ERROR % snapshot_commit
|
160
|
+
end
|
161
|
+
|
162
|
+
file_name = "solano-#{SecureRandom.hex(10)}.patch"
|
163
|
+
tmp_dir = Dir.mktmpdir("patches")
|
164
|
+
file_path = File.join(tmp_dir, file_name)
|
165
|
+
Dir
|
166
|
+
out = `hg export -o #{tmp_dir}/patch-%n -r #{snapshot_commit}:tip`
|
167
|
+
say out
|
168
|
+
build_patch(tmp_dir, file_path)
|
169
|
+
|
170
|
+
file_size = File.size(file_path)
|
171
|
+
file_sha1 = Digest::SHA1.file(file_path).hexdigest.upcase
|
172
|
+
|
173
|
+
#upload patch
|
174
|
+
say Text::Process::REQUST_PATCH_URL
|
175
|
+
res = api.request_patch_url({:session_id => session_id})
|
176
|
+
if (auth_url = res['auth_url']) then
|
177
|
+
say Text::Process::UPLOAD_PATCH % auth_url
|
178
|
+
upload_file(auth_url, file_path)
|
179
|
+
else
|
180
|
+
raise Text::Error::NO_PATCH_URL
|
181
|
+
end
|
182
|
+
|
183
|
+
args = { :session_id => session_id,
|
184
|
+
:sha1 => file_sha1,
|
185
|
+
:size => file_size,}
|
186
|
+
api.upload_session_patch(args)
|
187
|
+
|
188
|
+
ensure
|
189
|
+
FileUtils.rm_rf(file_path) if file_path && File.exists?(file_path)
|
190
|
+
FileUtils.rm_rf(tmp_dir) if tmp_dir && File.exists?(tmp_dir)
|
191
|
+
end
|
192
|
+
|
193
|
+
protected
|
194
|
+
|
195
|
+
def build_patch(tmp_dir, file_path)
|
196
|
+
#patch currently includes one two many commits
|
197
|
+
files = Dir.glob(File.join(tmp_dir,"patch-*"))
|
198
|
+
files.sort!
|
199
|
+
files.shift
|
200
|
+
|
201
|
+
File.open( file_path, "w" ){|f_out|
|
202
|
+
files.each {|f_name|
|
203
|
+
File.open(f_name){|f_in|
|
204
|
+
f_in.each {|f_str| f_out.puts(f_str) }
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
def latest_commit
|
211
|
+
`hg log -f -l 1 --template='{node}\\n{desc|firstline}\\n{author|user}\\n{author|email}\\n{date}\\n{author|user}\\n{author|email}\\n{date}\\n\\n'`
|
212
|
+
end
|
213
|
+
|
214
|
+
class << self
|
215
|
+
include SolanoConstant
|
216
|
+
|
217
|
+
def hg_changes?(options={})
|
218
|
+
options[:exclude] ||= []
|
219
|
+
options[:exclude] = [options[:exclude]] unless options[:exclude].is_a?(Array)
|
220
|
+
cmd = "hg status -mardu"
|
221
|
+
p = IO.popen(cmd)
|
222
|
+
changes = false
|
223
|
+
while line = p.gets do
|
224
|
+
line = line.strip
|
225
|
+
status, name = line.split(/\s+/)
|
226
|
+
next if options[:exclude].include?(name)
|
227
|
+
if status !~ /^\?/ then
|
228
|
+
changes = true
|
229
|
+
break
|
230
|
+
end
|
231
|
+
end
|
232
|
+
unless $?.success? then
|
233
|
+
warn(Text::Warning::SCM_UNABLE_TO_DETECT)
|
234
|
+
return false
|
235
|
+
end
|
236
|
+
return changes
|
237
|
+
end
|
238
|
+
|
239
|
+
def hg_push(this_branch, additional_refs=[])
|
240
|
+
raise "not implemented"
|
241
|
+
end
|
242
|
+
|
243
|
+
def version_ok
|
244
|
+
version = nil
|
245
|
+
begin
|
246
|
+
version_string = `hg -q --version`
|
247
|
+
m = version_string.match(Dependency::VERSION_REGEXP)
|
248
|
+
version = m[0] unless m.nil?
|
249
|
+
rescue Errno
|
250
|
+
rescue Exception
|
251
|
+
end
|
252
|
+
if version.nil? || version.empty? then
|
253
|
+
return false
|
254
|
+
end
|
255
|
+
version_parts = version.split(".")
|
256
|
+
if version_parts[0].to_i < 2 then
|
257
|
+
warn(Text::Warning::HG_VERSION % version)
|
258
|
+
end
|
259
|
+
true
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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 HgCommitLogParser
|
5
|
+
attr_accessor :commit_log
|
6
|
+
|
7
|
+
# example commit_log generated by
|
8
|
+
# `hg log -f -l 1 --template='{node}\n{desc|firstline}\n{author|user}\n{author|email}\n{date}\n{author|user}\n{author|email}\n{date}\n'`
|
9
|
+
|
10
|
+
# beb3d918995bbe6370bb21fd76b4f433bfd64dc4
|
11
|
+
# commit summary
|
12
|
+
# user
|
13
|
+
# user@host.domain
|
14
|
+
# 1399437808.00
|
15
|
+
# user
|
16
|
+
# user@host.domain
|
17
|
+
# 1399437808.00
|
18
|
+
|
19
|
+
def initialize(commit_log)
|
20
|
+
@commit_log = commit_log
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns a list of commits in the following format
|
24
|
+
# [{
|
25
|
+
# "id" => "15e8cbd88d68d210953d51c28e26c6b9944a313b",
|
26
|
+
# "author" => {"name"=>"Bob Smith", "email"=>"bob@example.com"},
|
27
|
+
# "committer" => {"name"=>"Fred Smith", "email"=>"fred@example.com"},
|
28
|
+
# "summary" => "ignore .ruby-version for rvm",
|
29
|
+
# "date" => 1380603292
|
30
|
+
# }]
|
31
|
+
|
32
|
+
def commits
|
33
|
+
record = []
|
34
|
+
commits = []
|
35
|
+
commit_log.lines.each do |line|
|
36
|
+
line.strip!
|
37
|
+
line.sanitize!
|
38
|
+
if line.empty?
|
39
|
+
c = parse_commit(record)
|
40
|
+
commits.push(c)
|
41
|
+
record = []
|
42
|
+
else
|
43
|
+
record.push(line)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
commits
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def parse_commit(record)
|
53
|
+
time = record[4].to_i
|
54
|
+
author = build_user(record[2], record[3])
|
55
|
+
committer = build_user(record[5], record[6])
|
56
|
+
build_commit(record[0], author, committer, record[1], time)
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_user(name, email)
|
60
|
+
{"name" => name, "email" => email}
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_commit(sha, author, committer, summary, date)
|
64
|
+
{"id" => sha, "author" => author, "committer" => committer, "summary" => summary, "date" => date}
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Copyright (c) 2011-2015 Solano Labs All Rights Reserved
|
2
|
+
|
3
|
+
module Solano
|
4
|
+
class SCM
|
5
|
+
attr_accessor :default_origin_url
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@default_origin_url = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def support_data
|
12
|
+
data = Hash.new
|
13
|
+
data['scm_name'] = self.scm_name
|
14
|
+
if !self.repo? then
|
15
|
+
data['is_repo'] = false
|
16
|
+
return data
|
17
|
+
end
|
18
|
+
|
19
|
+
%w(scm_name repo? root repo_name origin_url current_branch default_branch changes?).each do |method|
|
20
|
+
key = method
|
21
|
+
if method =~ /[?]\z/ then
|
22
|
+
key = "is_#{method.sub(/[?]\z/, '')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
value = self.send(method.to_sym) rescue nil
|
26
|
+
|
27
|
+
data[key] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
return data
|
31
|
+
end
|
32
|
+
|
33
|
+
def scm_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def repo?
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
def root
|
41
|
+
return Dir.pwd
|
42
|
+
end
|
43
|
+
|
44
|
+
def repo_name
|
45
|
+
return "unknown"
|
46
|
+
end
|
47
|
+
|
48
|
+
def origin_url
|
49
|
+
return @default_origin_url
|
50
|
+
end
|
51
|
+
|
52
|
+
def ignore_path
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def current_branch
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_branch
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def changes?(options={})
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_snap_id
|
69
|
+
return @snap_id
|
70
|
+
end
|
71
|
+
|
72
|
+
def push_latest(session_data, suite_details, options={})
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_snapshot(session_id, options={})
|
77
|
+
raise Text::Error:SNAPSHOT_NOT_SUPPORTED
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_patch(session_id, options={})
|
81
|
+
raise Text::Error:PATCH_NOT_SUPPORTED
|
82
|
+
end
|
83
|
+
|
84
|
+
def upload_file(auth_url, file_path)
|
85
|
+
if (`which curl >/dev/null 2>&1 ; echo $?`).to_i == 0 then
|
86
|
+
`curl -f --upload-file "#{file_path}" "#{auth_url}"`
|
87
|
+
if(`echo $?`).to_i == 22 then
|
88
|
+
raise "Failed to upload #{file_path} URL (#{out.code})"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
uri = URI(auth_url)
|
92
|
+
body = File.read(file_path)
|
93
|
+
out = Net::HTTP.start(uri.host, :use_ssl => true) do |http|
|
94
|
+
http.send_request("PUT", uri.request_uri, body, {"content-type" => "",})
|
95
|
+
end
|
96
|
+
if out.code.to_i != 200
|
97
|
+
raise "Failed to upload to #{file_path} (#{out.code})"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def current_commit
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def commits
|
108
|
+
[]
|
109
|
+
end
|
110
|
+
|
111
|
+
def number_of_commits(id_from, id_to)
|
112
|
+
return 0
|
113
|
+
end
|
114
|
+
|
115
|
+
def latest_commit
|
116
|
+
return nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|