geet 0.1.9 → 0.1.10
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 +4 -4
- data/README.md +7 -2
- data/bin/geet +1 -2
- data/geet.gemspec +1 -1
- data/lib/geet/commandline/configuration.rb +0 -4
- data/lib/geet/git/repository.rb +36 -16
- data/lib/geet/github/api_interface.rb +1 -1
- data/lib/geet/gitlab/api_interface.rb +135 -0
- data/lib/geet/gitlab/issue.rb +29 -0
- data/lib/geet/version.rb +1 -1
- data/spec/integration/create_gist_spec.rb +1 -1
- data/spec/integration/create_issue_spec.rb +2 -2
- data/spec/integration/create_pr_spec.rb +2 -2
- data/spec/integration/list_issues_spec.rb +2 -2
- data/spec/integration/list_labels_spec.rb +1 -1
- data/spec/integration/list_milestones_spec.rb +1 -1
- data/spec/integration/list_prs_spec.rb +2 -2
- data/spec/integration/merge_pr_spec.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c59d2f426846e6efe8e86311fdaa40c2d492c551
|
4
|
+
data.tar.gz: ad031d1e06cb881481c6cd8e72581e3069b365e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44015551198b6a04525a17079b065b85a81f5086ae2a22febcc3466596700035201da645ed2b7cb5093ddc6e4585d4fff105cf7fd806227eeb64f089f3e4e853
|
7
|
+
data.tar.gz: 64e7d77b762b34d4444e443ac447b9c0a9ad201724daee2eb4f5745f83598853e8dd4d5674e48bc68815756d7132530103b5e7ee1ec3756a0837d38398f056da
|
data/README.md
CHANGED
@@ -8,13 +8,18 @@ This tool is very similar to [Hub](https://github.com/github/hub), but it suppor
|
|
8
8
|
|
9
9
|
Please see the [development status](#development-status) section for informations about the current development.
|
10
10
|
|
11
|
+
## Providers support
|
12
|
+
|
13
|
+
Currently, there are many functionalities implemented for GitHub (see help), and only issues listing for GitLab.
|
14
|
+
|
11
15
|
## Samples
|
12
16
|
|
13
17
|
### Prerequisite(s)
|
14
18
|
|
15
|
-
Geet requires the
|
19
|
+
Geet requires the API token environment variable to be set, eg:
|
16
20
|
|
17
|
-
export GITHUB_API_TOKEN=0123456789abcdef0123456789abcdef
|
21
|
+
export GITHUB_API_TOKEN=0123456789abcdef0123456789abcdef # for GitHub
|
22
|
+
export GITLAB_API_TOKEN=0123456789abcd-ef0-1 # for GitLab
|
18
23
|
|
19
24
|
All the commands need to be run from the git repository.
|
20
25
|
|
data/bin/geet
CHANGED
@@ -14,10 +14,9 @@ class GeetLauncher
|
|
14
14
|
commandline_configuration = Commandline::Configuration.new
|
15
15
|
|
16
16
|
command, options = commandline_configuration.decode_argv || exit
|
17
|
-
api_token = commandline_configuration.api_token
|
18
17
|
|
19
18
|
# `:upstream` is always false in the gist command case.
|
20
|
-
repository = Git::Repository.new(
|
19
|
+
repository = Git::Repository.new(upstream: !!options[:upstream])
|
21
20
|
|
22
21
|
case command
|
23
22
|
when GIST_CREATE_COMMAND
|
data/geet.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.required_ruby_version = '>= 2.2.0'
|
12
12
|
s.authors = ['Saverio Miroddi']
|
13
|
-
s.date = '2017-11-
|
13
|
+
s.date = '2017-11-26'
|
14
14
|
s.email = ['saverio.pub2@gmail.com']
|
15
15
|
s.homepage = 'https://github.com/saveriomiroddi/geet'
|
16
16
|
s.summary = 'Commandline interface for performing SCM (eg. GitHub) operations (eg. PR creation).'
|
data/lib/geet/git/repository.rb
CHANGED
@@ -19,58 +19,58 @@ module Geet
|
|
19
19
|
ORIGIN_NAME = 'origin'
|
20
20
|
UPSTREAM_NAME = 'upstream'
|
21
21
|
|
22
|
-
def initialize(
|
23
|
-
@api_token = api_token
|
22
|
+
def initialize(upstream: false, location: nil)
|
24
23
|
@upstream = upstream
|
25
24
|
@location = location
|
25
|
+
@api_token = extract_env_api_token
|
26
26
|
end
|
27
27
|
|
28
28
|
# REMOTE FUNCTIONALITIES (REPOSITORY)
|
29
29
|
|
30
30
|
def collaborators
|
31
|
-
|
31
|
+
attempt_provider_call(:Collaborator, :list, api_interface)
|
32
32
|
end
|
33
33
|
|
34
34
|
def labels
|
35
|
-
|
35
|
+
attempt_provider_call(:Label, :list, api_interface)
|
36
36
|
end
|
37
37
|
|
38
38
|
def create_gist(filename, content, description: nil, publik: false)
|
39
|
-
|
39
|
+
attempt_provider_call(:Gist, :create, filename, content, api_interface, description: description, publik: publik)
|
40
40
|
end
|
41
41
|
|
42
42
|
def create_issue(title, description)
|
43
|
-
|
43
|
+
attempt_provider_call(:Issue, :create, title, description, api_interface)
|
44
44
|
end
|
45
45
|
|
46
46
|
def abstract_issues(milestone: nil)
|
47
|
-
|
47
|
+
attempt_provider_call(:AbstractIssue, :list, api_interface, milestone: milestone)
|
48
48
|
end
|
49
49
|
|
50
50
|
def issues
|
51
|
-
|
51
|
+
attempt_provider_call(:Issue, :list, api_interface)
|
52
52
|
end
|
53
53
|
|
54
54
|
def milestone(number)
|
55
|
-
|
55
|
+
attempt_provider_call(:Milestone, :find, number, api_interface)
|
56
56
|
end
|
57
57
|
|
58
58
|
def milestones
|
59
|
-
|
59
|
+
attempt_provider_call(:Milestone, :list, api_interface)
|
60
60
|
end
|
61
61
|
|
62
62
|
def create_pr(title, description, head)
|
63
|
-
|
63
|
+
attempt_provider_call(:PR, :create, title, description, head, api_interface)
|
64
64
|
end
|
65
65
|
|
66
66
|
def prs(head: nil)
|
67
|
-
|
67
|
+
attempt_provider_call(:PR, :list, api_interface, head: head)
|
68
68
|
end
|
69
69
|
|
70
70
|
# REMOTE FUNCTIONALITIES (ACCOUNT)
|
71
71
|
|
72
72
|
def authenticated_user
|
73
|
-
|
73
|
+
attempt_provider_call(:Account, :new, api_interface).authenticated_user
|
74
74
|
end
|
75
75
|
|
76
76
|
# OTHER/CONVENIENCE FUNCTIONALITIES
|
@@ -105,6 +105,12 @@ module Geet
|
|
105
105
|
|
106
106
|
# PROVIDER
|
107
107
|
|
108
|
+
def extract_env_api_token
|
109
|
+
env_variable_name = "#{provider_domain[/(.*)\.\w+/, 1].upcase}_API_TOKEN"
|
110
|
+
|
111
|
+
ENV[env_variable_name] || raise("#{env_variable_name} not set!")
|
112
|
+
end
|
113
|
+
|
108
114
|
def provider_domain
|
109
115
|
# We assume that it's not possible to have origin and upstream on different providers.
|
110
116
|
#
|
@@ -117,12 +123,26 @@ module Geet
|
|
117
123
|
domain
|
118
124
|
end
|
119
125
|
|
120
|
-
|
126
|
+
# Attempt to find the provider class and send the specified method, returning a friendly
|
127
|
+
# error (functionality X [Y] is missing) when a class/method is missing.
|
128
|
+
def attempt_provider_call(class_name, meth, *args)
|
121
129
|
module_name = provider_domain[/(.*)\.\w+/, 1].capitalize
|
122
130
|
|
123
131
|
require_provider_modules
|
124
132
|
|
125
|
-
|
133
|
+
full_class_name = "Geet::#{module_name}::#{class_name}"
|
134
|
+
|
135
|
+
if Kernel.const_defined?(full_class_name)
|
136
|
+
klass = Kernel.const_get(full_class_name)
|
137
|
+
|
138
|
+
if ! klass.respond_to?(meth)
|
139
|
+
raise "The functionality invoked (#{class_name} #{meth}) is not currently supported!"
|
140
|
+
end
|
141
|
+
|
142
|
+
klass.send(meth, *args)
|
143
|
+
else
|
144
|
+
raise "The functionality (#{class_name}) invoked is not currently supported!"
|
145
|
+
end
|
126
146
|
end
|
127
147
|
|
128
148
|
def require_provider_modules
|
@@ -135,7 +155,7 @@ module Geet
|
|
135
155
|
# OTHER HELPERS
|
136
156
|
|
137
157
|
def api_interface
|
138
|
-
|
158
|
+
attempt_provider_call(:ApiInterface, :new, @api_token, path(upstream: @upstream), @upstream)
|
139
159
|
end
|
140
160
|
|
141
161
|
# Example: `donaldduck/geet`
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'uri'
|
5
|
+
require 'net/http'
|
6
|
+
require 'json'
|
7
|
+
require 'shellwords'
|
8
|
+
|
9
|
+
module Geet
|
10
|
+
module Gitlab
|
11
|
+
class ApiInterface
|
12
|
+
API_BASE_URL = 'https://gitlab.com/api/v4'
|
13
|
+
|
14
|
+
def initialize(api_token, path_with_namespace, upstream)
|
15
|
+
@api_token = api_token
|
16
|
+
@path_with_namespace = path_with_namespace
|
17
|
+
@upstream = upstream
|
18
|
+
end
|
19
|
+
|
20
|
+
def upstream?
|
21
|
+
@upstream
|
22
|
+
end
|
23
|
+
|
24
|
+
def path_with_namespace(encoded: false)
|
25
|
+
encoded ? CGI.escape(@path_with_namespace) : @path_with_namespace
|
26
|
+
end
|
27
|
+
|
28
|
+
# Send a request.
|
29
|
+
#
|
30
|
+
# Returns the parsed response, or an Array, in case of multipage.
|
31
|
+
#
|
32
|
+
# params:
|
33
|
+
# :api_path: api path, will be appended to the API URL.
|
34
|
+
# for root path, prepend a `/`:
|
35
|
+
# - use `/gists` for `https://api.github.com/gists`
|
36
|
+
# when owner/project/repos is included, don't prepend `/`:
|
37
|
+
# - use `issues` for `https://api.github.com/myowner/myproject/repos/issues`
|
38
|
+
# :params: (Hash)
|
39
|
+
# :data: (Hash) if present, will generate a POST request, otherwise, a GET
|
40
|
+
# :multipage: set true for paged Github responses (eg. issues); it will make the method
|
41
|
+
# return an array, with the concatenated (parsed) responses
|
42
|
+
# :http_method: :get, :patch, :post and :put are accepted, but only :patch/:put are meaningful,
|
43
|
+
# since the others are automatically inferred by :data.
|
44
|
+
#
|
45
|
+
def send_request(api_path, params: nil, data: nil, multipage: false, http_method: nil)
|
46
|
+
address = api_url(api_path)
|
47
|
+
# filled only on :multipage
|
48
|
+
parsed_responses = []
|
49
|
+
|
50
|
+
loop do
|
51
|
+
response = send_http_request(address, params: params, data: data, http_method: http_method)
|
52
|
+
|
53
|
+
parsed_response = JSON.parse(response.body)
|
54
|
+
|
55
|
+
if error?(response)
|
56
|
+
formatted_error = decode_and_format_error(parsed_response)
|
57
|
+
raise(formatted_error)
|
58
|
+
end
|
59
|
+
|
60
|
+
return parsed_response if !multipage
|
61
|
+
|
62
|
+
parsed_responses.concat(parsed_response)
|
63
|
+
|
64
|
+
address = link_next_page(response.to_hash)
|
65
|
+
|
66
|
+
return parsed_responses if address.nil?
|
67
|
+
|
68
|
+
# Gitlab's next link address already includes all the params, so we remove
|
69
|
+
# the passed ones (if there's any).
|
70
|
+
params = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def api_url(api_path)
|
77
|
+
"#{API_BASE_URL}/#{api_path}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def send_http_request(address, params: nil, data: nil, http_method: nil)
|
81
|
+
uri = encode_uri(address, params)
|
82
|
+
http_class = find_http_class(http_method, data)
|
83
|
+
|
84
|
+
Net::HTTP.start(uri.host, use_ssl: true) do |http|
|
85
|
+
request = http_class.new(uri)
|
86
|
+
|
87
|
+
request['Private-Token'] = @api_token
|
88
|
+
request.body = data.to_json if data
|
89
|
+
|
90
|
+
http.request(request)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def encode_uri(address, params)
|
95
|
+
address += '?' + URI.encode_www_form(params) if params
|
96
|
+
|
97
|
+
URI(address)
|
98
|
+
end
|
99
|
+
|
100
|
+
def error?(response)
|
101
|
+
!response.code.start_with?('2')
|
102
|
+
end
|
103
|
+
|
104
|
+
def decode_and_format_error(parsed_response)
|
105
|
+
parsed_response.fetch('error')
|
106
|
+
end
|
107
|
+
|
108
|
+
def link_next_page(response_headers)
|
109
|
+
# An array (or nil) is returned.
|
110
|
+
link_header = Array(response_headers['link'])
|
111
|
+
|
112
|
+
return nil if link_header.empty?
|
113
|
+
|
114
|
+
link_header[0][/<(\S+)>; rel="next"/, 1]
|
115
|
+
end
|
116
|
+
|
117
|
+
def find_http_class(http_method, data)
|
118
|
+
http_method ||= data ? :post : :get
|
119
|
+
|
120
|
+
case http_method
|
121
|
+
when :get
|
122
|
+
Net::HTTP::Get
|
123
|
+
when :patch
|
124
|
+
Net::HTTP::Patch
|
125
|
+
when :put
|
126
|
+
Net::HTTP::Put
|
127
|
+
when :post
|
128
|
+
Net::HTTP::Post
|
129
|
+
else
|
130
|
+
raise "Unsupported HTTP method: #{http_method.inspect}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geet
|
4
|
+
module Gitlab
|
5
|
+
class Issue
|
6
|
+
attr_reader :number, :title, :link
|
7
|
+
|
8
|
+
def initialize(number, title, link)
|
9
|
+
@number = number
|
10
|
+
@title = title
|
11
|
+
@link = link
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.list(api_interface)
|
15
|
+
api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/issues"
|
16
|
+
|
17
|
+
response = api_interface.send_request(api_path, multipage: true)
|
18
|
+
|
19
|
+
response.each_with_object([]) do |issue_data, result|
|
20
|
+
number = issue_data.fetch('iid')
|
21
|
+
title = issue_data.fetch('title')
|
22
|
+
link = issue_data.fetch('web_url')
|
23
|
+
|
24
|
+
result << new(number, title, link)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/geet/version.rb
CHANGED
@@ -5,7 +5,7 @@ require_relative '../../lib/geet/git/repository'
|
|
5
5
|
require_relative '../../lib/geet/services/create_gist'
|
6
6
|
|
7
7
|
describe Geet::Services::CreateGist do
|
8
|
-
let(:repository) { Geet::Git::Repository.new
|
8
|
+
let(:repository) { Geet::Git::Repository.new }
|
9
9
|
let(:tempfile) { Tempfile.open('geet_gist') { |file| file << "testcontent" } }
|
10
10
|
|
11
11
|
it 'should create a public gist' do
|
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/create_issue'
|
5
5
|
|
6
6
|
describe Geet::Services::CreateIssue do
|
7
|
-
let(:repository) { Geet::Git::Repository.new
|
8
|
-
let(:upstream_repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new }
|
8
|
+
let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
|
9
9
|
|
10
10
|
context 'with labels, assignees and milestones' do
|
11
11
|
it 'should create an issue' do
|
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/create_pr'
|
5
5
|
|
6
6
|
describe Geet::Services::CreatePr do
|
7
|
-
let(:repository) { Geet::Git::Repository.new(
|
8
|
-
let(:upstream_repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new() }
|
8
|
+
let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
|
9
9
|
|
10
10
|
context 'with labels, reviewers and milestones' do
|
11
11
|
it 'should create a PR' do
|
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/list_issues'
|
5
5
|
|
6
6
|
describe Geet::Services::ListIssues do
|
7
|
-
let(:repository) { Geet::Git::Repository.new(
|
8
|
-
let(:upstream_repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new() }
|
8
|
+
let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
|
9
9
|
|
10
10
|
it 'should list the issues' do
|
11
11
|
allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
|
@@ -4,7 +4,7 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/list_labels'
|
5
5
|
|
6
6
|
describe Geet::Services::ListLabels do
|
7
|
-
let(:repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new() }
|
8
8
|
|
9
9
|
it 'should list the labels' do
|
10
10
|
allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/geet')
|
@@ -4,7 +4,7 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/list_milestones'
|
5
5
|
|
6
6
|
describe Geet::Services::ListMilestones do
|
7
|
-
let(:repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new() }
|
8
8
|
|
9
9
|
it 'should list the milestones' do
|
10
10
|
allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/geet')
|
@@ -4,8 +4,8 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/list_prs'
|
5
5
|
|
6
6
|
describe Geet::Services::ListPrs do
|
7
|
-
let(:repository) { Geet::Git::Repository.new(
|
8
|
-
let(:upstream_repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new() }
|
8
|
+
let(:upstream_repository) { Geet::Git::Repository.new(upstream: true) }
|
9
9
|
|
10
10
|
it 'should list the PRs' do
|
11
11
|
allow(repository).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
|
@@ -4,7 +4,7 @@ require_relative '../../lib/geet/git/repository'
|
|
4
4
|
require_relative '../../lib/geet/services/merge_pr'
|
5
5
|
|
6
6
|
describe Geet::Services::MergePr do
|
7
|
-
let(:repository) { Geet::Git::Repository.new(
|
7
|
+
let(:repository) { Geet::Git::Repository.new() }
|
8
8
|
|
9
9
|
it 'should merge the PR for the current branch' do
|
10
10
|
allow(repository).to receive(:current_branch).and_return('mybranch1')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Saverio Miroddi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simple_scripting
|
@@ -55,6 +55,8 @@ files:
|
|
55
55
|
- lib/geet/github/label.rb
|
56
56
|
- lib/geet/github/milestone.rb
|
57
57
|
- lib/geet/github/pr.rb
|
58
|
+
- lib/geet/gitlab/api_interface.rb
|
59
|
+
- lib/geet/gitlab/issue.rb
|
58
60
|
- lib/geet/helpers/os_helper.rb
|
59
61
|
- lib/geet/services/create_gist.rb
|
60
62
|
- lib/geet/services/create_issue.rb
|
@@ -107,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
109
|
version: '0'
|
108
110
|
requirements: []
|
109
111
|
rubyforge_project:
|
110
|
-
rubygems_version: 2.6.
|
112
|
+
rubygems_version: 2.6.11
|
111
113
|
signing_key:
|
112
114
|
specification_version: 4
|
113
115
|
summary: Commandline interface for performing SCM (eg. GitHub) operations (eg. PR
|