octokom 0.0.2 → 0.0.3
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/.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
|