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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a827d2870894f3b5f4c778cbc97475081b536758
|
4
|
+
data.tar.gz: 925ab8ea02b3347905a44f4752f79348329b2801
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cb21e97e894109e9b5e05a0a1c30df072d3de2f2a446a197f838ce6c6d07340d4896fcadca62b7007406990bc6cf3ea3d749d0a33d0f22009e05d1da0e7796a
|
7
|
+
data.tar.gz: cdde0f3ac7c090bb8ce170091a1ffcd2d5a3849d4d6ec93b5aee7cac933e48d091755d70596c8a2ccd4411797f3cef9f0e9dcc4025fd9fb11195f400a0b5387f
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.travis.yml
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
3
|
rvm:
|
4
|
+
- rbx-19mode
|
5
|
+
- jruby-19mode
|
6
|
+
- 1.9.2
|
4
7
|
- 1.9.3
|
5
8
|
- 2.0.0
|
9
|
+
- 2.1.0
|
6
10
|
- ruby-head
|
7
11
|
|
8
12
|
matrix:
|
9
13
|
allow_failures:
|
14
|
+
- rvm: rbx-19mode
|
15
|
+
- rvm: jruby-19mode
|
16
|
+
- rvm: 1.9.2
|
10
17
|
- rvm: ruby-head
|
11
18
|
|
12
19
|
notifications:
|
13
20
|
email:
|
14
|
-
on_success:
|
21
|
+
on_success: never
|
15
22
|
on_failure: always
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Octokom
|
2
2
|
|
3
|
-
|
3
|
+
Create pull requests, GitHub issues etc. quickly from your command line in three words `octokom pull-request create`. Octokom finds automatically the name of your repository and Git branch.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -8,10 +8,18 @@ Work in progress command line application for your every day GitHub workflow.
|
|
8
8
|
$ gem install octokom
|
9
9
|
```
|
10
10
|
|
11
|
-
##
|
11
|
+
## The Interface
|
12
12
|
|
13
|
-
|
13
|
+
Run `octokom --help` to see a list of all supported commands and options.
|
14
|
+
|
15
|
+
#### Pull Request
|
14
16
|
|
15
17
|
```bash
|
16
18
|
$ octokom pull-request create --title "New feature"
|
17
19
|
```
|
20
|
+
|
21
|
+
#### GitHub Issue
|
22
|
+
|
23
|
+
```bash
|
24
|
+
$ octokom issue create --title "Nasty Bug"
|
25
|
+
```
|
data/bin/octokom
CHANGED
data/lib/octokom.rb
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
-
require '
|
2
|
-
require 'octokit'
|
3
|
-
require 'security'
|
4
|
-
|
5
|
-
require 'octokom/authentication'
|
6
|
-
require 'octokom/command'
|
7
|
-
require 'octokom/keychain'
|
8
|
-
require 'octokom/repository'
|
9
|
-
require 'octokom/version'
|
1
|
+
require 'octokom/core_ext/blank'
|
10
2
|
|
11
3
|
module Octokom
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
autoload :CLI, 'octokom/cli'
|
5
|
+
autoload :Client, 'octokom/client'
|
6
|
+
autoload :Command, 'octokom/command'
|
7
|
+
autoload :Editor, 'octokom/editor'
|
8
|
+
autoload :Repository, 'octokom/repository'
|
9
|
+
autoload :Shell, 'octokom/shell'
|
10
|
+
autoload :VERSION, 'octokom/version'
|
15
11
|
end
|
data/lib/octokom/cli.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require 'netrc'
|
3
|
+
|
4
|
+
module Octokom
|
5
|
+
class Client
|
6
|
+
private_class_method :new
|
7
|
+
|
8
|
+
attr_reader :token
|
9
|
+
|
10
|
+
KEY = 'api.github.com'
|
11
|
+
URL = 'https://github.com/mjio/octokom'
|
12
|
+
|
13
|
+
def self.authenticate
|
14
|
+
new.authenticate
|
15
|
+
end
|
16
|
+
|
17
|
+
# Use the existing credentials form the ~/.netrc file to verify API
|
18
|
+
# requests to GitHub. When no credentials exist, ask for login and
|
19
|
+
# password to request a new token from GitHub.
|
20
|
+
def authenticate
|
21
|
+
_, @token = netrc[KEY]
|
22
|
+
|
23
|
+
unless token
|
24
|
+
login, password = ask_for_credentials
|
25
|
+
create_authorization(login, password)
|
26
|
+
end
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def client
|
32
|
+
require 'octokit'
|
33
|
+
Octokit::Client.new(access_token: token)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def path
|
39
|
+
@path ||= Netrc.default_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def file
|
43
|
+
@file ||= File.open(path, File::RDWR|File::CREAT, 0600)
|
44
|
+
end
|
45
|
+
|
46
|
+
def data
|
47
|
+
@data ||= Netrc.parse(Netrc.lex(file.each_line.to_a))
|
48
|
+
end
|
49
|
+
|
50
|
+
def netrc
|
51
|
+
@netrc ||= Netrc.new(path, data)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ask_for_credentials
|
55
|
+
login = ask_for_login
|
56
|
+
password = ask_for_password
|
57
|
+
puts # newline
|
58
|
+
[login, password]
|
59
|
+
end
|
60
|
+
|
61
|
+
def ask_for_login
|
62
|
+
print 'GitHub Login: '
|
63
|
+
STDIN.gets.chomp
|
64
|
+
end
|
65
|
+
|
66
|
+
def ask_for_password
|
67
|
+
print 'Password: '
|
68
|
+
STDIN.noecho(&:gets).chomp
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_authorization(login, password)
|
72
|
+
require 'octokit'
|
73
|
+
|
74
|
+
begin
|
75
|
+
client = Octokit::Client.new(login: login, password: password)
|
76
|
+
auth = client.create_authorization(scopes: ['repo'], note: api_note, note_url: URL)
|
77
|
+
rescue Octokit::Unauthorized
|
78
|
+
puts "Unable to authorise user @#{login}"
|
79
|
+
exit(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
@token = auth.token
|
83
|
+
save_token_to_netrc(login)
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_token_to_netrc(login)
|
87
|
+
netrc[KEY] = login, @token
|
88
|
+
netrc.save
|
89
|
+
end
|
90
|
+
|
91
|
+
def api_note
|
92
|
+
"Octokom #{hostname}@#{Time.now.usec}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def hostname
|
96
|
+
`hostname`.chomp
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/octokom/command.rb
CHANGED
@@ -1,7 +1,70 @@
|
|
1
|
+
require 'clamp'
|
2
|
+
|
3
|
+
autoload :Issue, 'octokom/command/issue'
|
4
|
+
autoload :PullRequest, 'octokom/command/pull_request'
|
5
|
+
|
1
6
|
module Octokom
|
2
7
|
class Command < Clamp::Command
|
3
|
-
|
8
|
+
include Octokom::Shell
|
9
|
+
|
10
|
+
# Shortcut for `Clamp::Command.option` with `:flag` argument.
|
11
|
+
#
|
12
|
+
# For example:
|
13
|
+
# flag ['-a'], A option'
|
14
|
+
#
|
15
|
+
# Is the same as:
|
16
|
+
# option ['-a'], :flag, 'A option'
|
17
|
+
def self.flag(tags, desc, &block)
|
18
|
+
option_without_type(tags, :flag, desc, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Alias for the `Clamp::Command.option` method that uses the long option
|
22
|
+
# tag as type string.
|
23
|
+
#
|
24
|
+
# For example:
|
25
|
+
# Octokom::Command.option ['-a', '--abc'], 'ABC option'
|
26
|
+
#
|
27
|
+
# Is the same as:
|
28
|
+
# Clamp::Command.option ['-a', '--abc'], 'ABC', 'ABC option'
|
29
|
+
def self.option_with_type(tags, desc, &block)
|
30
|
+
type = tags.first[/^--(.*)$/, 1].upcase
|
31
|
+
option_without_type(tags, type, desc, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Override `Clamp::Command.execute` method so it can be used as a block.
|
35
|
+
def self.execute(&block)
|
36
|
+
define_method(:execute, block) do
|
37
|
+
yield if block_given?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create aliases for the Clamp::Command `option` and `subcommand` methods.
|
42
|
+
singleton_class.class_eval do
|
43
|
+
alias_method :option_without_type, :option
|
44
|
+
alias_method :option, :option_with_type
|
45
|
+
alias_method :sub_command, :subcommand
|
46
|
+
end
|
47
|
+
|
48
|
+
flag ['--dry-run', '-y'], 'Do nothing, only show what would happen.'
|
49
|
+
flag ['--verbose', '-v'], 'Show the debug output.'
|
50
|
+
|
51
|
+
flag ['--version'], 'Version number.' do
|
52
|
+
puts "Octokom #{Octokom::VERSION}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Override the Clamp help message option.
|
56
|
+
flag ['--help'], 'Help message.' do
|
57
|
+
request_help
|
58
|
+
end
|
59
|
+
|
60
|
+
# Initializes a new Repository instance that is available in all
|
61
|
+
# Octokom::Command instances.
|
62
|
+
def git
|
63
|
+
@git ||= Octokom::Repository.new(verbose?)
|
64
|
+
end
|
4
65
|
|
5
|
-
|
66
|
+
def authenticate(&block)
|
67
|
+
yield Octokom::Client.authenticate
|
68
|
+
end
|
6
69
|
end
|
7
70
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Issue < Octokom::Command
|
2
|
+
sub_command 'create', 'Create a GitHub issue.' do
|
3
|
+
option ['--title', '-t'], 'Title for the issue.'
|
4
|
+
option ['--desc', '-d'], 'Description for the issue.'
|
5
|
+
option ['--repo', '-r'], 'The GitHub repository you want to update. (e.g. mjio/octokom)'
|
6
|
+
flag ['--editor', '-e'], 'Open the editor even when the issue title is set.'
|
7
|
+
|
8
|
+
execute do
|
9
|
+
@repo ||= git.repo_path
|
10
|
+
|
11
|
+
if @editor || @title.blank?
|
12
|
+
@title, @desc = Octokom::Editor.open(git, @title, @desc)
|
13
|
+
end
|
14
|
+
|
15
|
+
if @title.blank?
|
16
|
+
error('The title can not be empty')
|
17
|
+
end
|
18
|
+
|
19
|
+
authenticate do |auth|
|
20
|
+
task("Create issue #{@repo}") do
|
21
|
+
auth.client.create_issue(@repo, @title, @desc) unless dry_run?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,29 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
class PullRequest < Octokom::Command
|
2
|
+
sub_command 'create', 'Create a pull request.' do
|
3
|
+
option ['--title', '-t'], 'Title for the pull request.'
|
4
|
+
option ['--desc', '-d'], 'Description for the pull request.'
|
5
|
+
option ['--base', '-b'], 'The branch you want your changes pulled into. (e.g. master)'
|
6
|
+
option ['--head', '-h'], 'The branch where your changes are implemented. (e.g. feature)'
|
7
|
+
option ['--repo', '-r'], 'The GitHub repository you want to update. (e.g. mjio/octokom)'
|
8
|
+
flag ['--editor', '-e'], 'Open the editor even when the pull request title is set.'
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
option ['--head', '-d'], '', 'The branch where your changes are implemented. Example: "new_feature"'
|
11
|
-
# option ['--issue', '-i'], '', 'Create pull request from an existing issue. Example: "123"'
|
12
|
-
option ['--title', '-t'], '', 'Title for the pull request.'
|
13
|
-
option ['--body', '-b'], '', 'Body for the pull request.'
|
10
|
+
execute do
|
11
|
+
@base ||= git.base_branch
|
12
|
+
@head ||= git.head_branch
|
13
|
+
@repo ||= git.repo_path
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
if @base == @head
|
16
|
+
error("Base and head branch can not be the same (#{@base}:#{@head})")
|
17
|
+
end
|
18
|
+
|
19
|
+
if @editor || @title.blank?
|
20
|
+
@title, @desc = Octokom::Editor.open(git, @title, @desc)
|
21
|
+
end
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
if @title.blank?
|
24
|
+
error('The title can not be empty')
|
25
|
+
end
|
26
|
+
|
27
|
+
if git.pending_commits?
|
28
|
+
task('Push latests commits') do
|
29
|
+
git.push(@head) unless dry_run?
|
30
|
+
end
|
31
|
+
end
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
33
|
+
authenticate do |auth|
|
34
|
+
task("Create pull-request #{@repo} (#{@base}:#{@head})") do
|
35
|
+
auth.client.create_pull_request(@repo, @base, @head, @title, @desc) unless dry_run?
|
27
36
|
end
|
28
37
|
end
|
29
38
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# This file is from "activesupport/lib/active_support/core_ext/object/blank.rb"
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def blank?
|
5
|
+
respond_to?(:empty?) ? !!empty? : !self
|
6
|
+
end
|
7
|
+
|
8
|
+
def present?
|
9
|
+
!blank?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class NilClass
|
14
|
+
def blank?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class FalseClass
|
20
|
+
def blank?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TrueClass
|
26
|
+
def blank?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Array
|
32
|
+
alias_method :blank?, :empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
class Hash
|
36
|
+
alias_method :blank?, :empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
class String
|
40
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
41
|
+
|
42
|
+
def blank?
|
43
|
+
BLANK_RE === self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Numeric
|
48
|
+
def blank?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Octokom
|
2
|
+
# The `Octokom::Editor` class opens and parses files where you can insert
|
3
|
+
# a title and description for pull-requests or GitHub issues. This class is
|
4
|
+
# inspired by the `Pry::Editor` class (http://pry.github.io).
|
5
|
+
class Editor
|
6
|
+
def self.open(repo, title, desc)
|
7
|
+
editor = Editor.new(repo, title, desc)
|
8
|
+
editor.prepare_tempfile
|
9
|
+
editor.open_editor
|
10
|
+
editor.parse_user_input
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(repo, title, desc)
|
14
|
+
@repo = repo
|
15
|
+
@title = title
|
16
|
+
@desc = desc
|
17
|
+
end
|
18
|
+
|
19
|
+
# Save a tempfile that includes the input from the command line or the
|
20
|
+
# last commit message.
|
21
|
+
# TODO Remove #Title and #Description section and use first line; following lines.
|
22
|
+
# TODO use last commit message for title
|
23
|
+
def prepare_tempfile
|
24
|
+
File.open(file_name, 'w') do |f|
|
25
|
+
f.puts '# Please enter a title and optional a description below'
|
26
|
+
f.puts '# the comments. To continue, save end close the file.'
|
27
|
+
f.puts "# Comment lines starting with '#' will be ignored."
|
28
|
+
f.puts
|
29
|
+
f.puts '# Title'
|
30
|
+
f.puts @title
|
31
|
+
f.puts
|
32
|
+
f.puts '# Description'
|
33
|
+
f.puts @desc
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Execute the command that is used to start the editor. This includes the
|
38
|
+
# waiting flag as well as the file and line number.
|
39
|
+
def open_editor
|
40
|
+
system("#{editor_name} #{waiting_flag} #{file_inc_line(file_name, 6)}")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Opens the tempfile once again to parse the content and return the `title`
|
44
|
+
# and `description`.
|
45
|
+
def parse_user_input
|
46
|
+
input = {title: [], desc: []}
|
47
|
+
parsing = false
|
48
|
+
|
49
|
+
read_tempfile.each do |line|
|
50
|
+
match = /^\s*#\s*(title|desc)/i.match(line)
|
51
|
+
parsing = match[1].downcase.to_sym if match
|
52
|
+
|
53
|
+
unless line =~ /^\s*#/
|
54
|
+
input[parsing] << line if parsing
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
input.update(input) { |_, lines| lines.join("\n") }.values
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def editor_name
|
64
|
+
@editor_name ||= ENV['GIT_EDITOR'] || ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
|
65
|
+
end
|
66
|
+
|
67
|
+
# Make sure the editor waits for the file to be closed.
|
68
|
+
def waiting_flag
|
69
|
+
case editor_name
|
70
|
+
when /^[gm]?vi/
|
71
|
+
'--nofork'
|
72
|
+
when /^subl/, /^mate/
|
73
|
+
'--wait'
|
74
|
+
when /^jedit/
|
75
|
+
'-wait'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Different editors have different ways how to open a file starting at
|
80
|
+
# a specific line number.
|
81
|
+
def file_inc_line(file_name, line)
|
82
|
+
case editor_name
|
83
|
+
when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
|
84
|
+
"+#{line} #{file_name}"
|
85
|
+
when /^mate/, /^geany/
|
86
|
+
"--line #{line} #{file_name}"
|
87
|
+
when /^subl/
|
88
|
+
"#{file_name}:#{line}"
|
89
|
+
when /^uedit32/
|
90
|
+
"#{file_name}/#{line}"
|
91
|
+
when /^jedit/
|
92
|
+
"#{file_name} +line:#{line}"
|
93
|
+
else
|
94
|
+
file_name
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def file_name
|
99
|
+
@file_name ||= "#{@repo.toplevel_path}/.git/OCTOKOM-#{Time.now.usec}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def read_tempfile
|
103
|
+
File.open(file_name).map(&:chop).reject(&:empty?)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|