bpescatore-stash 0.3.2
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/.document +5 -0
- data/Gemfile +22 -0
- data/LICENSE.txt +20 -0
- data/README.md +114 -0
- data/Rakefile +69 -0
- data/VERSION +1 -0
- data/bin/stash +170 -0
- data/lib/atlassian/stash/git.rb +57 -0
- data/lib/atlassian/stash/pull_request.rb +153 -0
- data/lib/atlassian/stash/repo_info.rb +62 -0
- data/lib/atlassian/stash/version.rb +9 -0
- data/lib/atlassian/util/text_util.rb +31 -0
- data/lib/stash_cli.rb +3 -0
- data/test/helper.rb +29 -0
- data/test/test_stash-create-pull-request.rb +84 -0
- data/test/test_stash-git.rb +96 -0
- data/test/test_stash-repo-info.rb +91 -0
- data/test/test_text-util.rb +103 -0
- metadata +241 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'git'
|
5
|
+
require 'launchy'
|
6
|
+
|
7
|
+
include Atlassian::Util::TextUtil
|
8
|
+
|
9
|
+
module Atlassian
|
10
|
+
module Stash
|
11
|
+
class CreatePullRequestResource
|
12
|
+
attr_accessor :resource
|
13
|
+
|
14
|
+
def initialize(sourceRepoInfo, targetRepoInfo, title, description, reviewers, source, target)
|
15
|
+
src_repository = {
|
16
|
+
'slug' => sourceRepoInfo.slug,
|
17
|
+
'project' => {
|
18
|
+
'key' => sourceRepoInfo.projectKey
|
19
|
+
}
|
20
|
+
}
|
21
|
+
target_repository = {
|
22
|
+
'slug' => targetRepoInfo.slug,
|
23
|
+
'project' => {
|
24
|
+
'key' => targetRepoInfo.projectKey
|
25
|
+
}
|
26
|
+
}
|
27
|
+
fromRef = {
|
28
|
+
'id' => source,
|
29
|
+
'repository' => src_repository
|
30
|
+
}
|
31
|
+
toRef = {
|
32
|
+
'id' => target,
|
33
|
+
'repository' => target_repository
|
34
|
+
}
|
35
|
+
@resource = {
|
36
|
+
'title' => title,
|
37
|
+
'fromRef' => fromRef,
|
38
|
+
'toRef' => toRef
|
39
|
+
}
|
40
|
+
|
41
|
+
@resource["description"] = description unless description.empty?
|
42
|
+
|
43
|
+
@resource["reviewers"] = reviewers.collect { |r|
|
44
|
+
{
|
45
|
+
'user' => {
|
46
|
+
'name' => r
|
47
|
+
}
|
48
|
+
}
|
49
|
+
} unless reviewers.empty?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class CreatePullRequest
|
54
|
+
|
55
|
+
def initialize(config)
|
56
|
+
@config = config
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_pull_request(source, target, reviewers, options)
|
60
|
+
Process.exit if not target or not source
|
61
|
+
|
62
|
+
@source = source
|
63
|
+
@target = target
|
64
|
+
|
65
|
+
srcRepoInfo = RepoInfo.create(@config, options.src_remote)
|
66
|
+
targetRepoInfo = RepoInfo.create(@config, options.target_remote)
|
67
|
+
|
68
|
+
title, description = title_and_description(options)
|
69
|
+
|
70
|
+
resource = CreatePullRequestResource.new(srcRepoInfo, targetRepoInfo, title, description, reviewers, @source, @target).resource
|
71
|
+
|
72
|
+
username = @config["username"]
|
73
|
+
password = @config["password"]
|
74
|
+
proxy_addr, proxy_port = parse_proxy(@config["proxy"])
|
75
|
+
|
76
|
+
username = ask("Username: ") unless @config["username"]
|
77
|
+
password = ask("Password: ") { |q| q.echo = '*' } unless @config["password"]
|
78
|
+
|
79
|
+
uri = URI.parse(@config["stash_url"])
|
80
|
+
prPath = targetRepoInfo.repoPath + '/pull-requests'
|
81
|
+
|
82
|
+
req = Net::HTTP::Post.new(uri.query.nil? ? "#{prPath}" : "#{prPath}?#{uri.query}", {'Content-Type' => 'application/json', 'Accept' => 'application/json'})
|
83
|
+
req.basic_auth username, password
|
84
|
+
req.body = resource.to_json
|
85
|
+
http = Net::HTTP.new(uri.host, uri.port, proxy_addr, proxy_port)
|
86
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @config["ssl_no_verify"]
|
87
|
+
http.use_ssl = uri.scheme.eql?("https")
|
88
|
+
|
89
|
+
response = http.start {|conn| conn.request(req) }
|
90
|
+
|
91
|
+
if not response.is_a? Net::HTTPCreated
|
92
|
+
responseBody = JSON.parse(response.body)
|
93
|
+
if responseBody['errors']
|
94
|
+
responseBody['errors'].collect { |error|
|
95
|
+
puts error['message']
|
96
|
+
if error['reviewerErrors']
|
97
|
+
error['reviewerErrors'].collect { |revError|
|
98
|
+
puts revError['message']
|
99
|
+
}
|
100
|
+
end
|
101
|
+
}
|
102
|
+
elsif responseBody['message']
|
103
|
+
puts responseBody['message']
|
104
|
+
else
|
105
|
+
puts 'An unknown error occurred.'
|
106
|
+
puts response.code
|
107
|
+
puts response.body
|
108
|
+
end
|
109
|
+
else
|
110
|
+
responseBody = JSON.parse(response.body)
|
111
|
+
prUri = uri.clone
|
112
|
+
prUri.path = prPath + '/' + responseBody['id'].to_s
|
113
|
+
prUri.query = uri.query
|
114
|
+
puts prUri.to_s
|
115
|
+
|
116
|
+
if @config["open"] || options.open
|
117
|
+
Launchy.open prUri.to_s
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def title_from_branch
|
125
|
+
convert_branch_name_to_sentence(@source) || "Merge '#{@source}' into '#{@target}'"
|
126
|
+
end
|
127
|
+
|
128
|
+
def git_commit_messages
|
129
|
+
@commit_messages ||= `git log --reverse --format=%s #{@target}..#{@source}`
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_proxy(conf)
|
133
|
+
return nil, nil unless conf
|
134
|
+
|
135
|
+
addr, port = conf.split(":")
|
136
|
+
if port =~ /\d+/
|
137
|
+
port = port.to_i
|
138
|
+
else
|
139
|
+
port = nil
|
140
|
+
end
|
141
|
+
[addr, port]
|
142
|
+
end
|
143
|
+
|
144
|
+
def title_and_description(options)
|
145
|
+
descr = (options.description or git_commit_messages)
|
146
|
+
title = (options.title or title_from_branch)
|
147
|
+
|
148
|
+
[title, descr]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module Atlassian
|
4
|
+
module Stash
|
5
|
+
class RepoInfo
|
6
|
+
def initialize(config, projectKey, slug)
|
7
|
+
@config = config
|
8
|
+
@projectKey = projectKey
|
9
|
+
@slug = slug
|
10
|
+
end
|
11
|
+
|
12
|
+
def projectKey
|
13
|
+
@projectKey
|
14
|
+
end
|
15
|
+
|
16
|
+
def slug
|
17
|
+
@slug
|
18
|
+
end
|
19
|
+
|
20
|
+
def repoPath
|
21
|
+
uri = URI.parse(@config["stash_url"])
|
22
|
+
repoPath = uri.path + '/projects/' + @projectKey + '/repos/' + @slug
|
23
|
+
repoPath
|
24
|
+
end
|
25
|
+
|
26
|
+
def repoUrl(suffix, branch)
|
27
|
+
uri = URI.parse(@config["stash_url"])
|
28
|
+
path = repoPath + (suffix.nil? ? '' : '/' + suffix)
|
29
|
+
uri.path = path
|
30
|
+
|
31
|
+
if (!branch.nil? and !branch.empty?)
|
32
|
+
q = uri.query || ''
|
33
|
+
q = q + (q.empty? ? '' : '&') + 'at=' + branch unless branch.nil?
|
34
|
+
uri.query = q
|
35
|
+
end
|
36
|
+
|
37
|
+
uri.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.create (config, remote=nil)
|
41
|
+
config = Hash.new if config.nil?
|
42
|
+
remote = config["remote"] if (remote.nil? || remote.empty?)
|
43
|
+
remoteUrl = Atlassian::Stash::Git.get_remote_url(remote)
|
44
|
+
|
45
|
+
if remoteUrl.nil?
|
46
|
+
remotes = Atlassian::Stash::Git.get_remotes
|
47
|
+
if remotes.empty?
|
48
|
+
raise "No git remotes found, could not determine Stash project URL"
|
49
|
+
else
|
50
|
+
remote = Atlassian::Stash::Git::DEFAULT_REMOTE if (remote.nil? || remote.empty?)
|
51
|
+
raise "Could not find requested git remote '#{remote}'. Remotes found: \r\n" + remotes
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if m = remoteUrl.match(/\/([a-zA-Z~][a-zA-Z0-9_\-]*)\/([[:alnum:]][\w\-\.]*).git$/)
|
56
|
+
return RepoInfo.new(config, m[1], m[2])
|
57
|
+
end
|
58
|
+
raise "Repository does not seem to be hosted in Stash; Remote url: " + remoteUrl
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Atlassian
|
3
|
+
module Util
|
4
|
+
module TextUtil
|
5
|
+
def convert_branch_name_to_sentence(branch_name)
|
6
|
+
return '' if branch_name.nil?
|
7
|
+
|
8
|
+
branch_name = branch_name.to_s
|
9
|
+
return '' if branch_name.empty?
|
10
|
+
|
11
|
+
issue_key_regex = /([A-Z]{1,10}-\d+)/
|
12
|
+
branch_components = branch_name.split(issue_key_regex);
|
13
|
+
|
14
|
+
parts = branch_components.each_with_index.map { |value, index|
|
15
|
+
(index % 2 === 0) ? value.gsub(/[\-_]/, ' ') : value
|
16
|
+
}
|
17
|
+
|
18
|
+
to_sentence_case(parts.join(''))
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_sentence_case(str)
|
22
|
+
return '' if str.nil?
|
23
|
+
|
24
|
+
str = str.to_s
|
25
|
+
return '' if str.empty?
|
26
|
+
|
27
|
+
str.slice(0, 1).upcase + str.slice(1, str.length)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/stash_cli.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
if ENV["COVERAGE"]
|
5
|
+
require "simplecov"
|
6
|
+
SimpleCov.start :test_frameworks do
|
7
|
+
add_filter "/vendor/"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
Bundler.setup(:default, :development)
|
13
|
+
rescue Bundler::BundlerError => e
|
14
|
+
$stderr.puts e.message
|
15
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
16
|
+
exit e.status_code
|
17
|
+
end
|
18
|
+
require 'minitest/autorun'
|
19
|
+
require 'shoulda'
|
20
|
+
require "mocha/mini_test"
|
21
|
+
|
22
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
23
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
24
|
+
# require 'atlassian-stash'
|
25
|
+
require File.dirname(__FILE__) + "/../lib/stash_cli"
|
26
|
+
|
27
|
+
|
28
|
+
class Minitest::Test
|
29
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
include Atlassian::Stash
|
4
|
+
include Atlassian::Stash::Git
|
5
|
+
|
6
|
+
class TestStashCreatePullRequest < Minitest::Test
|
7
|
+
|
8
|
+
context "#parse_proxy" do
|
9
|
+
setup do
|
10
|
+
@cpr = CreatePullRequest.new nil
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when proxy_conf is nil' do
|
14
|
+
should 'returns [nil, nil]' do
|
15
|
+
assert_equal [nil, nil], @cpr.send(:parse_proxy, nil)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when proxy_conf is blank' do
|
20
|
+
should 'returns [nil, nil]' do
|
21
|
+
assert_equal [nil, nil], @cpr.send(:parse_proxy, "")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when proxy_conf is "proxy.example.com"' do
|
26
|
+
should 'returns ["proxy.example.com", nil]' do
|
27
|
+
assert_equal ["proxy.example.com", nil], @cpr.send(:parse_proxy, "proxy.example.com")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when proxy_conf is "proxy.example.com:8080"' do
|
32
|
+
should 'returns ["proxy.example.com", 8080]' do
|
33
|
+
assert_equal ["proxy.example.com", 8080], @cpr.send(:parse_proxy, "proxy.example.com:8080")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when proxy_conf is "proxy.example.com:foo"' do
|
38
|
+
should 'returns ["proxy.example.com", nil]' do
|
39
|
+
assert_equal ["proxy.example.com", nil], @cpr.send(:parse_proxy, "proxy.example.com:foo")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context '#title_and_description' do
|
45
|
+
setup do
|
46
|
+
@cpr = CreatePullRequest.new nil
|
47
|
+
def @cpr.title_from_branch; 'title_from_branch'; end
|
48
|
+
def @cpr.git_commit_messages; 'git_commit_messages'; end
|
49
|
+
@options = Struct.new(:title, :description)
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with no options' do
|
53
|
+
should 'sets default title and description' do
|
54
|
+
title, descr = @cpr.send(:title_and_description, @options.new(nil, nil))
|
55
|
+
assert_equal title, 'title_from_branch'
|
56
|
+
assert_equal descr, 'git_commit_messages'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with title option' do
|
61
|
+
should 'sets custom title and default description' do
|
62
|
+
title, descr = @cpr.send(:title_and_description, @options.new('custom title', nil))
|
63
|
+
assert_equal title, 'custom title'
|
64
|
+
assert_equal descr, 'git_commit_messages'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with description option' do
|
69
|
+
should 'sets default title and custom description' do
|
70
|
+
title, descr = @cpr.send(:title_and_description, @options.new(nil, 'custom description'))
|
71
|
+
assert_equal title, 'title_from_branch'
|
72
|
+
assert_equal descr, 'custom description'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with both title and description options' do
|
77
|
+
should 'sets custom title and description' do
|
78
|
+
title, descr = @cpr.send(:title_and_description, @options.new('custom title', 'custom description'))
|
79
|
+
assert_equal title, 'custom title'
|
80
|
+
assert_equal descr, 'custom description'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
include Atlassian::Stash
|
4
|
+
include Atlassian::Stash::Git
|
5
|
+
|
6
|
+
class TestGit < Minitest::Test
|
7
|
+
|
8
|
+
should "extract remote with ssh remote" do
|
9
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns(
|
10
|
+
"origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (fetch)
|
11
|
+
origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (push)"
|
12
|
+
)
|
13
|
+
assert_equal 'ssh://git@stash.atlassian.com:7999/STASH/stash.git', Atlassian::Stash::Git.get_remote_url
|
14
|
+
end
|
15
|
+
|
16
|
+
should "extract push remote with different fetch and push urls" do
|
17
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns(
|
18
|
+
"origin ssh://git@github.com/~sebr/stash.git (fetch)
|
19
|
+
origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (push)"
|
20
|
+
)
|
21
|
+
assert_equal 'ssh://git@stash.atlassian.com:7999/STASH/stash.git', Atlassian::Stash::Git.get_remote_url
|
22
|
+
end
|
23
|
+
|
24
|
+
should "extract remote with http remote" do
|
25
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns(
|
26
|
+
"origin http://adam@sonoma:7990/stash/scm/QA/stash.git (fetch)
|
27
|
+
origin http://adam@sonoma:7990/stash/scm/QA/stash.git (push)"
|
28
|
+
)
|
29
|
+
assert_equal 'http://adam@sonoma:7990/stash/scm/QA/stash.git', Atlassian::Stash::Git.get_remote_url
|
30
|
+
end
|
31
|
+
|
32
|
+
should "extract remote with multiple remote urls" do
|
33
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns(
|
34
|
+
"bitbucket git@bitbucket.org:atlassian/stash-command-line-tools.git (fetch)
|
35
|
+
bitbucket git@bitbucket.org:atlassian/stash-command-line-tools.git (push)
|
36
|
+
kostya http://admin@kostya:7990/scm/CA/cylon.git (fetch)
|
37
|
+
kostya http://admin@kostya:7990/scm/CA/cylon.git (push)
|
38
|
+
local http://delirium:7990/git/STASH/stash.git (fetch)
|
39
|
+
local http://delirium:7990/git/STASH/stash.git (push)
|
40
|
+
origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (fetch)
|
41
|
+
origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (push)
|
42
|
+
seb http://adam@sonoma:7990/stash/scm/QA/stash.git (fetch)
|
43
|
+
seb http://adam@sonoma:7990/stash/scm/QA/stash.git (push)
|
44
|
+
upstream http://github-enterprise-11-10/stash/stash.git (fetch)
|
45
|
+
upstream http://github-enterprise-11-10/stash/stash.git (push)")
|
46
|
+
assert_equal 'ssh://git@stash.atlassian.com:7999/STASH/stash.git', Atlassian::Stash::Git.get_remote_url
|
47
|
+
end
|
48
|
+
|
49
|
+
should "extract custom remote with multiple remote urls" do
|
50
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns(
|
51
|
+
"bitbucket git@bitbucket.org:atlassian/stash-command-line-tools.git (fetch)
|
52
|
+
bitbucket git@bitbucket.org:atlassian/stash-command-line-tools.git (push)
|
53
|
+
kostya http://admin@kostya:7990/scm/CA/cylon.git (fetch)
|
54
|
+
kostya http://admin@kostya:7990/scm/CA/cylon.git (push)
|
55
|
+
local http://delirium:7990/git/STASH/stash.git (fetch)
|
56
|
+
local http://delirium:7990/git/STASH/stash.git (push)
|
57
|
+
origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (fetch)
|
58
|
+
origin ssh://git@stash.atlassian.com:7999/STASH/stash.git (push)
|
59
|
+
upstream ssh://git@stash.atlassian.com:7999/ATLASSIAN/stash.git (fetch)
|
60
|
+
upstream ssh://git@stash.atlassian.com:7999/ATLASSIAN/stash.git (push)
|
61
|
+
seb http://adam@sonoma:7990/stash/scm/QA/stash.git (fetch)
|
62
|
+
seb http://adam@sonoma:7990/stash/scm/QA/stash.git (push)
|
63
|
+
upstream http://github-enterprise-11-10/stash/stash.git (fetch)
|
64
|
+
upstream http://github-enterprise-11-10/stash/stash.git (push)")
|
65
|
+
assert_equal 'ssh://git@stash.atlassian.com:7999/ATLASSIAN/stash.git', Atlassian::Stash::Git.get_remote_url('upstream')
|
66
|
+
end
|
67
|
+
|
68
|
+
should "repo with no remotes returns nil" do
|
69
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns("")
|
70
|
+
assert_equal nil, Atlassian::Stash::Git.get_remote_url
|
71
|
+
end
|
72
|
+
|
73
|
+
should "repo with unfound remote returns nil" do
|
74
|
+
Atlassian::Stash::Git.stubs(:get_remotes).returns(
|
75
|
+
"bitbucket git@bitbucket.org:atlassian/stash-command-line-tools.git (fetch)
|
76
|
+
bitbucket git@bitbucket.org:atlassian/stash-command-line-tools.git (push)"
|
77
|
+
)
|
78
|
+
assert_equal nil, Atlassian::Stash::Git.get_remote_url('origin')
|
79
|
+
end
|
80
|
+
|
81
|
+
should "branches are matched" do
|
82
|
+
Atlassian::Stash::Git.stubs(:get_branches).returns(
|
83
|
+
" feature
|
84
|
+
temp
|
85
|
+
* master
|
86
|
+
remotes/origin/master
|
87
|
+
remotes/origin/release/v1.0
|
88
|
+
remotes/origin/feature/awesome
|
89
|
+
remotes/upstream/master
|
90
|
+
remotes/upstream/release/v1.0
|
91
|
+
remotes/upstream/feature/Issue7")
|
92
|
+
assert_equal true, Atlassian::Stash::Git.is_branch?('master')
|
93
|
+
assert_equal true, Atlassian::Stash::Git.is_branch?('remotes/upstream/master')
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|