lita-jls 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +24 -0
- data/Rakefile +6 -0
- data/lib/lita-jls.rb +8 -0
- data/lib/lita-jls/bot_builder.rb +240 -0
- data/lib/lita-jls/github_url_parser.rb +43 -0
- data/lib/lita-jls/repository.rb +57 -0
- data/lib/lita-jls/util.rb +259 -0
- data/lib/lita/handlers/jls.rb +321 -0
- data/lita-jls.gemspec +41 -0
- data/locales/en.yml +4 -0
- data/spec/fixtures/bad_project/Gemfile +2 -0
- data/spec/fixtures/logstash-codec-edn/logstash-codec-edn.gemspec +29 -0
- data/spec/fixtures/logstash.gemspec +3 -0
- data/spec/fixtures/project_with_version_file/lib/testmore/version.rb +3 -0
- data/spec/fixtures/project_with_version_file/project_with_version_file.gemspec +9 -0
- data/spec/fixtures/vcr_cassettes/fetch_version_of_logstash-output-s3.yml +96 -0
- data/spec/fixtures/vcr_cassettes/gem_doesnt_exist.yml +57 -0
- data/spec/fixtures/vcr_cassettes/successful_clacheck.yml +127 -0
- data/spec/fixtures/vcr_cassettes/successful_clacheck_long_commit.yml +133 -0
- data/spec/fixtures/vcr_cassettes/successful_migrate_pr.yml +81 -0
- data/spec/lita-jls/bot_builder_spec.rb +158 -0
- data/spec/lita-jls/github_url_parser_spec.rb +80 -0
- data/spec/lita/handlers/jls_spec.rb +211 -0
- data/spec/spec_helper.rb +31 -0
- metadata +338 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 82fcfffe36fd74ada588e3e69026933c65307904
|
4
|
+
data.tar.gz: e7e5e78740affb2ec4e9de2eec9345eb84f77da3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aab3bdc7a67337e64cac4acd57de18f70ba3713adc38915462dca2e60e85b97c41f95c40e8616763992e098d6d1c82b7fa7ce07ba80931a33f2cd026cf84397d
|
7
|
+
data.tar.gz: 78b56db4fe290dd3ae558cdc3c84a3c0a99d0cc6fd54050c22de2fa83746c334191a697e25250462b730e0031ecfe0fab459edbb421ee01752eacfd3a86782f7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2014 Jordan Sissel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# lita-jls
|
2
|
+
|
3
|
+
TODO: Add a description of the plugin.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add lita-jls to your Lita instance's Gemfile:
|
8
|
+
|
9
|
+
``` ruby
|
10
|
+
gem "lita-jls"
|
11
|
+
```
|
12
|
+
|
13
|
+
|
14
|
+
## Configuration
|
15
|
+
|
16
|
+
TODO: Describe any configuration attributes the plugin exposes.
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
TODO: Describe the plugin's features and how to use them.
|
21
|
+
|
22
|
+
## License
|
23
|
+
|
24
|
+
[MIT](http://opensource.org/licenses/MIT)
|
data/Rakefile
ADDED
data/lib/lita-jls.rb
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'lita-jls/util'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'open3'
|
4
|
+
require 'gems'
|
5
|
+
require 'semverly'
|
6
|
+
|
7
|
+
module LitaJLS
|
8
|
+
module Reporter
|
9
|
+
class HipChat
|
10
|
+
def initialize(build_results)
|
11
|
+
@build_results = build_results
|
12
|
+
end
|
13
|
+
|
14
|
+
def format(message)
|
15
|
+
formatted_message = []
|
16
|
+
|
17
|
+
@build_results.each do |build_result|
|
18
|
+
if build_result.status == :ok
|
19
|
+
formatted_message << " - (success) #{build_result.message}"
|
20
|
+
else
|
21
|
+
if build_result.full_message
|
22
|
+
formatted_message << " - (stare) #{build_result.message} \nstacktrace: #{build_result.error || build_result.full_message}"
|
23
|
+
else
|
24
|
+
formatted_message << " - (stare) #{build_result.message}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
message.reply(formatted_message.join("\n"))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class BuildResult
|
35
|
+
attr_reader :status, :full_message, :error, :message
|
36
|
+
|
37
|
+
def initialize(options = {})
|
38
|
+
@status = options[:status]
|
39
|
+
@full_message = options[:full_message]
|
40
|
+
@error = options[:error]
|
41
|
+
@message = options[:message]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BotBuilder
|
46
|
+
include LitaJLS::Logger
|
47
|
+
|
48
|
+
class ConfigurationError < StandardError; end
|
49
|
+
|
50
|
+
GEM_TO_EXCLUDE = ["logstash"].freeze
|
51
|
+
|
52
|
+
TASKS_ORDER = ['bundle install',
|
53
|
+
'bundle exec rake vendor',
|
54
|
+
'bundle exec rspec',
|
55
|
+
'bundle exec rake publish_gem'].freeze
|
56
|
+
|
57
|
+
GEM_CREDENTIALS_FILE = '~/.gem/credentials'
|
58
|
+
|
59
|
+
attr_reader :current_path, :project_name, :ruby_version
|
60
|
+
|
61
|
+
def initialize(path, options = {})
|
62
|
+
@cache_commands = {}
|
63
|
+
@current_path = File.expand_path(path)
|
64
|
+
@project_name = File.basename(current_path)
|
65
|
+
@ruby_version = options.fetch(:ruby_version, nil)
|
66
|
+
@tasks_order = options[:tasks_order] || TASKS_ORDER
|
67
|
+
end
|
68
|
+
|
69
|
+
def is_gem?
|
70
|
+
File.exists?(find_gemspec)
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_gemspec
|
74
|
+
file = [project_name, "gemspec"].join('.')
|
75
|
+
File.join(current_path, file)
|
76
|
+
end
|
77
|
+
|
78
|
+
def gem_specification
|
79
|
+
# HACK: if you are using the `real` bundler way of creating gem
|
80
|
+
# You have to create a version.rb file containing the version number
|
81
|
+
# and require the file in the gemspec.
|
82
|
+
# Ruby will cache this require and not reload it again in a long running
|
83
|
+
# process like the bot.
|
84
|
+
cmd = "ruby -e \"spec = Gem::Specification.load('#{find_gemspec}'); puts [spec.name, spec.version].join(',')\""
|
85
|
+
results = execute_command_with_ruby(cmd)
|
86
|
+
|
87
|
+
if run_successfully?(results)
|
88
|
+
name, version = results.stdout.strip.split(',')
|
89
|
+
gemspec = Struct.new(:name, :version)
|
90
|
+
return gemspec.new(name, version)
|
91
|
+
else
|
92
|
+
raise results.stderr
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def publishable?
|
97
|
+
if is_gem?
|
98
|
+
return !GEM_TO_EXCLUDE.include?(gem_specification.name)
|
99
|
+
else
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def run_successfully?(task_result)
|
105
|
+
logger.debug("Check if run run_successfully", :exit_code => task_result.status.inspect, :task_result => task_result.inspect)
|
106
|
+
task_result.status.success?
|
107
|
+
end
|
108
|
+
|
109
|
+
def execution_report(task_result)
|
110
|
+
if run_successfully?(task_result)
|
111
|
+
report_ok(task_result.cmd, task_result.stdout)
|
112
|
+
else
|
113
|
+
report_error(task_result.cmd, task_result.stdout, task_result.stderr)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def cache_command(cmd, options = {})
|
118
|
+
@cache_commands[cmd] ||= execute_command(cmd, options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def report_error(message, full_message = nil, error = nil)
|
122
|
+
BuildResult.new(:status => :error, :message => message, :full_message => full_message, :error => error)
|
123
|
+
end
|
124
|
+
|
125
|
+
def report_ok(message, full_message = nil)
|
126
|
+
BuildResult.new(:status => :ok, :message => message, :full_message => full_message)
|
127
|
+
end
|
128
|
+
|
129
|
+
def fetch_last_released_version(name)
|
130
|
+
# Assume you have correctly configured the ~/gem/credentials file
|
131
|
+
credentials_file = File.expand_path(GEM_CREDENTIALS_FILE)
|
132
|
+
|
133
|
+
if File.exist?(credentials_file)
|
134
|
+
response = Gems.versions(name)
|
135
|
+
if response != 'This rubygem could not be found.'
|
136
|
+
return response.first.fetch('number', nil)
|
137
|
+
else
|
138
|
+
return nil
|
139
|
+
end
|
140
|
+
else
|
141
|
+
raise ConfigurationError.new("Missing rubygems credentials in #{credentials_file}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def local_version
|
146
|
+
SemVer.parse(gem_specification.version.to_s)
|
147
|
+
end
|
148
|
+
|
149
|
+
def rubygems_version
|
150
|
+
rubygems_version = fetch_last_released_version(project_name)
|
151
|
+
|
152
|
+
if rubygems_version.nil?
|
153
|
+
return SemVer.new(0, 0, 0)
|
154
|
+
else
|
155
|
+
return SemVer.parse(rubygems_version)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def execute_command_with_ruby(cmd)
|
160
|
+
Dir.chdir(current_path) do
|
161
|
+
Bundler.with_clean_env do
|
162
|
+
environment_variables = {}
|
163
|
+
|
164
|
+
if ruby_version
|
165
|
+
if using_rvm?
|
166
|
+
cmd = "rvm #{ruby_version} do #{cmd}"
|
167
|
+
elsif using_rbenv?
|
168
|
+
raise ConfigurationError.new('RBENV is currently not supported')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
return cache_command(cmd, environment_variables)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def execute_command(cmd, environment_variables = {})
|
178
|
+
logger.debug("Running command", :cmd => cmd, :path => current_path)
|
179
|
+
Open3.popen3(environment_variables, cmd, :chdir => current_path) do |input, stdout, stderr, thr|
|
180
|
+
return OpenStruct.new(:stdout => stdout.read,
|
181
|
+
:status => thr.value,
|
182
|
+
:stderr => stderr.read,
|
183
|
+
:cmd => cmd)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def using_rvm?
|
188
|
+
run_successfully?(cache_command('which rvm'))
|
189
|
+
end
|
190
|
+
|
191
|
+
def using_rbenv?
|
192
|
+
run_successfully?(cache_command('which rbenv'))
|
193
|
+
end
|
194
|
+
|
195
|
+
def run_tasks
|
196
|
+
messages = []
|
197
|
+
|
198
|
+
@tasks_order.each do |task|
|
199
|
+
result = execute_command_with_ruby(task)
|
200
|
+
messages << execution_report(result)
|
201
|
+
break unless run_successfully?(result)
|
202
|
+
end
|
203
|
+
messages
|
204
|
+
end
|
205
|
+
|
206
|
+
def build
|
207
|
+
messages = []
|
208
|
+
|
209
|
+
if is_gem?
|
210
|
+
if publishable?
|
211
|
+
if local_version < rubygems_version
|
212
|
+
logger.debug("Remote version is higher on rubygems, we dont do anything")
|
213
|
+
|
214
|
+
messages << report_error("Higher version on rubygems (#{rubygems_version}) than the local version (#{local_version}), see http://rubygems.org/gems/#{project_name}")
|
215
|
+
elsif local_version == rubygems_version
|
216
|
+
logger.debug("Same version on rubygems", :local_version => local_version, :rubygems_version => rubygems_version)
|
217
|
+
|
218
|
+
messages << report_error("Local version and rubygems version are the same (#{local_version}|#{rubygems_version}), see http://rubygems.org/gems/#{project_name}")
|
219
|
+
else
|
220
|
+
logger.debug("Start the build process")
|
221
|
+
|
222
|
+
messages.concat(run_tasks)
|
223
|
+
|
224
|
+
if local_version == rubygems_version
|
225
|
+
messages << report_ok("version on rubygems match local version, published #{local_version} see http://rubygems.org/gems/#{project_name}")
|
226
|
+
else
|
227
|
+
messages << report_error("versions on rubygems doesn't match see http://rubygems.org/gems/#{project_name}")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
else
|
231
|
+
messages << report_error("#{project_name} is blacklisted, you cannot deploy it with this tool.")
|
232
|
+
end
|
233
|
+
else
|
234
|
+
messages << report_error("#{project_name} doesn't have a gemspec")
|
235
|
+
end
|
236
|
+
|
237
|
+
messages
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module LitaJLS
|
2
|
+
class GithubUrlParser
|
3
|
+
attr_reader :user, :project, :pr, :url
|
4
|
+
|
5
|
+
URL_BASE = "https://github.com"
|
6
|
+
|
7
|
+
def initialize(git_url, options)
|
8
|
+
@url = git_url
|
9
|
+
full_path = URI.parse(@url).path
|
10
|
+
_, @user, @project, _, @pr = full_path.split('/')
|
11
|
+
|
12
|
+
@options = { :link => :repository }.merge(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid_url?
|
16
|
+
url =~ /^#{URL_BASE.gsub('/', '\/')}/
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_repository!
|
20
|
+
if user.nil? || project.nil? || !valid_url?
|
21
|
+
raise "Invalid URL. Expected something like: #{URL_BASE}/elasticsearch/snacktime/"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_pull_request!
|
26
|
+
if user.nil? || project.nil? || pr.nil? || !valid_url?
|
27
|
+
raise "Invalid URL. Expected something like: #{URL_BASE}/elasticsearch/snacktime/pull/12345"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate!
|
32
|
+
send("validate_#{@options[:link]}!")
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.parse(git_url, options = {})
|
36
|
+
new(git_url, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def git_url
|
40
|
+
"#{URL_BASE}/#{user}/#{project}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'lita-jls/util'
|
2
|
+
|
3
|
+
module LitaJLS
|
4
|
+
class Repository
|
5
|
+
include LitaJLS::Util
|
6
|
+
include LitaJLS::Logger
|
7
|
+
|
8
|
+
REMOTE = 'origin'
|
9
|
+
|
10
|
+
def initialize(parsed_url)
|
11
|
+
@parsed_url = parsed_url
|
12
|
+
end
|
13
|
+
|
14
|
+
def clone
|
15
|
+
clone_at(@parsed_url.git_url, git_path)
|
16
|
+
git(git_path, "am", "--abort") if unfinished_rebase?
|
17
|
+
end
|
18
|
+
|
19
|
+
def unfinished_rebase?
|
20
|
+
File.directory?(".git/rebase-apply")
|
21
|
+
end
|
22
|
+
|
23
|
+
def git_path
|
24
|
+
@git_path ||= gitdir(@parsed_url.project)
|
25
|
+
end
|
26
|
+
|
27
|
+
def switch_branch(branch, create_new=false)
|
28
|
+
if create_new
|
29
|
+
logger.info("Creating and switching branches", :branch => branch, :repo => git_path)
|
30
|
+
git(git_path, "checkout", "-b", branch)
|
31
|
+
git(git_path, "reset", "--hard", "#{REMOTE}/master")
|
32
|
+
else
|
33
|
+
logger.info("Switching branches", :branch => branch, :repo => git_path)
|
34
|
+
git(git_path, "checkout", branch)
|
35
|
+
git(git_path, "reset", "--hard", "#{REMOTE}/#{branch}")
|
36
|
+
git(git_path, "pull", "--ff-only")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def git_patch(patch_file)
|
41
|
+
raise "Previous patch apply had failed. Please resolve it before continuing" if File.directory?(".git/rebase-apply")
|
42
|
+
git(git_path, "am", "--3way", patch_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
def git_push_branch(local_branch)
|
46
|
+
git(git_path, "push", "origin", local_branch)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_local_branch(branch, ignore_error=false)
|
50
|
+
begin
|
51
|
+
git(git_path, "branch", "-D", branch)
|
52
|
+
rescue => e
|
53
|
+
raise e unless ignore_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require "cabin"
|
2
|
+
require "fileutils"
|
3
|
+
require "uri"
|
4
|
+
require "faraday" # for cla check
|
5
|
+
require "json" # for cla check
|
6
|
+
#
|
7
|
+
# TODO(sissel): This code needs some suuuper serious refactoring and testing improvements.
|
8
|
+
|
9
|
+
module LitaJLS
|
10
|
+
module Logger
|
11
|
+
def logger
|
12
|
+
return @logger if @logger
|
13
|
+
@logger = Cabin::Channel.get
|
14
|
+
@logger.level = :debug if ENV["DEBUG"]
|
15
|
+
@logger
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Util
|
20
|
+
include Logger
|
21
|
+
|
22
|
+
CLANotSigned = Class.new(StandardError)
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# This method requires @cla_uri being set before it'll work.
|
27
|
+
def cla?(repository, pr)
|
28
|
+
raise "No @cla_uri set. Cannot check CLA signature." unless @cla_uri
|
29
|
+
uri = URI.parse(@cla_uri)
|
30
|
+
conn = Faraday.new(:url => "#{uri.scheme}://#{uri.host}")
|
31
|
+
conn.basic_auth(uri.user, uri.password)
|
32
|
+
response = conn.get(uri.path, :repository => repository, :number => pr)
|
33
|
+
check = JSON.parse(response.body)
|
34
|
+
# TODO(sissel): json exception? .get exception?
|
35
|
+
|
36
|
+
if check["status"] == "error"
|
37
|
+
raise CLANotSigned, check["message"]
|
38
|
+
end
|
39
|
+
|
40
|
+
true
|
41
|
+
end # cla?
|
42
|
+
|
43
|
+
def logstash_team?(user)
|
44
|
+
["electrical",
|
45
|
+
"jordansissel",
|
46
|
+
"ph",
|
47
|
+
"colinsurprenant",
|
48
|
+
"jsvd",
|
49
|
+
"untergeek",
|
50
|
+
"talevy",
|
51
|
+
"kurtado",
|
52
|
+
"suyograo",
|
53
|
+
"purbon"].include?(user.downcase)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clone a git url into a local path.
|
57
|
+
#
|
58
|
+
# This caches a remote git repo and performs a clone against that in
|
59
|
+
# order to make subsequent clones faster. It will try to provide the
|
60
|
+
# latest (via 'git fetch') after cloning is complete.
|
61
|
+
def clone_at(url, gitpath)
|
62
|
+
# TODO(sissel): Refactor this into two 'clone' calls.
|
63
|
+
logger.debug("clone_at", :url => url, :gitpath => gitpath)
|
64
|
+
|
65
|
+
# Cache a remote git url so that we can clone it more quickly in the
|
66
|
+
# future.
|
67
|
+
cache = File.join(gitdir(File.join("_")), File.basename(gitpath))
|
68
|
+
FileUtils.mkdir_p(cache) unless File.directory?(cache)
|
69
|
+
|
70
|
+
logger.info("Cloning to cache", :url => url, :cache => cache)
|
71
|
+
begin
|
72
|
+
git(".", "clone", url, cache)
|
73
|
+
rescue => e
|
74
|
+
logger.debug("clone_at failed, trying to open repo instead", :cache => cache, :error => e)
|
75
|
+
git(cache, "log", "-n0") # Verify some kind of git works here
|
76
|
+
end
|
77
|
+
remote = "origin"
|
78
|
+
|
79
|
+
# Update from remote if already cloned
|
80
|
+
logger.debug("Fetching a remote", :cache => cache, :remote => remote)
|
81
|
+
git(cache, "fetch", remote)
|
82
|
+
|
83
|
+
# Clone from cache.
|
84
|
+
# This allows us to have multiple local working/clones and just keep
|
85
|
+
# cloning from the local cache. The alternative is to clone from the
|
86
|
+
# remote every time we do a new clone_at, and that would be slow through
|
87
|
+
# github or gitlab.
|
88
|
+
logger.info("Cloning from cache", :cache => cache, :gitpath => gitpath)
|
89
|
+
begin
|
90
|
+
git(".", "clone", cache, gitpath)
|
91
|
+
rescue => e
|
92
|
+
logger.info(e)
|
93
|
+
logger.debug("clone_at from cache failed, trying to open repo instead", :repo => gitpath, :cache => cache)
|
94
|
+
end
|
95
|
+
git(gitpath, "remote", "set-url", remote, url)
|
96
|
+
|
97
|
+
uri = URI.parse(url)
|
98
|
+
push_url = "git@github.com:#{uri.path}.git"
|
99
|
+
git(gitpath, "remote", "set-url", "--push", remote, push_url)
|
100
|
+
git(gitpath, "fetch")
|
101
|
+
|
102
|
+
# TODO(sissel): pull --ff-only?
|
103
|
+
gitpath
|
104
|
+
end # def clone_at
|
105
|
+
|
106
|
+
def gitdir(project)
|
107
|
+
if !@gitdir
|
108
|
+
@gitdir = workdir("gitbase")
|
109
|
+
FileUtils.mkdir_p(@gitdir) unless File.directory?(@gitdir)
|
110
|
+
logger.debug("Git dir", :path => @gitdir)
|
111
|
+
end
|
112
|
+
path = File.join(@gitdir, project)
|
113
|
+
Dir.mkdir(path) unless File.directory?(path)
|
114
|
+
path
|
115
|
+
end # def gitdir
|
116
|
+
|
117
|
+
def workdir(path=nil)
|
118
|
+
return File.join(@workdir, path) if @workdir
|
119
|
+
@workdir = File.join(Dir.tmpdir, "lita-jls")
|
120
|
+
Dir.mkdir(@workdir) unless File.directory?(@workdir)
|
121
|
+
if path.nil?
|
122
|
+
@workdir
|
123
|
+
else
|
124
|
+
File.join(@workdir, path)
|
125
|
+
end
|
126
|
+
end # def workdir
|
127
|
+
|
128
|
+
def apply_patch(repo, patch_body, &block)
|
129
|
+
require "mbox"
|
130
|
+
require "time"
|
131
|
+
# The github '.patch' format is an mbox containing one mail+patch per
|
132
|
+
# commit.
|
133
|
+
mbox = Mbox.new(patch_body)
|
134
|
+
mbox.each_with_index do |mail, i|
|
135
|
+
commit = apply_commit(repo, mail, &block)
|
136
|
+
logger.info("Created commit", :commit => commit, :patch => i)
|
137
|
+
end
|
138
|
+
end # def apply_patch
|
139
|
+
|
140
|
+
def apply_commit(repo, mail, &block)
|
141
|
+
from_re = /([^<]+) (<[^>]+>)/
|
142
|
+
match = from_re.match(mail.headers["from"])
|
143
|
+
name = match[1].gsub(/(^")|("$)/, "")
|
144
|
+
email = match[2].gsub(/^<|>$/, "")
|
145
|
+
if name.nil? || email.nil?
|
146
|
+
raise "Unable to parse name and email from '#{mail.headers["from"]}'. Cannot continue"
|
147
|
+
end
|
148
|
+
time = Time.parse(mail.headers["date"])
|
149
|
+
#File.write("/tmp/x", patch)
|
150
|
+
|
151
|
+
# Take the email subject but strip [PATCH] or [PATCH N/M] out.
|
152
|
+
subject = mail.headers["subject"].gsub(/^\[PATCH[^\]]*\] /, "")
|
153
|
+
# The email body (minus the patch itself) is the rest of the commit message
|
154
|
+
description = /^(?<description>.*\n)?---\n.*$/m.match(mail.content.first.content)["description"] || ""
|
155
|
+
|
156
|
+
if subject.empty? && description.empty?
|
157
|
+
raise "Empty commit message (no subject or description). Refusing to continue."
|
158
|
+
end
|
159
|
+
|
160
|
+
#patch = mail.content.first.content.gsub(/^(?:.*\n)?---\n.*?\n\n/m, "")
|
161
|
+
#patch += "\n" if patch[-1,1] != "\n"
|
162
|
+
# Patch must have a trailing newline.
|
163
|
+
patch = [mail.headers, mail.content, ""].join("\n")
|
164
|
+
|
165
|
+
# Apply the code change to the git index
|
166
|
+
Dir.chdir(repo) do
|
167
|
+
cmd = ["git", "am", "--3way"]
|
168
|
+
File.write("/tmp/patch", patch)
|
169
|
+
IO.popen(cmd, "w+") do |io|
|
170
|
+
io.write(patch)
|
171
|
+
io.close_write
|
172
|
+
logger.pipe(io => :debug)
|
173
|
+
end
|
174
|
+
status = $?
|
175
|
+
if !status.success?
|
176
|
+
logger.warn("Git am failed", :code => status.exitstatus, :command => cmd, :pwd => Dir.pwd)
|
177
|
+
git(repo, "am", "--abort")
|
178
|
+
raise "Git am failed: #{cmd.join(" ")}"
|
179
|
+
end
|
180
|
+
logger.info("Git am successful!", :code => status.exitstatus, :command => cmd, :pwd => Dir.pwd)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Combine subject + description for the full commit message
|
184
|
+
message = "#{subject}\n\n#{description}"
|
185
|
+
|
186
|
+
commit_settings = {
|
187
|
+
# TODO(sissel): override the committer with whoever
|
188
|
+
#:author => { :email => email, :name => name, :time => time },
|
189
|
+
#:committer => { :email => "jls@semicomplete.com", :name => "Jordan Sissel", :time => Time.now },
|
190
|
+
:message => message
|
191
|
+
}
|
192
|
+
|
193
|
+
# Allow any modifications to the commit object itself.
|
194
|
+
block.call(commit_settings)
|
195
|
+
|
196
|
+
Dir.chdir(repo) do
|
197
|
+
cmd = ["git", "commit", "--amend", "-F-"]
|
198
|
+
IO.popen(cmd, "w+") do |io|
|
199
|
+
io.write(commit_settings[:message])
|
200
|
+
io.close_write
|
201
|
+
logger.pipe(io => :debug)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end # def apply_commit
|
206
|
+
|
207
|
+
def system!(*args)
|
208
|
+
logger.debug("Running command", :args => args)
|
209
|
+
# TODO(sissel): use Open4
|
210
|
+
IO.popen(args, "r") do |io|
|
211
|
+
logger.pipe(io => :debug)
|
212
|
+
end
|
213
|
+
status = $?
|
214
|
+
return if status.success?
|
215
|
+
raise "Command failed; #{args.inspect}"
|
216
|
+
end
|
217
|
+
|
218
|
+
def github_client
|
219
|
+
require "octokit"
|
220
|
+
# This requires you have ~/.netrc setup correctly
|
221
|
+
# I don't know if it works with 2FA
|
222
|
+
@client ||= Octokit::Client.new(:netrc => true).tap do |client|
|
223
|
+
client.login
|
224
|
+
client.auto_paginate = true
|
225
|
+
end
|
226
|
+
end # def client
|
227
|
+
|
228
|
+
def github_issue_label(project, issue, labels)
|
229
|
+
logger.debug('Adding label to a specific issue', :project => project, :issue => issue, :labels => labels)
|
230
|
+
github_client.add_labels_to_an_issue(project, issue, labels) unless labels.empty?
|
231
|
+
rescue => e
|
232
|
+
raise e.class, "Failed adding label '#{labels}' to issue #{issue} on #{project}: #{e}"
|
233
|
+
end # def github_issue_label
|
234
|
+
|
235
|
+
def github_issue_comment(project, issue, comment)
|
236
|
+
logger.debug('Adding a comment to an given issue', :project => project, :issue => issue, :comment => comment)
|
237
|
+
github_client.add_comment(project, issue, comment) unless comment.empty?
|
238
|
+
rescue => e
|
239
|
+
raise e.class, "Failed adding a comment #{comment} to issue #{issue} on #{project}: #{e}"
|
240
|
+
|
241
|
+
end # def github_issue_comment
|
242
|
+
|
243
|
+
def github_create_pr(project, branch, title, body)
|
244
|
+
logger.debug('Creating a pull request', :project => project, :branch => branch)
|
245
|
+
github_client.create_pull_request(project, "master", branch, title, body)
|
246
|
+
end
|
247
|
+
|
248
|
+
def github_get_pr(project, pr_num)
|
249
|
+
github_client.pull_request(project, pr_num)
|
250
|
+
end
|
251
|
+
|
252
|
+
def git(gitdir, *args)
|
253
|
+
Dir.chdir(gitdir) do
|
254
|
+
system!("git", *args)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end # module Util
|
258
|
+
|
259
|
+
end # module LitaJLS
|