octokom 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.travis.yml +8 -1
- data/LICENSE.txt +1 -1
- data/README.md +11 -3
- data/bin/octokom +1 -1
- data/lib/octokom.rb +8 -12
- data/lib/octokom/cli.rb +6 -0
- data/lib/octokom/client.rb +99 -0
- data/lib/octokom/command.rb +65 -2
- data/lib/octokom/command/issue.rb +26 -0
- data/lib/octokom/command/pull_request.rb +31 -22
- data/lib/octokom/core_ext/blank.rb +51 -0
- data/lib/octokom/editor.rb +106 -0
- data/lib/octokom/repository.rb +59 -24
- data/lib/octokom/shell.rb +22 -0
- data/lib/octokom/version.rb +1 -1
- data/octokom.gemspec +8 -6
- data/spec/octokom/cli_spec.rb +13 -0
- data/spec/octokom/client_spec.rb +67 -0
- data/spec/octokom/command/issue_spec.rb +58 -0
- data/spec/octokom/command/pull_request_spec.rb +3 -4
- data/spec/octokom/command_spec.rb +56 -3
- data/spec/octokom/editor_spec.rb +33 -0
- data/spec/octokom/repository_spec.rb +62 -5
- data/spec/octokom/shell_spec.rb +54 -0
- data/spec/spec_helper.rb +7 -1
- data/spec/support/OCTOKOM-123 +11 -0
- data/spec/support/git_branch_no_pending_commits.txt +5 -0
- data/spec/support/git_branch_no_remote.txt +5 -0
- data/spec/support/git_branch_pending_commits.txt +5 -0
- data/spec/support/git_log_commits.txt +1 -0
- metadata +59 -42
- data/lib/octokom/authentication.rb +0 -42
- data/lib/octokom/keychain.rb +0 -54
- data/spec/octokom/authentication_spec.rb +0 -25
- data/spec/octokom/keychain_spec.rb +0 -45
- data/spec/octokom_spec.rb +0 -8
data/lib/octokom/repository.rb
CHANGED
@@ -1,29 +1,64 @@
|
|
1
1
|
module Octokom
|
2
2
|
class Repository
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
3
|
+
include Octokom::Shell
|
4
|
+
|
5
|
+
def initialize(verbose)
|
6
|
+
@verbose = verbose
|
7
|
+
end
|
8
|
+
|
9
|
+
def repo_path
|
10
|
+
error('Git remote not defined') if remote_url.blank?
|
11
|
+
path = remote_url.split('/')[-2..-1]
|
12
|
+
path.join('/')[/(.*)\.git$/, 1]
|
13
|
+
end
|
14
|
+
|
15
|
+
def toplevel_path
|
16
|
+
git('rev-parse --show-toplevel')
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO Make base branch configurable.
|
20
|
+
def base_branch
|
21
|
+
'master'
|
22
|
+
end
|
23
|
+
|
24
|
+
def head_branch
|
25
|
+
git('rev-parse --abbrev-ref HEAD')
|
26
|
+
end
|
27
|
+
|
28
|
+
def pending_commits?
|
29
|
+
current_sha1 = last_commits[/^\*\s((\w|-|_)*)\s+(\w{7})/, 3]
|
30
|
+
remote_sha1 = last_commits[/remotes\/origin\/#{head_branch}\s+(\w{7})/, 1]
|
31
|
+
|
32
|
+
return true if remote_sha1.blank?
|
33
|
+
current_sha1 != remote_sha1 ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_commit_message
|
37
|
+
git('log -1 --oneline')[/^\w+\s(.*)$/, 1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def push(branch)
|
41
|
+
git("push origin #{branch}")
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# TODO Make origin configurable.
|
47
|
+
def remote_url
|
48
|
+
@remote_url ||= git('config --get remote.origin.url')
|
49
|
+
end
|
50
|
+
|
51
|
+
def last_commits
|
52
|
+
@last_commits ||= git('branch -avvv')
|
53
|
+
end
|
54
|
+
|
55
|
+
def verbose?
|
56
|
+
!!@verbose
|
57
|
+
end
|
58
|
+
|
59
|
+
def git(cmd)
|
60
|
+
puts "git #{cmd}" if verbose?
|
61
|
+
`git #{cmd}`.chomp
|
27
62
|
end
|
28
63
|
end
|
29
64
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Octokom
|
2
|
+
module Shell
|
3
|
+
def info(message)
|
4
|
+
puts "Info #{message}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def error(message)
|
8
|
+
puts "Error #{message}"
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Print a waiting message before a long running task and replace
|
13
|
+
# the message as soon as the task has finished.
|
14
|
+
def task(message)
|
15
|
+
print "Waiting #{message}"
|
16
|
+
yield
|
17
|
+
print "\r"
|
18
|
+
print "Done #{message}"
|
19
|
+
puts
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/octokom/version.rb
CHANGED
data/octokom.gemspec
CHANGED
@@ -8,20 +8,22 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Octokom::VERSION
|
9
9
|
gem.authors = 'Martin Jagusch'
|
10
10
|
gem.email = '_@mj.io'
|
11
|
-
gem.description = '
|
11
|
+
gem.description = 'GitHub For Your Command Line'
|
12
12
|
gem.summary = "octokom-#{Octokom::VERSION}"
|
13
13
|
gem.homepage = 'https://github.com/mjio/octokom'
|
14
14
|
gem.license = 'MIT'
|
15
15
|
|
16
|
+
gem.required_ruby_version = '>= 1.9.3'
|
17
|
+
|
16
18
|
gem.files = `git ls-files`.split($/)
|
17
19
|
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
20
|
gem.test_files = gem.files.grep(%r{^spec/})
|
19
21
|
gem.require_paths = ['lib']
|
20
22
|
|
21
|
-
gem.add_dependency 'clamp', '~> 0.6.
|
22
|
-
gem.add_dependency '
|
23
|
-
gem.add_dependency '
|
23
|
+
gem.add_dependency 'clamp', '~> 0.6.3'
|
24
|
+
gem.add_dependency 'netrc', '~> 0.7.7'
|
25
|
+
gem.add_dependency 'octokit', '~> 2.7.1'
|
24
26
|
|
25
|
-
gem.add_development_dependency 'rake', '~> 10.1.
|
26
|
-
gem.add_development_dependency 'rspec', '~>
|
27
|
+
gem.add_development_dependency 'rake', '~> 10.1.1'
|
28
|
+
gem.add_development_dependency 'rspec', '~> 3.0.0.beta2'
|
27
29
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octokom::CLI do
|
4
|
+
it 'has a "issue" sub command' do
|
5
|
+
command = Octokom::CLI.find_subcommand('issue')
|
6
|
+
expect(command).to be_kind_of(Clamp::Subcommand::Definition)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'has a "pull-request" sub command' do
|
10
|
+
command = Octokom::CLI.find_subcommand('pull-request')
|
11
|
+
expect(command).to be_kind_of(Clamp::Subcommand::Definition)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'octokit'
|
3
|
+
|
4
|
+
describe Octokom::Client do
|
5
|
+
let(:netrc) { {Octokom::Client::KEY => credentials} }
|
6
|
+
|
7
|
+
before do
|
8
|
+
allow(Netrc).to receive(:new) { netrc }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#authenticate' do
|
12
|
+
context 'with valid netrc file' do
|
13
|
+
let(:credentials) { ['fox', 'chunky'] }
|
14
|
+
|
15
|
+
it 'parses the credentials' do
|
16
|
+
auth = Octokom::Client.authenticate
|
17
|
+
expect(auth.token).to eq('chunky')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns a client instance' do
|
21
|
+
auth = Octokom::Client.authenticate
|
22
|
+
expect(auth).to be_kind_of(Octokom::Client)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'handels invalid API tokens on GitHub'
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with invalid netrc file' do
|
29
|
+
let(:credentials) { [] }
|
30
|
+
let(:octokit) { double }
|
31
|
+
let(:auth) { double(:token => nil) }
|
32
|
+
|
33
|
+
before do
|
34
|
+
allow_any_instance_of(Octokom::Client).to receive(:ask_for_login) { 'fox' }
|
35
|
+
allow_any_instance_of(Octokom::Client).to receive(:ask_for_password) { 'bacon' }
|
36
|
+
allow(Octokit::Client).to receive(:new) { octokit }
|
37
|
+
allow(octokit).to receive(:create_authorization) { auth }
|
38
|
+
allow(netrc).to receive(:save) { true }
|
39
|
+
allow(STDOUT).to receive(:puts)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'initializes a new Octokit client' do
|
43
|
+
expect(Octokit::Client).to receive(:new).with(login: 'fox', password: 'bacon')
|
44
|
+
Octokom::Client.authenticate
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'creates a authorization token' do
|
48
|
+
expect(octokit).to receive(:create_authorization)
|
49
|
+
Octokom::Client.authenticate
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'creates a authorization token' do
|
53
|
+
expect(netrc).to receive(:save)
|
54
|
+
Octokom::Client.authenticate
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#client' do
|
60
|
+
let(:credentials) { ['fox', 'chunky'] }
|
61
|
+
|
62
|
+
it 'initializes a new Octokit client' do
|
63
|
+
auth = Octokom::Client.authenticate
|
64
|
+
expect(auth.client).to be_kind_of(Octokit::Client)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'octokom/command/issue'
|
3
|
+
|
4
|
+
describe Issue do
|
5
|
+
describe '#create' do
|
6
|
+
let(:git) { double(repo_path: 'user/repo') }
|
7
|
+
let(:auth) { double(client: client) }
|
8
|
+
let(:client) { double }
|
9
|
+
|
10
|
+
before do
|
11
|
+
allow(Octokom::Repository).to receive(:new) { git }
|
12
|
+
allow(Octokom::Client).to receive(:authenticate) { auth }
|
13
|
+
allow(client).to receive(:create_issue)
|
14
|
+
allow_any_instance_of(Octokom::Command).to receive(:print)
|
15
|
+
allow_any_instance_of(Octokom::Command).to receive(:puts)
|
16
|
+
end
|
17
|
+
|
18
|
+
subject(:cmd) { Issue.new('test') }
|
19
|
+
|
20
|
+
context 'when no "title" option is set' do
|
21
|
+
let(:options) { %w(create) }
|
22
|
+
|
23
|
+
it 'opens the editor' do
|
24
|
+
expect(Octokom::Editor).to receive(:open).with(git, nil, nil) { ['Bacon'] }
|
25
|
+
|
26
|
+
cmd.run(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'exits when no "title" can be parsed'
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when "title" option is set' do
|
33
|
+
let(:options) { %w(create --title Bacon) }
|
34
|
+
|
35
|
+
it 'creates a GitHub issue' do
|
36
|
+
expect(client).to receive(:create_issue).with('user/repo', 'Bacon', nil)
|
37
|
+
|
38
|
+
cmd.run(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'and "dry-run" tag is set' do
|
42
|
+
it 'does not create a GitHub issue' do
|
43
|
+
expect(client).to_not receive(:create_issue)
|
44
|
+
|
45
|
+
cmd.run(options.push('--dry-run'))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'and "editor" tag is set' do
|
50
|
+
it 'opens the editor' do
|
51
|
+
expect(Octokom::Editor).to receive(:open).with(git, 'Bacon', nil) { ['Bacon'] }
|
52
|
+
|
53
|
+
cmd.run(options.push('--editor'))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'octokom/command/pull_request'
|
2
3
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
command = Octokom::Command::PullRequest.find_subcommand('create')
|
6
|
-
expect(command).to be_kind_of(Clamp::Subcommand::Definition)
|
4
|
+
describe PullRequest do
|
5
|
+
describe '#create' do
|
7
6
|
end
|
8
7
|
end
|
@@ -1,8 +1,61 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Octokom::Command do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
describe '#flag' do
|
5
|
+
subject(:cmd) do
|
6
|
+
class FlagCmd < Octokom::Command
|
7
|
+
flag ['--bacon'], 'Bacon'
|
8
|
+
execute { @bacon }
|
9
|
+
end
|
10
|
+
|
11
|
+
FlagCmd.new('test')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns true when the flag option is set' do
|
15
|
+
expect(cmd.run(['--bacon'])).to be(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns nil when the flag option is not set' do
|
19
|
+
expect(cmd.run([])).to be_nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#option' do
|
24
|
+
subject(:cmd) do
|
25
|
+
class OptionCmd < Octokom::Command
|
26
|
+
option ['--bacon'], 'Bacon'
|
27
|
+
execute { @bacon }
|
28
|
+
end
|
29
|
+
|
30
|
+
OptionCmd.new('test')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns the the option value' do
|
34
|
+
expect(cmd.run(['--bacon', '1'])).to eq('1')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns nil when the option is not set' do
|
38
|
+
expect(cmd.run([])).to be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#git' do
|
43
|
+
subject(:cmd) do
|
44
|
+
class GitCmd < Octokom::Command
|
45
|
+
execute { git }
|
46
|
+
end
|
47
|
+
|
48
|
+
GitCmd.new('test')
|
49
|
+
end
|
50
|
+
|
51
|
+
let(:git) { double }
|
52
|
+
|
53
|
+
before do
|
54
|
+
allow(Octokom::Repository).to receive(:new) { git }
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'initializes a Repository instance' do
|
58
|
+
expect(cmd.run([])).to eq(git)
|
59
|
+
end
|
7
60
|
end
|
8
61
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octokom::Editor do
|
4
|
+
let(:repo) { double(toplevel_path: '~') }
|
5
|
+
let(:title) { 'Feature' }
|
6
|
+
let(:desc) { '...' }
|
7
|
+
let(:editor) { Octokom::Editor.new(repo, nil, nil) }
|
8
|
+
|
9
|
+
describe '#open_editor' do
|
10
|
+
before do
|
11
|
+
allow(editor).to receive(:editor_name) { 'vi' }
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'build the command' do
|
15
|
+
expect(editor).to receive(:system).with(/^vi --nofork +6*+/)
|
16
|
+
editor.open_editor
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#parse_user_input' do
|
21
|
+
before do
|
22
|
+
allow(editor).to receive(:file_name) { './spec/support/OCTOKOM-123' }
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'finds the title' do
|
26
|
+
expect(editor.parse_user_input[0]).to eq('Chunky Bacon')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'finds the description' do
|
30
|
+
expect(editor.parse_user_input[1]).to eq("Really\n remarkable!")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,15 +1,72 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Octokom::Repository do
|
4
|
-
subject(:repo) { Octokom::Repository }
|
4
|
+
subject(:repo) { Octokom::Repository.new(false) }
|
5
5
|
|
6
|
-
describe '#
|
6
|
+
describe '#repo_path' do
|
7
|
+
context 'when a remote path is defined' do
|
8
|
+
before do
|
9
|
+
allow(repo).to receive(:remote_url) { 'https://github.com/fox-1/bacon.rb.git' }
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns the GitHub repository path' do
|
13
|
+
expect(repo.repo_path).to eq('fox-1/bacon.rb')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when no remote path is defined' do
|
18
|
+
let(:stdout) { double }
|
19
|
+
|
20
|
+
before do
|
21
|
+
allow(repo).to receive(:remote_url) { nil }
|
22
|
+
allow(stdout).to receive(:puts)
|
23
|
+
allow(repo).to receive(:puts) { |msg| stdout.puts(msg) }
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'prints a error message and exits' do
|
27
|
+
expect(stdout).to receive(:puts).with(/Git remote not defined/)
|
28
|
+
expect { repo.repo_path }.to raise_error(SystemExit)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#pending_commits?' do
|
34
|
+
before do
|
35
|
+
allow(repo).to receive(:head_branch) { 'test-1' }
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with pending commits' do
|
39
|
+
before do
|
40
|
+
allow(repo).to receive(:last_commits) { load_example(:git_branch_pending_commits) }
|
41
|
+
end
|
42
|
+
|
43
|
+
specify { expect(repo.pending_commits?).to be(true) }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with no pending commits' do
|
47
|
+
before do
|
48
|
+
allow(repo).to receive(:last_commits) { load_example(:git_branch_no_pending_commits) }
|
49
|
+
end
|
50
|
+
|
51
|
+
specify { expect(repo.pending_commits?).to be(false) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with no remote branch' do
|
55
|
+
before do
|
56
|
+
allow(repo).to receive(:last_commits) { load_example(:git_branch_no_remote) }
|
57
|
+
end
|
58
|
+
|
59
|
+
specify { expect(repo.pending_commits?).to be(true) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#last_commit_message' do
|
7
64
|
before do
|
8
|
-
allow(repo).to receive(:
|
65
|
+
allow(repo).to receive(:git) { load_example(:git_log_commits) }
|
9
66
|
end
|
10
67
|
|
11
|
-
it 'returns
|
12
|
-
expect(repo.
|
68
|
+
it 'returns the commit message' do
|
69
|
+
expect(repo.last_commit_message).to eq('Add all the features')
|
13
70
|
end
|
14
71
|
end
|
15
72
|
end
|